Chapter 0: Jini and Servlets

People are becoming increasingly accustomed to "finding things on the Web." The pages they view may be static or may be produced dynamically by a variety of technologies such as CGI scripts, Active Server Pages, Java servlets, Java Server Pages, etc. This chapter looks at how servlets can be written that use Jini services to generate content.

1. Dynamic Web pages

Along with email, the Web is the most widely used internet service used by most people. The explosive growth in the number of Web pages over the years has led people to just "look for it on the Web."

Initially all Web pages were static HTML documents, existing as files on an HTTP server. CGI (Common Gateway Interface) was introduced as a means of generating content dynamically. A CGI script is a program called by the HTTP server to handle a document request, and the program is expected to write a valid HTML (or other) document to its standard output which is returned to the user.

CGI scripts may be written in any language, but gain their name because the languages used are often scripting languages such as one of the Unix shells, Perl, Python, etc. However, any language can be used such as C, C++, Eiffel - just as long as a single program can be called which will process the request.

Java is not good for CGI scripts, because it requires calling a runtime engine such as jre with an argument that is the class to load so it can call the main() method. That is, starting a Java program requires calling the runtime with an additional argument, and CGI is not designed to deal with such situations. Rather clumsily, to use a Java class as CGI script requires writing a shell script or DOS batch file that calls the Java runtime with class name as argument.

CGI scripts have other drawbacks: each invocation of a CGI script runs as a separate process. This causes runtime overheads, as it may involve loading and starting a runtime interpreter such as the Java, Perl, tcl or shell interpreters. These are all large programs with substantial initialisation processing, and having to repeat this for each CGI call can waste large amounts of time.

Loadable modules are employed to reduce these overheads. These are loaded into an HTTP server and run as part of that server. This means that startup is only done once rather than on each invocation. In addition, the module can have access to the internals of the HTTP server and can use this to optimise calls even more. By running only once instance of the module, there are also other benefits such as easy sharing of state information across different calls to the module.

2. Servlets

Java servlets are just one method of embedding an interpreted language into an HTTP server. The language in this case is Java, and the particular API's exposed are now part of the Java standards. Why a servlet is of more interest than other systems is that it can allow us to access Jini services and use them within the servlet, which would be impossible say, for Perl modules.

A servlet typically extends HttpServlet. It will usually override the methods


public void init();
public void doGet(HttpServletRequest request,
                  HttpServletResponse response);
        throws IOException, ServletException
public void doPost(HttpServletRequest request,
                   HttpServletResponse response)
        throws IOException, ServletException;

The init() method is called when the servlet is first loaded, and can be used to perform once-off initialisation. The doGet() and doPost() methods are called from HTTP GET and POST requests, respectively. Usually you implement one of these in full, and just make the other one call the first.

The request and response parameters are used to get information from the HTTP request and to pass information back as an HTML document. In particular, output of the HTML reply document is done by getting a PrintWriter from response and writing to it:


PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>" + "Jini Service Finder" + "</title>");
out.println("</head>");
out.println("<body>");
// etc

3. Jakarta Tomcat Configuration

A Java servlet is called from a servlet engine such as the Jakarta Tomcat engine which is part of the Apache server project. This engine can be run as a module within an Apache HTTP server, or can be run as a standalone HTTP server which also interprets servlet calls.

The remainder of this section deals with the Jakarta Tomcat server in standalone mode, but much is applicable to running it as an Apache module. Some of this section may be relevant to other servlet engines. We shall discuss Jakarta Tomcat version 3.1 - due to problems with security managers, earlier versions of Tomcat will not work with Jini.

Tomcat must be run with a security manager. This is done under Unix by calling


tomcat.sh start -security
with a similar batch file for MSDOS. Unfortunately, this fails due to a bug in the shell script. In line 124 of the script occurs

  if [ "$1" = "-security" ] ; then
    echo Starting with a SecurityManager
    $JAVACMD $TOMCAT_OPTS -Djava.security.manager -Djava.security.policy==${TOMCAT_HOME}/conf/tomcat.policy -Dtomcat.home=${TOMCAT_HOME}  org.apache.tomcat.startup.Tomcat "$@"
The Java runtime then complains about an unrecognised option -security. A shift operation is missing to lose this option:

  if [ "$1" = "-security" ] ; then
    echo Starting with a SecurityManager
    shift    # lose the -security option
    $JAVACMD $TOMCAT_OPTS -Djava.security.manager -Djava.security.policy==${TOMCAT_HOME}/conf/tomcat.policy -Dtomcat.home=${TOMCAT_HOME}  org.apache.tomcat.startup.Tomcat "$@"

This runs Java with a security manager - and a security policy file. The policy file supplied with Tomcat will run Java servlets, but not Jini clients. The policy file needs to be extended with the extra permissions needed by Jini clients. You have the choice of either adding the extra security options to the tomcat.policy file, or pointing to another file with the correct permissions.

You may not have the necessary permissions to modify the script files. For example, if the system supervisor has installed Tomcat then you won't have write permission on these files. Certain other configuration files control things like the port Tomcat listens on, and you won't be able to modify those either. So you may need to negotiate changes with your sysadmin, or run a private copy of Tomcat.

Tomcat uses port 8080 by default. The HTTP server started by reggie also uses port 8080. If you are using Tomcat and the reggie lookup service on the same machine then one of these needs to be set to use a different port. To change Tomcat, you need to modify $TOMCAT_HOME/conf/server.xml.

A servlet that acts as a Jini client must know the Jini libraries. It is enough to put the Jini jar files in the CLASSPATH before Tomcat is run. Tomcat will add its own entries to the class path.

If your servlet client installs Jini event listeners in Jini services that it discovers, then the client must set the java.rmi.server.codebase property to include the listener's class files, so that the service can install the listener properly. Most clients will be aware that they are installing an event listener, but not always: if the ServiceDiscoveryManager is used, then it installs event listeners. The Sun implementation needs the class net/jini/lookup/ServiceDiscoveryManager$LookupCacheImpl$LookupListener_Stub.class to be accessible from an HTTP server.

Tomcat does not set the java.rmi.server.codebase property. The startup script needs to be modified again to add this to the Java runtime command.

4. Servlet as Jini client

Once appropriate security permissions have been set up, a Java servlet can act as a Jini client. The "brute force" way is to run everything from each of the HTTP request methods. So in doGet() we might look for lookup services, then query them for a desired service, fetch the service and then finally invoke the service. This works: it is just awfully slow and will often take several minutes (which seems like hours in internet time...).

The tricks to get workable clients are to minimise repeated work, and to run caching mechanisms in separate threads, so that information is available immediately when an HTTP request comes in. This means

  1. if a lookup service is at a known address, then don't search for it on every HTTP request, but find it once in the init() method. Then store it in a field available to every HTTP request or in a session object.
  2. if you need to monitor changes in services registered on a lookup service, then add a listener by registrar.notify() which will be called asynchronously when changes occur.
  3. if you need to have a particular service available immediately, then keep a cache by using a ServiceDiscoveryManager which will use its own thread to keep the cache up to date. (But then you have to remember to modify the startup script to set the java.rmi.server.codebase property.)

5. FileClassifierServlet

The file classifier can be turned into a simple Web servlet that illustrates some of the techniques to use servlets and to make them appear responsive rather than horribly slow. A Web interface to a file classifier will firstly display a form for entry of data such as


<form action="...">
File Name <input type="textfield" name="filename">
<input type="Submit">
</form>
When this form is submitted, the servlet will get the filename field and call the file classifier service for this filename. The result will be returned as an HTML document.

We will use a single servlet both for the initial form and for the reply. The initial form could have been done from a static HTML page, but making it into a servlet call means that we will invoke the method init() if this is the first time this servlet has been called. That is, the first time that a user shows interest in the service, we can start processing for this service.

The ServiceDiscoveryManager is of great use here. We can use it to start building a cache of services which runs in a separate thread. So init() can start the cache and return immediately.

The call to doGet() can determine if the filename property is set. If not, it returns the initial form entry page. If set, then it can query the cache for a service and use it. By this stage, there is a strong chance that the cache will have something in it. To get to this stage, the following activities will have occurred

  1. init() will have started cache building in a separate thread
  2. doGet() will have returned a form to the user
  3. The user will have spent time filling in the form and submitting it
  4. doGet() will be able to use a non-empty cache of services without delay
In the majority of cases, this will give fast response.

There are of course a variety of complexities that can be added to this

  1. Initial page delivery can be done by a different servlet, in which case the two servlets will need to communicate the cache between them
  2. Filling the cache may take longer than the user submitting the form. The following program just says "Try again", but you could add delays until the cache has an entry
  3. Someone who calls the submission page directly, without going through the initial form will probably beat cache filling if this is the first time this servlet has been called. This is highly unlikely, but possible. The servlet would then fail to find a service until called again
These add to complexity, but not to basic concepts.

A servlet based on the above discussion is


/**
 * FileClassifierServlet.java
 */

package servlet;

import common.FileClassifier;
import common.MIMEType;

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.core.lookup.ServiceItem;
import net.jini.lease.LeaseRenewalManager;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceID;
import net.jini.lookup.LookupCache;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class FileClassifierServlet extends HttpServlet {

    private LookupCache cache;

    public void init() {
        ServiceDiscoveryManager clientMgr = null;

        System.setSecurityManager(new RMISecurityManager());

        try {
            LookupDiscoveryManager mgr =
                new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
                                           null /* unicast locators */,
                                           null /* DiscoveryListener */);
            clientMgr = new ServiceDiscoveryManager(mgr, 
                                                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);
        }
    }

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
	throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        String docType =
	    "<!DOCTYPE HTML PUBLIC \"-//W3C/DTD HTML 4.0 " +
	    "Transitional//EN\"\n";
        out.println(docType +
                    "<HTML>\n" +
                    "<head><title>File Classifier</title></head>\n" +
                    "<body>\n" +
                    "<h1> File Classifier </h1>");

	String fileName = request.getParameter("filename");
	if (fileName == null || fileName.length() == 0) {
	    showFirstPage(out);
	} else {
	    handleForm(fileName, out);
	}
	out.println("</BODY> </HTML>");
    }

    public void showFirstPage(PrintWriter out) {
	out.println("<form action=\"http://localhost:8088/jini/servlet/servlet.FileClassifierServlet\">\n" +
		    "File Name <input type=\"textfield\" name=\"filename\">\n" +
		    "<br>\n" +
		    "<input type=\"Submit\">\n" +
		    "</form>\n");                    
    }

    public void handleForm(String fileName, PrintWriter out) {

	out.println("<h1>Filename: " + fileName + "</h1>\n");
 
        ServiceItem item = null;
	item = cache.lookup(null);
	
	if (item == null) {
	    out.println("<P>No service- try again later</P>");
	    return;
	}

	FileClassifier classifier = (FileClassifier) item.service;
	// out.println(" Found classifier. ");
	MIMEType type = null;
	try {
	    type = classifier.getMIMEType(fileName);
	} catch(Exception e) {
	    out.println("<pre>" + e.toString() + "</pre>");
	    return;
	}
	// out.println(" Found type. " + type);

	if (type == null) {
	    out.println("<P>Unknown file type</P>");
	} else {
	    out.println("<P>Type is: " + type.toString() + "</P>");
	}
    }        
} // FileClassifierServlet

6. Deploying the Servlet

The servlet class FileClassifierServlet needs to be located where the servlet engine can locate it. There is now a standard naming convention for locating servlet classes under a complex directory structure. If the servlet is meant to be accessed from, say, the url http://www.jini.monash.edu.au:8088/jini/servlet/servlet.FileClassifierServlet where the servlet engine is Tomcat, then the class file needs to be copied to


$TOMCAT_HOME/webapps/jini/WEB-INF/classes/servlet/servlet/FileClassifierServlet.class
(There is an unfortunate repitition of the servlet part here. The first occurrence distinguishes between servlets and jsp's. The second occurrence is part of the package name, and reflects the package naming structure we have used in this book.)


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.