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
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).
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.
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 |
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:
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
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.
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
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:
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>
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>
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