Chapter 0: Java Network Launching Protocol

The Java Network Launching Protocol is a new Java protocol to allow remote applications to be downloaded and run on a client machine. All necessary class files, images, etc, can be downloaded, and do not need to be present on the client machine beforehand. This chapter looks at JNLP and compares it to Jini, and shows how the two can be used together to get the advantages of both

1. JNLP

The concept of applets was one of the initial boosts for Java's success. However, while Java has gone from strength to strength, applets have fallen by the wayside. This is partly because the sandbox model is so tight that there is little you can do with them, but also because the Java runtime engines in the different browsers were inconsistent and often buggy, frequently causing the browser to crash.

THE JNLP is another approach to this type of problem, where remote code is sent to a browser for execution. Instead of running within the browser though, the code is run in a manner similar to CGI scripts by passing it to a Java runtime engine that runs as a separate application. There are plenty of mechanisms within JNLP to ensure safety in a variety of sandbox (or freer) models, and also to ensure that resources required are present (such as a late enough version of the Java runtime).

2. JNLP file

A JNLP application is described by a JNLP file, which is an XML document. Typically, this document will be downloaded by a Web browser. The browser itself will not know what to do with this file, so it will pass it to a "JNLP helper" to deal with. This is a standard browser technique, and allows a browser to call a Postscript viewer such as ghostscript to handle Postscript files, Adobe acrobat to handle PDF files, and so on. The JNLP helper will read the list of resources required and download them if necessary. It will also check that all constraints such as version of the Java runtime are satisfied. Once this is all done, the helper will start the Java application running.

A minimal JNLP file can be


<?xml version="1.0" encoding="UTF-8"?>
<jnlp codebase="http://www.jini.monash.edu.au"/>
  <information>
    <title>File Classifier Application</title>
  </information>
  <resources>
    <jar href="FileClassifierApplication.jar"/>
  </resources>
  <application-desc main-class="jnlp.FileClassifierApplication"/>
</jnlp>
This states the codebase from where all resources will be drawn, the jar file containing the classes and the main class of an application (rather than an applet). This is enough for the helper to run the application.

There is of course far more that can be specified in a JNLP file, but that is enough to get going. The JNLP specification has the full details.

3. Jini and JNLP Comparison

On the face of it, Jini and JNLP appear to occupy similar spaces in that they both allow code to be migrated to a client machine and to execute there. The following table summarises some of the differences and similarities
Jini JNLP
downloads a service downloads an application
a client must be running to request a service a browser must be running which calls a helper to start the application
each service looks after itself, independently of any clients each JNLP file specifies all required parts of an application; if any part changes, the JNLP file must be updated
the client may need to know the location of a lookup service, but not of any service the user of a JNLP application must know the URL of the JNLP file
no generic client generic JNLP helper

4. Combining Jini and JNLP

If a user does not have the client-side of a Jini application installed, then they cannot make use of Jini services. Here is where JNLP can help, by allowing a user to download an application that can run as the client. The user only needs to know a URL for this, and finding URLs is a common experience for most users nowadays. Corporate Web sites, Web search engines, portals and so on are all mechanisms used to find interesting Web sites and download information.

The converse question is: if JNLP is used to find an application, what is the value of Jini in this? The answer to this lises in the distributed management of Jini services. Suppose a JNLP application relies on a number of components put together, say as a collection of packages. The JNLP file has to specify the location of every one of these packages. If one of the packages changes, then the JNLP file has to be updated. It gets more complex if one of the packages changes to become dependant upon another new package: that new package has to be added to the JNLP file. In other words, management of a JNLP application has to be centralised, to the manager of the JNLP file.

Jini, on the other hand, lets every package/service be managed by its own manager. If a Jini service changes implementation, then it can do so without any external consultation, and just re-registers the new implementation with Jini lookup services. If a service changes to use another service, it does not need to inform anyone else about this change. A Jini client does not need to know how services are implemented ot even where they are located.

Jini is not quite management-free: a client may need to know where lookup services reside. On a local network a client can use multicast to locate lookup services, but outside of this local network clients will need to used unicast to find lookup services at known locations. This is still an improvement: Jini lookup services are relatively stable, persistent and stationary services, whereas the services themselves may be transient or unstable.

The combination of Jini and JNLP works like this:

  1. A user finds the URL for an application
  2. The user downloads the JNLP file which is executed by the JNLP helper
  3. The helper downloads the application's jar files, and runs a Java runtime engine with these jar files
  4. Properties are set in the JNLP file for Jini lookup services, so the application can locate its required Jini lookup services at runtime
  5. When the application needs a service, it finds it from the Jini lookup services and uses it
  6. If the service needs to make use of other services, then it can perform its own search for them, without the knowledge of the application

5. Configuring the Browser

In order to use JNLP, the browser must be configured to call the JNLP helper when it is sent a .jnlp file. This means firstly that a MIME type must be associated to files with the .jnlp extension:


application/x-java-jnlp-file      jnlp
Then the browser must be told what action to perform on such files:

application/x-java-jnlp-file      $JAVAWS_HOME/javaws %s

The mechanism to do this depends on the browser. For example, for Netscape it is done from the Navigator-Applications-New menu

6. Non-core Files

When an application is run by the JNLP helper javaws the runtime environment is setup to include the Java core packages and the JNLP package. There is basically nothing else that can be assumed about the runtime environment. So you can't make typical Jini assumptions that there is a client running that will already know what services to ask for. This is what JNLP is good for: it can start such a client. But you have to work at getting the environment correct for that client, which means including enough information in the JNLP file.

If the downloaded client is going to ask for a service, then it needs to know the class files for that service. The JNLP helper will not be able to supply the class files unless they are included in jar files specified by the JNLP file. So the jar file FileClassifierApplication.jar should include


jnlp/FileClassifierApplication.class
common/FileClassifier.class
common/MIMEType.class

Non-core packages such as Jini are not included in the runtime environment. There is at present no way to specify that an additional set of packages such as Jini must be available in the runtime. If the application runs as a Jini client, then it must have access to the Jini class files, and the only way it can do this to download them from the network. So the application must make the Jini class files available from a Web server, and must also reference them in the JNLP file:


<?xml version="1.0" encoding="UTF-8"?>
<jnlp codebase="http://www.jini.monash.edu.au"/>
  <information>
    <title>File Classifier Application</title>
  </information>
  <resources>
    <jar href="FileClassifierApplication.jar"/>
    <jar href="jini-core.jar"/>
    <jar href="jini-ext.jar"/>
    <jar href="sun-utils.jar"/>
    <jar href="tools.jar"/>
  </resources>
  <application-desc main-class="jnlp.FileClassifierApplication"/>
</jnlp>
JNLP will try to cache these files, so they only need to be downloaded once. It will still slow things down the first time.

There is a licensing issue associated with this: those who have downloaded the Jini classes will have signed a license giving conditions of use, and putting the classes on a public Web server will not meet these conditions. So you can't presently have a worldwide accessible Web application that uses Jini - it must be protected against access which will invalidate the license.

If the downloaded client uses a class such as ServiceDiscoveryManager, then it will try to install listeners on the lookup services it discovers. It will need to specify a codebase for the lookup services to download the class files for the listener. Usually, the codebase is specified as a command line parameter, but we don't have access to the command line for applications run by javaws. So the application needs to explicitly set this parameter using


System.setProperty("java.rmi.server.codebase", 
                   "http://.../classes/");
The host does not have to be a local machine, just one accessible by any lookup services that need to use it to download code.

7. Security

JNLP has a security model based on Java 1.2 security. However, it only supports three possible situations, unlike Jini which can deal with arbitrary security policies. This limited choice may be broadened in the future. The choices are

  1. Sandbox model. This is far too restrictive for Jini as it does not allow for connection to arbitrary hosts
  2. J2EE application client. This models the security requirements of a typical entreprise edition client, but again may be too restrictive. For example, it does not allow the codebase property to be set for Jini
  3. AllPermissions. This it too open for most Jini clients, and is not a good idea. But it is the only model that is open enough for Jini to function correctly

The AllPermissions model is the only one that will allow Jini to work. An application can request this model in the JNLP file. The JNLP runtime applies two checks before running the application:

  1. Every jar file in the JNLP file must be signed, by the same authority
  2. The JNLP runtime will prompt the user to confirm that is okay to run the application from this signed authority
These mitigate the chances that the application will run amok, but they are no guarantee of safety. After the application has reformatted your disk, you may regret agreeing to a request that you can no longer check up on.

The JNLP file now looks like


<?xml version="1.0" encoding="UTF-8"?>
<jnlp codebase="http://www.jini.monash.edu.au"/>
  <information>
    <title>File Classifier Application</title>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
    <jar href="FileClassifierApplication.jar"/>
    <jar href="jini-core.jar"/>
    <jar href="jini-ext.jar"/>
    <jar href="sun-utils.jar"/>
    <jar href="tools.jar"/>
  </resources>
  <application-desc main-class="jnlp.FileClassifierApplication"/>
</jnlp>

8. Setting Lookup Service Locations

The client should not have the locations of the lookup services hard-coded in, but should instead pick them up from the JNLP file. This is not totally robust, as lookup services can disappear just like other services. But they are relatively stable and information could be given in a fixed configuration file updated as needed. Similarly, the codebase for any class definitions required for the application can also be specified.

JNLP allows system properties to be specified in the JNLP file. This is just another attribute in the file


<?xml version="1.0" encoding="UTF-8"?>
<jnlp codebase="http://www.jini.monash.edu.au"/>
  <information>
    <title>File Classifier Application</title>
  </information>
  <security>
    <all-permissions/>
  </security>
  <resources>
    <jar href="FileClassifierApplication.jar"/>
    <jar href="jini-core.jar"/>
    <jar href="jini-ext.jar"/>
    <jar href="sun-utils.jar"/>
    <jar href="tools.jar"/>
    <property name="LookupServices" value="jini://www.jini.monash.edu.au"/>
    <property name="codebase" value="http://www.jini.monash.edu.au/classes/"/>
  </resources>
  <application-desc main-class="jnlp.FileClassifierApplication"/>
</jnlp>

9. Example

We shall use the familiar file classifer example for this. The extra information needed for this has already been discussed



/**
 * FileClassifierApplication.java
 */

package jnlp;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.util.StringTokenizer;
import java.util.Vector;

import net.jini.core.lookup.ServiceItem;
import common.MIMEType;
import common.FileClassifier;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.LookupCache;
import net.jini.core.lookup.ServiceItem;
import net.jini.lease.LeaseRenewalManager;
import net.jini.core.discovery.LookupLocator;
import net.jini.discovery.LookupLocatorDiscovery;

import java.net.InetAddress;
import java.net.UnknownHostException;

import net.jini.core.lookup.ServiceRegistrar;

/** * JNLP application that uses the FileClassifier service
 */
public class FileClassifierApplication extends JFrame 
    implements net.jini.discovery.DiscoveryListener {
    private static final long WAITFOR = 50000L;
    ServiceItem item;
    TextField text;
    FileClassifier classifier;
    LookupCache cache;

    static public void main(String[] argv) {
	System.out.println("Found and starting");
	new FileClassifierApplication();
    }

    public FileClassifierApplication() {

	cacheFileClassifierService();

	Container contentPane = getContentPane();
	Panel top = new Panel();
	Panel bottom = new Panel();
	contentPane.add(top, BorderLayout.CENTER);
	contentPane.add(bottom, BorderLayout.SOUTH);
	
	top.setLayout(new BorderLayout());
	top.add(new Label("Filename"), BorderLayout.WEST);
	text = new TextField(20);
	top.add(text, BorderLayout.CENTER);

	bottom.setLayout(new FlowLayout());
	Button classify = new Button("Classify");
	Button quit = new Button("Quit");
	bottom.add(classify);
	bottom.add(quit);

	// listeners
	quit.addActionListener(new QuitListener());
	classify.addActionListener(new ClassifyListener());

	pack();
	setVisible(true);
    }

    protected void cacheFileClassifierService() {

       ServiceDiscoveryManager clientMgr = null;
        LookupLocatorDiscovery discovery = null;
	LookupLocator[] locators = null;

	// set the codebase before installing the security manager
	System.setProperty("java.rmi.server.codebase", 
			   System.getProperty("codebase"));
        System.setSecurityManager(new RMISecurityManager());

        try {
	    locators = getLocators();
	    discovery = new LookupLocatorDiscovery(locators);

            clientMgr = new ServiceDiscoveryManager(discovery, 
                                                new LeaseRenewalManager());
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
  
        Class [] classes = new Class[] {FileClassifier.class};
        ServiceTemplate template = new ServiceTemplate(null, classes, 
                                                       null);

        try {
            cache = clientMgr.createLookupCache(template, 
                                                null, /* no filter */ 
                                                null /* no listener */);
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * get the list of locators from properties in the JNLP file
     */
    protected LookupLocator[] getLocators() {
	Vector lookupNames = new Vector();
	String lookupStr = System.getProperty("LookupServices");
	if (lookupStr == null) {
	    return null;
	}

	// split this on whitespace
	StringTokenizer tokenizer = new StringTokenizer(lookupStr);
	while (tokenizer.hasMoreTokens()) {
	    String token = tokenizer.nextToken();
	    lookupNames.add(token);
	}
	LookupLocator[] locators = new LookupLocator[lookupNames.size()];
	for (int n = 0; n < locators.length; n++) {
	    LookupLocator loc = null;
	    try {
		loc = new LookupLocator((String) lookupNames.elementAt(n));
	    } catch(Exception e) {
		// empty - just add a null element
	    }
	    locators[n] = loc;
	}
	return locators;
    }

    class QuitListener implements ActionListener {
	public void actionPerformed(ActionEvent evt) {
	    System.exit(0);
	}
    }

    class ClassifyListener implements ActionListener {
	public void actionPerformed(ActionEvent evt) {
	    String fileName = text.getText();
	    Component frame = text.getParent();
	    while (! (frame instanceof Frame)) {
		frame = frame.getParent();
	    }
	    final Dialog dlg = new Dialog((Frame) frame);
	    dlg.setLayout(new BorderLayout());
	    TextArea response = new TextArea(3, 20);

	    ServiceItem item = null;
	    item = cache.lookup(null);
	    
	    if (item == null) {
		JOptionPane.showMessageDialog(null, "No service available");
		return;
	    }
	    FileClassifier classifier = (FileClassifier) item.service;
	    
	    MIMEType type = null;
	    try {
		type = classifier.getMIMEType(fileName);
		if (type == null) {
		    response.setText("The type of file " + fileName +
				     " is unknown");
		} else {
		    response.setText("The type of file " + fileName +
				     " is " + type.toString());
		}
	    } catch(RemoteException e) {
		response.setText(e.toString());
	    }

	    Button ok = new Button("ok");
	    ok.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    dlg.setVisible(false);
		}
	    });

	    dlg.add(response, BorderLayout.CENTER);
	    dlg.add(ok, BorderLayout.SOUTH);
	    dlg.setSize(300, 100);
	    dlg.setVisible(true);
	}
    }

    public void discovered(net.jini.discovery.DiscoveryEvent e) {
	System.out.println("discovered");
	 ServiceRegistrar[] registrars = e.getRegistrars();
	 for (int n = 0; n < registrars.length; n++) {
	     ServiceRegistrar reg = registrars[n];
	     try {
		 System.out.println("  discovered " +
				reg.getLocator().getHost());
	     } catch(java.rmi.RemoteException ex) {
	     }
	 }
    }

    public void discarded(net.jini.discovery.DiscoveryEvent e) {
	System.out.println("discarded");
	 ServiceRegistrar[] registrars = e.getRegistrars();
	 for (int n = 0; n < registrars.length; n++) {
	     ServiceRegistrar reg = registrars[n];
	     try {
		 System.out.println("  discarded " +
				reg.getLocator().getHost());
	     } catch(java.rmi.RemoteException ex) {
	     }
	 }
    }
} // FileClassifierApplication


If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone


This file is Copyright (©) 1999, 2000, 2001 by Jan Newmarch (http://jan.netcomp.edu.au) jan.newmarch@jan.newmarch.name.

This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v0.4 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.