Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Make Applications Update-Aware Employ the Java Web Start technology to place JAR files on a Web server to facilitate errorfree updates by Mark Nadelson I have multiple Java-based applications running throughout the company wide network. Some applications are running in various locations in the United States, some are running in London, and others are running in Singapore. These applications include both stand-alone daemon applications with the requirement to run 24-7, and others are GUI-based client applications executed by users at their desktops. My initial setup was to create the required Java Archive (JAR) files and locate them on a host to which all locations have access. I then set up executable batch files that can launch the given applications. This setup worked great! Then I received my first phone call. London and Singapore were getting horrible performance because they had to load the JAR files from across oceans and continents. No problem. Being the clever guy that I am, I got our system administrators to copy the needed JARs to a host local to each location and created location-specific executable batch files (with the CLASSPATH set up to point to the local JARs). This setup worked great! Then I made my first code update. I updated the JAR files located on the original host machine. Two very bad things happened. The first was that all programs using the updated JARs started getting strange errors. I realized this problem occurred because I updated the JAR files without taking down and restarting all applications using it. This unfortunate oversight caused corruption within currently executing applications. The other very bad thing that occurred was the result of forgetting about London and Singapore. I never updated their local JARs, and no one could understand why they were getting results that differed from my local users' results. What a mess! Now every time I needed to do a release I had to take down all applications associated with the JAR being updated as well as notifying the system administrators to copy the JARs to their location-specific hosts. I also had to keep track of which applications used which JARs because they would all have to be taken down before an update could occur. I learned this lesson the hard way when I forgot that some of the applications shared a base JAR file and did not terminate all of them before doing my update. Got to Be a Better Way Luckily there is a better approach. Java Web Start was the answer to my deployment and code synchronization problems. The Java Web Start technology allows the developer to place all JARs on a Web server (see Resources). Included with the JARs is a configuration XML file with a JNLP extension. Java Web Start uses this configuration file for determining the JARs (and other resources) needed for the application as well as how to execute the application. The execution specification includes the version of the Java Runtime Environment to run, the command to run, its arguments, and any system properties that need to be set. Once the application has been placed on the Web server (and assuming Java Web Start is installed on the local host), users can execute the application simply by pointing their Web browser to the URL address of the JNLP file. This application activates Java Web Start and the magic begins. Java Web Start checks to see if the resources required are cached on the user's host machine. If not, Java Web Start downloads all required resources and executes the application once the resources are downloaded. The next time the same application is started, Java Web Start compares the resources stored on the local cache to the resources stored on the Web server. If the resources on the Web server have been updated, Java Web Start downloads all updates before launching the application. To quote Emeril, "BAM!" All my problems are solved. I have an automated way of ensuring all applications running in all locations have the latest and greatest code files. I also don't have to worry about corrupting running applications from copying over the JAR they are referencing, because the JARs used are stored locally. Did I say all my problems are solved? I lied. What about my applications that run 24-7? Java Web Start synchronization occurs only when the application is initially launched. It doesn't help if the application is already running. It also doesn't help client applications if a critical bug was fixed. Users running the client app have to be notified manually of the new release and that they need to restart. To solve the problem of applications being informed of updates during execution, I developed an object to check if the resources required have been updated on the Web server. This object is called the ResourceChecker (see Listing 1). ResourceChecker is constructed using the name of the application's JNLP file. Using the JNLP file, a javax.jnlp.BasicService object is retrieved through the Service.lookup() method. BasicService is then used to derive the URL root path used to download the application. Calling BasicService.getCodeBase retrieves the URL(). Using the URL, the current state (size and time created) of all resources required by the application is retrieved and cached. The resources are cached within resourceMap. The name of the resource is its key, and the latest size and time the resource was created (stored within a ResourceInfo object) are stored as its value. Storing State Upon construction, ResourceChecker caches the current state of all application-specific resources located on the Web server using cacheResources(). A better approach would be to store the current state of all resources from the local host's cache, which would be ideal because there could be a situation between the time the application was downloaded and started and the time the Web server was updated that would be missed by subsequent examinations by the ResourceChecker. (If anyone knows how to check the local cache, forward it on. I would love to know.) The first resource checked is the JNLP file itself. A URLConnection is opened to the JNLP file, and the JNLP's size and time created are stored within resourceMap. Next, the JNLP file is downloaded. Given the JNLP file, all resource dependencies are extracted using getResourceList(). The getResourceList() method returns a Collection of resource names denoted by the jar, nativelib, or extension XML tags located within the JNLP file. The names are contained within the href attribute tag. The cacheResources() method iterates through the collection of resource names, opening a URLConnection to each one. The resource name and its associated ReportInfo object are stored within resourceMap. The haveResourcesChanged() method is used to determine whether the resources on the Web server have changed. The haveResourcesChanged() method downloads the current attributes of the resources from the Web server and are compared to the attributes cached within resourceMap. The resources that are checked are file size and time stamp. A better approach might be checking a checksum on the files. It has been brought to my attention that there are cases when a file may be changed but the file size and time stamp remain constant. If the file attributes differ, haveResourcesChanged() returns TRUE; otherwise FALSE is returned. If the value returned from haveResourcesChanged() is TRUE, you have the option to terminate the current execution and relaunch the application using relaunch(). The relaunch() method constructs a URL string containing the fully qualified path of the JNLP file, constructs an argument array using javaws.exe (Java Web Start) and the JNLP URL, and spawns the application. After the application is relaunched, System.exit() terminates the application's current execution. To see the ResourceChecker in action, I've included a small example application that is a Web Browser Controller. It's a simple GUI that takes a user-entered URL and redirects the Web browser to the entered address (see Figure 1. Web Browser Figure 1). This application is started using Java Web Start, Controller which must be installed on the client machine (see Sun's Java Web Start documentation for installation instructions), and the BrowserController.war file must be deployed to your Web server of choice. If all is correctly installed, executing the Browser Controller is accomplished by pointing your Web browser to http://your_web_server's_host_address/BrowserController/BrowserController.jnlp. Test the Functionality The first time Browser Controller is run, the resources defined within BrowserController.jnlp are downloaded (see Listing 2). Once Java Web Start completes the download, it executes Browser Controller using the command defined in the <application-desc> JNLP tag. When BrowserController (see Listing 3) is constructed, it creates an instance of ResourceChecker. Each time users enter a Web address and attempt to direct their browser to it, checkResourcesChanged() is called. At this point the size and time stamps of BrowserController.jnlp and BrowserController.jar (located on the Web server) are compared to ResourceChecker's cache. If either is different, the user is notified and prompted to restart BrowserController. If the user chooses to restart, Java Web Start is called given the JNLP URL, and the latest version of BrowserChecker is downloaded and restarted. Finally, the old version of Browser Controller exits. To test this functionality: 1. Change the name in the <vendor> JNLP tag and redeploy. Surf to a Web address and choose to reload. Notice that the next time Java Web Start runs BrowserChecker, the name in the splash screen is different. 2. Change the title of the BrowserChecker GUI, replace <Insert Your Name Here> with your name, recreate the BrowserController.jar, and redeploy. Surf to a Web address and choose to reload. Notice that the next time Java Web Start runs BrowserChecker, the BrowserChecker title contains your name. Now I'm happy. The Java Web Start architecture gives me the ability to keep all locations using the updated application in sync. These locations also all have local copies; therefore, performance degradation over the WAN is no longer an issue. To strengthen the synchronization and deployment of applications, an imbedded ResourceChecker object checks to see if an upgrade is available and can either restart the parent application automatically or prompt users by asking if they would like to restart. So far, this setup is working great! About the Author Mark Nadelson is a project manager at Sempra Energy Trading. He has worked as a programmer in a variety of industries including telecommunications, Internet, and finance. His published works include magazine articles and two books: Making Unix and Windows NT Talk (CMP Books, 1999) and C++ Objects for Making UNIX and WinNT Talk (CMP Books, 2000). Reach Mark at [email protected]. Listing 1 ResourceChecker Listing 1. The ResourceChecker object solves the problem of applications being informed of updates during execution. It checks if the resources required have been updated on the Web server and is constructed using the name of the application's JNLP file. package JavaWebStart; import javax.jnlp.*; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers. ParserConfigurationException; import java.util.HashMap; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.net.*; import java.io.*; import org.w3c.dom.*; import org.xml.sax.SAXException; /** * This class is used to check application-specific * recources defined within a Java Web JNLP * specification. */ public class ResourceChecker { private HashMap resourceMap = new HashMap(); // Map of resources and their attributes private Collection resourceList; // List of resource names private String jnlpFile; // Name of the JNLP file private URL rootURL; // The root URL path to the // Java Web Start resources private final String resourceTags[] = {"jar","nativelib","extension"}; /** * Constructor. Takes a JNLP file name and caches * the resources and attributes contained within */ public ResourceChecker( String _jnlpFile) throws UnavailableServiceException, MalformedURLException, IOException, ParserConfigurationException, SAXException { jnlpFile = _jnlpFile; // Get the root URL path of the Java Web Start // resources BasicService bs = ( BasicService)ServiceManager.lookup( "javax.jnlp.BasicService"); rootURL = bs.getCodeBase(); // Initialize resourceMap cacheResources(); } /** * Creates the cache of initial Java Web Start * resource attributes */ private void cacheResources() throws MalformedURLException, IOException, ParserConfigurationException, SAXException { URL jnlpURL = new URL( rootURL.toString() + jnlpFile); URLConnection jnlpURLConnection = jnlpURL.openConnection(); resourceMap.put("JNLP", new ResourceInfo( jnlpURLConnection)); resourceList = getResourceList( jnlpURLConnection); Iterator resourceListI = resourceList.iterator(); while(resourceListI.hasNext()) { String resourceName = ( String)resourceListI.next(); URL resourceURL = new URL( rootURL.toString() + resourceName); URLConnection resourceConnection = resourceURL.openConnection(); resourceMap.put(resourceName, new ResourceInfo(resourceConnection)); resourceConnection = null; resourceURL = null; } jnlpURL = null; jnlpURLConnection = null; } /** * Determines if Java Web Start resources have * been updated on the Web Server @returns TRUE * if the resources have been updated, FALSE * otherwise */ public boolean haveResourcesChanged() throws MalformedURLException, IOException { URL jnlpURL = new URL( rootURL.toString() + jnlpFile); URLConnection jnlpURLConnection = jnlpURL.openConnection(); ResourceInfo resourceInfo = ( ResourceInfo)resourceMap.get("JNLP"); ResourceInfo currentResourceInfo = new ResourceInfo(jnlpURLConnection); if (!resourceInfo.equals(currentResourceInfo)) { return true; } currentResourceInfo = null; Iterator resourceListI = resourceList.iterator(); while(resourceListI.hasNext()) { String resourceName = ( String)resourceListI.next(); URL resourceURL = new URL( rootURL.toString() + resourceName); URLConnection resourceConnection = resourceURL.openConnection(); resourceInfo = ( ResourceInfo)resourceMap.get(resourceName); currentResourceInfo = new ResourceInfo(resourceConnection); if (!resourceInfo.equals(currentResourceInfo)) { return true; } currentResourceInfo = null; resourceConnection = null; resourceURL = null; } jnlpURL = null; jnlpURLConnection = null; return false; } /** * Executes javaws using the URL of the JNLP * file. This causes Java Web Start to * synchronize and reload the parent application. * Once javaws is executed, the parent * applicationÕs current execution is terminated. */ public void relaunch() throws MalformedURLException, IOException { String args[] = new String[2]; URL jnlpURL = new URL( rootURL.toString() + jnlpFile); Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec( "c:\\program files\\java web start\\javaws.exe " + jnlpURL.toString()); try { process.waitFor(); } catch( Exception ex) { } System.exit(0); } /** * The JNLP file is downloaded from the Web Server * and all resource names are extracted and * returned within a Collection */ private Collection getResourceList( URLConnection _urlConnection) throws ParserConfigurationException, SAXException, IOException { ArrayList resourceList = new ArrayList(); Document xmlDocument = null; Element xmlObjectElement = null; int length = _urlConnection.getContentLength(); byte buffer[] = new byte[length]; InputStream inStream = _urlConnection.getInputStream(); inStream.read(buffer, 0, (int)length); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); ByteArrayInputStream xmlStream = new ByteArrayInputStream(buffer); xmlDocument = builder.parse(xmlStream); for (int resourceCount = 0; resourceCount < resourceTags.length; resourceCount++) { NodeList nodeList = xmlDocument.getElementsByTagName( resourceTags[resourceCount]); for (int count = 0; count < nodeList.getLength(); count++) { Node node = nodeList.item(count); NamedNodeMap attributes = node.getAttributes(); for (int attributeCount = 0; attributeCount < attributes.getLength(); attributeCount++) { Node attributeNode = attributes.item( attributeCount); String name = attributeNode.getNodeName(); if (name.equals("href")) { resourceList.add( attributeNode.getNodeValue()); } } } } return resourceList; } } Figure 2 BrowserController.jnlp Listing 2. When the Browser Controller application is first run, resources within its JNLP file are downloaded. <xml version="1.0" encoding="utf-8"?> <!– JNLP File for Browser Controller Demo Application –> <jnlp codebase="$$codebase" href="$$name"> <information> <title>Web Browser Controller</title> <vendor>Mark Nadelson</vendor> <description>An Application To Control Your Web Browser!</description> </information> <resources> <j2se version="1.4+"/> <jar href="lib/BrowserController.jar"/> </resources> <application-desc main-class= "demo.BrowserController"/> </jnlp> Figure 3 Browser Controller Listing 3. When BrowserController is constructed, it creates an instance of ResourceChecker. Each time users enter a Web address and attempt to direct their browsers to it, the app calls checkResourcesChanged(). package demo; import JavaWebStart.ResourceChecker; import javax.jnlp.*; import javax.swing.*; import javax.xml.parsers. ParserConfigurationException; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.*; import java.net.URL; import java.net.MalformedURLException; import java.io.IOException; import org.xml.sax.SAXException; /** * BrowserController takes a user inputted URL and * redirects the default Web Browser to surf to it. * When started with Java Web Start, * BrowserController knows when a new of its * dependencies is available and will prompt the * user to download it on their next surf attempt. */ public class BrowserController extends JFrame implements ActionListener { private JTextField webAddressField = new JTextField(20); private JButton surfButton = new JButton("Surf"); private JButton exitButton = new JButton("Exit"); private BasicService basicService; private ResourceChecker resourceChecker; { getContentPane().setLayout(new BorderLayout()); { JPanel webAddressPanel = new JPanel( new FlowLayout(FlowLayout.CENTER)); webAddressPanel.setBorder( BorderFactory.createRaisedBevelBorder()); { webAddressPanel.add(new JLabel( "Enter Your Web Address")); webAddressPanel.add(webAddressField); webAddressField.addActionListener(this); } getContentPane().add(webAddressPanel, BorderLayout.CENTER); JPanel buttonPanel = new JPanel( new FlowLayout(FlowLayout.CENTER)); buttonPanel.setBorder( BorderFactory.createRaisedBevelBorder()); { buttonPanel.add(exitButton); exitButton.addActionListener(this); buttonPanel.add(surfButton); surfButton.addActionListener(this); } getContentPane().add(buttonPanel, BorderLayout.SOUTH); } addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); } public BrowserController() { super( "Browser Controller By <Insert Your Name Here>"); try { resourceChecker = new ResourceChecker( "BrowserController.jnlp"); basicService = (BasicService)ServiceManager. lookup("javax.jnlp.BasicService"); if (!basicService.isWebBrowserSupported()) { JOptionPane. showMessageDialog(this, "Sorry Your Web Browser is Not Supported. Goodbye."); System.exit(0); } } catch(UnavailableServiceException ex) { JOptionPane.showMessageDialog(this, "Error Getting BasicService: " + ex); System.exit(0); } catch(MalformedURLException ex) { JOptionPane.showMessageDialog(this, "Error Caching Web Start Resources: " System.exit(0); } catch(IOException ex) { JOptionPane.showMessageDialog(this, "Error Caching Web Start Resources: " System.exit(0); } catch(ParserConfigurationException ex) { JOptionPane.showMessageDialog(this, "Error Caching Web Start Resources: " System.exit(0); } catch(SAXException ex) { JOptionPane.showMessageDialog(this, "Error Caching Web Start Resources: " System.exit(0); } + ex); + ex); + ex); + ex); } public void actionPerformed(ActionEvent _evt) { if (_evt.getSource() == exitButton) { System.exit(0); } else if (_evt.getSource() == surfButton || _evt.getSource() == webAddressField) { try { if (resourceChecker.haveResourcesChanged()) { if (JOptionPane.showConfirmDialog( this, "This Application Has A New Version Available\nWould You Like To Restart Using The Lastest Version?") == JOptionPane.YES_OPTION) { resourceChecker.relaunch(); } } } catch(MalformedURLException ex) { JOptionPane.showMessageDialog(this, "Error Checking Web Start Resources: " + ex); } catch(IOException ex) { JOptionPane.showMessageDialog(this, "Error Checking Web Start Resources: " + ex); } String webAddress = webAddressField.getText().trim(); if (webAddress == null || webAddress.length() == 0) { JOptionPane.showMessageDialog(this, "Please Enter A Web Address"); } else { try { basicService.showDocument( new URL(webAddress)); } catch(MalformedURLException ex) { JOptionPane.showMessageDialog(this, "URL Address Entered is Invalid: " + ex); } } } } public static void main(String args[]) { BrowserController browserController = new BrowserController(); browserController.setSize(500, 100); browserController.setVisible(true); } }