Periklis Ntanasis:
Master's Touch

fade out

JUPAR: Updating Java desktop applications made easy!

Introduction

A neat feature for a desktop application is to automatically notify the user for available updates or even provide automatic updating functionality.

Building such an architecture seemed interesting to me, so out of curiosity I’ve decided to implement a library and an architecture that can provide that kind of functionality.

I’ve created JUPAR wihch stands up for Java Updates Architecture! It is pretty simple and it’s main purpose is to act as a proof of concept for how this functionality could be implemented but one could easily use it in a real world application as well.

As you can easily assume I have implemented this concept to work with Java desktop applications but the same concept could easily be implemented in any other language too.

Below I’ll explain how this architecture works and later there will be a demostration.

Architecture

First of all the application checks if there is a newer version available.

It is doing this by checking an xml file located somewhere in a remote server. I am using xml files because they are easily configurable and Java has a built in API for parsing them.

So, when this is done we can notify the user that a new version is available and prompt him to manually download and install it or allow the application to perfom an automatic update.

Let’s assume now that an automatic update takes place. The application parses another xml file which contains links with all the files that we are going to need for the update.

Then all the needed files are downloaded and stored in a temporary directory. There, there is also an xml file with the instructions. Instructions are the steps that our application needs to follow in order to be updated.

There are 3 kind of instructions.

  • MOVE: Moves a new file in the place of an old one

  • DELETE: Deletes an old file

  • EXECUTE: Executes a new file (a jar actually)

The purpose of MOVE and DELETE is pretty obvious. The purpose of EXECUTE is to perform advanced operations such as updating a database scheme. This keeps JUPAR complexity low and provides more flexibility to the developer.

The hole procedure is described to the next figure.

JUPAR Architecture

Demonstration

Ok, let’s now see it in action!

I have built a dump application that displays the application version. We are going to see both the code that we should include in our application and the xml files that we should create!

Here is the relevant code:

/**
 * Check for new version
 */
int answer = -1;
Release release = new Release();
release.setpkgver("1.0");
release.setPkgrel("1");
ReleaseXMLParser parser = new ReleaseXMLParser();
try {
  Release current =
      parser.parse("http://niovi.aueb.gr/~p3070130/latest.xml", Modes.URL);
  if (current.compareTo(release) > 0) {
    answer =
        JOptionPane.showConfirmDialog(rootPane, "A new version of this"
        + " program is available\nWould you like to install it?",
        "Update", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE);
    switch (answer) {
      case 0:
        /**
         * Download needed files
         */
        Downloader dl = new Downloader();
        dl.download("http://niovi.aueb.gr/~p3070130/files.xml", "tmp", Modes.URL);
        break;
      case 1:
        break;
    }
  }
} catch (SAXException ex) {
  JOptionPane.showMessageDialog(rootPane, "The xml wasn't loaded succesfully!\n",
      "Something went wrong!", JOptionPane.WARNING_MESSAGE);
  Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  answer = -1;
} catch (FileNotFoundException ex) {
  JOptionPane.showMessageDialog(rootPane,
      "Files were unable to be read or created successfully!\n"
      + "Please be sure that you have the right permissions and"
      + " internet connectivity!",
      "Something went wrong!", JOptionPane.WARNING_MESSAGE);
  Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  answer = -1;
} catch (IOException ex) {
  JOptionPane.showMessageDialog(rootPane, "IOEXception!",
      "Something went wrong!", JOptionPane.WARNING_MESSAGE);
  Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  answer = -1;
} catch (InterruptedException ex) {
  JOptionPane.showMessageDialog(rootPane, "The connection has been lost!\n"
      + "Please check your internet connectivity!",
      "Something went wrong!", JOptionPane.WARNING_MESSAGE);
  Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  answer = -1;
}

/**
 * Start the updating procedure
 */
if (answer == 0) {
  try {
    Updater update = new Updater();
    update.update("update.xml", "tmp", Modes.FILE);
    JOptionPane.showMessageDialog(rootPane,
        "The update was completed successfuly.\n"
        + "Please restart the application in order the changes take effect.");
  } catch (SAXException ex) {
    Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  } catch (InterruptedException ex) {
    Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  } catch (FileNotFoundException ex) {
    Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  } catch (IOException ex) {
    Logger.getLogger(JUPARDemo.class.getName()).log(Level.SEVERE, null, ex);
  }
}

/**
 * Delete tmp directory
 */
File tmp = new File("tmp");
if (tmp.exists()) {
  for (File file : tmp.listFiles()) {
    file.delete();
  }
  tmp.delete();
}

Let’s see the highlights!

Check for new version

We set this applications version and release number. So version is a float number such as 1.0 , 1.1 etc and release is an integer starting from 1 and counting.

Then we set the remote xml with the current release info. Here is an example:

<?xml version="1.0" encoding="UTF-8"?>
<information>
    <pubDate>Sat, 24 Dec 2011 19:58:42 +0200</pubDate>
    <pkgver>1.0</pkgver>
    <pkgrel>2</pkgrel>
    <severity>normal</severity>
    <extra>
                <message></message>
    </extra>
</information>

Message can be a short description of the update that you can display to the user or something like that.

Severity can be used as you wish. You may for example perform automatic updates only when severity is high or whatever you like.

Back to the code, the Release Class implements the comparable interface so we can directly compare two different releases.

In this demo, if a new release is available an option dialog is displayed asking the user if he wants to update or not. As I said before one could just notify the user for the new version and don’t perform an automatic update.

Anyway, if the answer is positive we parse the files.xml file and download the needed files. Here how files.xml looks like:

<?xml version="1.0" encoding="UTF-8"?>
<download>
        <file>http://niovi.aueb.gr/~p3070130/update.xml</file>
        <file>http://niovi.aueb.gr/~p3070130/JUPARDemo.jar</file>
</download>

All the files are stored in a temporary directory.

Automatic Update

When the update starts we parse the instructions file which is by then stored localy. This is the last xml file and looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<update>
    <instruction>
        <action>MOVE</action>
        <file>JUPARDemo.jar</file>
        <destination>JUPARDemo.jar</destination>
    </instruction>
</update>

The above xml file contains an instruction that moves the new file JUPARDemo.jar from the temporary directory to the current working directory (see below) in the place of the old JUPARDemo.jar.

Finally we delete the temporary directory.

Pros and Cons

Advantages

JUPAR is pretty small and simple. Everyone could easily hack its code and add the kind of functionality that she needs. It’s pretty flexible, developer handles all the exceptions as she wants. It doesn’t require any server side software installed, it just needs some xml files in the right place!

Disadvantages

Lack of documentation.

All the exceptions are thrown for the developer to handle them. However, some times it’s not clear where the exception occurred. For example a method may throw IOException both when a file isn’t accessible through network or it hasn’t permissions to write a file locally. Most of the times this isn’t important as the developer will log the error and get the exact line where the exception occurred, however if we care to show an appropriate message to the user this is a problem.

Temporary files are stored to the current working directory and files are copied/deleted to/from the current working directory.

For example if our working directory is curdir and we execute our application like java -jar dist/JUPARDemo.jar the tmp file will be created in the curdir directory and then the new JUPARDemo.jar file will be copied to curdir too.

After some though Java doesn’t provides a clear way of finding the executable’s path. So, I have included an update method that takes also the executable’s path as an argument and let’s up to the developer how to handle this.

In my account there are 3 possible ways to do this.

Find the executable path with Java

You can try to find the executables path with a command like:

new File(MyClass.class.getProtectionDomain().getCodeSource()
.getLocation().toURI().gā€Œā€‹etPath());

However I am not sure if this works in every case and as far as I know it doesn’t works for JVMs previous to 1.5.

Use a predefined path

It is possible to use a predefined path in your Java application.

There are 2 supported ways to accomplish this.

First way

Use a full path to set the temporary directory:

dl.download("http://niovi.aueb.gr/~p3070130/files.xml",
 "/home/periklis/Workspace/JUPARDEMO/dist/tmp", Modes.URL);
...
update.update("update.xml",
"/home/periklis/Workspace/JUPARDEMO/dist/tmp", Modes.FILE);
...
File tmp = new File("/home/periklis/Workspace/JUPARDEMO/dist/tmp");

And then have an update.xml with also full paths.

<?xml version="1.0" encoding="UTF-8"?>
<update>
  <instruction>
   <action>MOVE</action>
   <file>JUPARDemo.jar</file>
   <destination>/home/periklis/Workspace/JUPARDEMO/dist/JUPARDemo.jar</destination>
  </instruction>
</update>

This is how demo fullv1 and fullv2 that comes with JUPAR is implemented.

Second way

This way is actually like the first one but it doesn’t need a modified updates.xml. As I mentioned before, after some consideration I decided to provide an update method that takes the executable’s full path as an argument.

So we only need to have something like that:

dl.download("http://niovi.aueb.gr/~p3070130/files.xml",
 "/home/periklis/Workspace/JUPARDEMO/dist/tmp", Modes.URL);
...
update.update("update.xml",
"/home/periklis/Workspace/JUPARDEMO/dist/tmp",
"/home/periklis/Workspace/JUPARDEMO/dist", Modes.FILE);
...
File tmp = new File("/home/periklis/Workspace/JUPARDEMO/dist/tmp");

Both first and second way work but comes with a great limitation. The developer should maintain different versions of the executable for every different system that she wants to deploy it. Ok, in a way this is how things work in the linux world where the package maintainer maintains a package adjusted to her distribution. However, this shouldn’t be considered as granted so I’ll continue with the 3rd approach that I think is the most smooth.

Use a predefined path from local configuration

This way is actually like the above with the difference that the developer will read the executable’s path from a configuration file. This has the avantage that the package maintainer doesn’t need to recompile the application to add the full path.

By convention the configuration file could be in /home/user/.appname in linux systems and to the corresponding in Windows.

Disadvantages Conclusion

There are some problems into JUPAR but the developer can easily overcome them and that is due to JUPAR’s flexibility. If there is a best way to overcome a problem one could easily implement it into JUPAR and share some love :)

Todo List

Here are some thoughts that I may look into the future:

  • Store the file/filepaths as File objects and not as Strings

  • Throw more detailed Exceptions

  • Let the methods that use an URLConnection to set the timeout value

  • Write some documentation

  • Add JSON and maybe plain text support other than xml

  • Test it!

Comments

fade out