Download Make Applications Update-Aware

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
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);
}
}