How to Create Update-Aware Application

Hi, I was wondering how it would be possible to create an update-aware application (please note that I am not talking about Java Webstart, it’s a Java application somebody downloaded and installed on this computer.)?

Actually, the application should just mirror the behavior of Java Webstart. The program checks the JNLP file on the server and updates itself if necessary. I found a related article: http://www.ftponline.com/javapro/2003_06/magazine/features/mnadelson/

(EDIT: Moderator: BlahBlahBlahh: I’ve just seen that webpage crash two different computers running firefox/mozilla, so inserting a warning here. I suspect they’re running some broken javascript as an advert)

Most gamers do like to “download” games, rather than use “webstart”. I deliver my game both as “webstart” and “downloadable version”. Webstart is auto-update enabled by default, however, I would also like to add that capability to the downloadable version. The downloadable version basically should do the same as Java Webstart - checks the .JNLP webstart file and updates itself.

Should not be rocket science? How about something like that to start with (This code is for Java Web Start), do you think that would work (it’s from the article above). One problem with the code is the “runtime.exec” command, which would only work for Windows! I would need something platform independent!:


The ResourceChecker object solves the problem of Java web start 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;
   }
}



Just embed a version number in the code and have it check the server for the most recent version number and if they don’t match, re-download or download an update.

Alright, that might not be too hard. However, if I download new *.jar files, I will need to restart the JVM. How do I do that independent if I run Windows, Linux, Mac, Unix, …?

Or distributed an installer with a pre-initialized JWS cache and use JWS.

That’s actually a good idea! This also has to work on all major operating systems + auto-install of JRE.

So, that’s what I came up with:

  1. Create a small Java Application ProgramRunner.java (started as regular application - that’s the only one install4j supports):

public ProgramRunner {
     // args[0] contains the path to the jnlp: e.g. http://www.x.com/test.jnlp
   public static void main(String args[]) {
     -> check if Java version required in .jnlp (args[0]) is <= the one in the current JVM {java.version}
     -> if NO: tell to download new version of software/game from web site
     -> if YES: start game through webstart: Runtime.exec("{java.home}/bin/javaws args[0]");
   }
}

  1. Wrap that little program into an installer which does automatic deployment of JRE + setup of menu icon + install screen.
    install4j looks pretty cool, that should work.

It might seem complicate, but these are the benefits:
A) It looks like a regular installation for a user/gamer - The game shows up in the start menu/desktop (icons) like any other game!
B) Simple download and install (JRE is installed automatically: install4j) / no hassles
C) install4j generates installers for Windows, Unix, Linux, Mac …
D) The user because he actually runs web start gets the newest versions automatically - Web Start is auto-update enabled!!!
E) Updating the game is easy! Just update the webstart files!!! the application installed with the installer goes to the webstart location every time to check for new versions!

Does this make sense? Any comments? That should be pretty straightforward to implement?

The goal is to make it look like a regular application with auto-update capabilities like web start. That is what is does. An application (java) launches Java web start (javaws) which not only starts the application but also updates it if required!

search for “xito” : it provides an update framework similar to JWS but this one can be fully integrated in a standalone application.

Haven’t used myself, but planning to test it in the coming month.

Lilian :slight_smile:

To prevent launching a new process, make a ClassLoader that loads the (updates) archives. Make sure the ClassLoader loading the main-class can’t find the archives, to prevent two versions of the class in the JVM.

Have a look into the “alternative” java webstart download system - Sun provides two of them, an easy one and a not-so-easy one. The not-so-easy-one is the recommended + preferred, fixes several bugs with the easy one, and … is based upon version numbers in the files :).

So, rather than re-invent the wheel, try implementing sun’s official version-based JNLP system.

As noted above (in case it wasnt clear, the comments were separated a lot), your files should (as in: IIRC “definitely are”, but I cant remember if theres any gotcha specifically to do with JAR files) be attached to the ClassLoader, not the JVM. So, for instance, you can dump the classloader and create a new one to force gettign different versions of classes.

Yes, that’s definitly the way to go!

That’s what I came up with. People install that code with Install4J or InstallAnywhere on their computer (automatic menu/desktop integration). No need to hassle with Java downloads.

The application updates itself automatically by using the web start update functionality. However, for the user it looks like a regular application on their computer which they start through the start-menu. What you think?


package king.lib.runner;

import java.io.IOException;

/**
 * Allows to run a webstart application.
 *
 * @author  Christoph Aschwanden
 * @since  March 30, 2006
 */
public final class WebstartRunner {

  
  /**
   * Private to prevent instantiation of this class.
   */
  private WebstartRunner() {
    // does nothing.
  }
  
  /**
   * Runs the inputted jnlp file. 
   * 
   * @param jnlp  The location of the online jnlp to run.
   */
  public static void run(String jnlp) {
    // find if OS is windows
    String osName = System.getProperty("os.name").toLowerCase();
    boolean windowsOS = osName.indexOf("windows") > 0;
    
    // find Java home directory
    String javaHome = System.getProperty("java.home");
    String fileSeparator = System.getProperty("file.separator");
    
    // set quoting style (windows only can use quotes)
    String quote = windowsOS ? "\"" : "";
    
    // build list of commands
    String[] cmd = new String[2];
    cmd[0] = quote + javaHome + fileSeparator + "bin" + fileSeparator + "javaws" + quote;
    cmd[1] = jnlp;
    
    // set property!
    System.setProperty("runtime.start.method", "install4j");
    System.setProperty("runtime.start.version", "1.0.0");
    
    // and execute command!
    try {
      Runtime.getRuntime().exec(cmd);
    }
    catch (IOException e) {
      System.err.println("Cannot start the webstart file (" + jnlp + "): " + e);
    }
  }
  
  /** 
   * The main method which will be called.
   *
   * @param args  The arguments for the webstart runner. args[0] should be the location of the
   *              .jnlp file to run.
   */
  public static void main(String[] args) {
    run(args[0]);
  }
}