Chapter 21: ServiceStarter

A service is created by a server and registered with lookup services. The server has a fairly standard format, usually varying only in small details: the actual service, its entry attributes, number of services, etc. The ServiceStarter class can help with some of this, and is used by Sun for its tools such as reggie

21.1. ServiceDescriptor

In the chapter on configuration we looked at how a "meta-server" might be written that would get information from a configuration file describing a service and use that to build the service. In order to make a service available for use, a number of parameters must be set up. These include

  1. The service class and how to construct it

  2. The transport protocol (Jeri/JRMP)

  3. The proxy for the service

  4. The codebase for the proxy files

  5. The classpath for the local files

  6. A security policy to run the server for this service

  7. Entry/attribute information

  8. Unicast locators of lookup services

  9. Group to join on lookup services

  10. Service item ID

Some of these belong to the service, some to its proxy, some to the containing server, while others are advertisement parameters for joining lookup services.

Jini has an interface ServiceDescriptor to give a standard way of handling some of these. This class is in the com.sun.jini.start package. This package is not specified by Jini, and may change or even disappear in later versions of Jini.

 
interface ServiceDescriptor {
	Object create(Configuration config); } 
      

This has a number of implementations

  1. NonActivatableServiceDescriptor

  2. SharedActivatableServiceDescriptor

  3. SharedActivationGroupDescriptor

The first is useful for the most common situation described in this book, of a non-activatable service. The meat of the NonActivatableServiceDescriptor class is in its constructors:

 
class NonActivatableServiceDescriptor {
    NonActivatableServiceDescriptor(String codebase, 
                                    String policy, 
                                    String classpath, 
                                    String implClassName, 
                                    String[] serverConfigArgs); 
    
    NonActivatableServiceDescriptor(String codebase, 
                                    String policy, 
                                    String classpath, 
                                    String implClassName, 
                                    String[] serverConfigArgs, 
                                    LifeCycle lifeCycle); 
} 
      
The codebase is a url(s) of a directory or jar file of the proxy classes on an HTTP server; policy is the filename of the policy for this service within the context of the policy existing for the server; classpath is the classpath for the service run by the server; serverConfigArgs is an array of configuration parameters (typically just a single file name); it is not yet clear what lifeCycle is, or how it is used.

It is notable what the constructor does, and does not, describe. It describes the service and how to run it. It describes the environment for the proxy, but not how to create it. However, one of the parameters to the create() method is a configuration, and this configuration can be used in ways we have seen before to specify the transport protocol and create the service proxy. The constructor does not describe the entry information, the service id or the groups to which this service belongs. The parameters described by NonActivatableServiceDescriptor describe the service's runtime/deployment environment, while the missing parameters describe the service's advertisement environment.

The NonActivatableServiceDescriptor class provides an implementation of the create() method. This is defined in the interface to return an Object but the class actually returns a com.sun.jini.start.NonActivatableServiceDescriptor.Created. This has no methods, just two public fields


public class Created { 
    public Object impl; 
    public Object proxy; 
} 
      
From this one can extract the implementation and its proxy.

There are further wrinkles in using NonActivatableServiceDescriptor. The implementation object must be constructed from its class. The constructor has two parameters: the string array serverConfigArgs, and a lifecycle object. At present it is not clear what role this object is expected to play, and it is sufficient to use a default value NoOpLifeCycle.

In addition to creating the implementation object, the create() method must create a proxy object for the implementation in order to return a Created object. This is done by requiring the implementation to support one of the two interfaces ServiceProxyAccessor or ProxyAccessor and calling getServiceProxy() or getProxy() respectively on the implementation. That is, the service must include the method getServiceProxy() (or getProxy()) and within this method it will probably create the proxy (possibly using the configuration information). object and return it.

21.2. Starting a nonactivatable service

The implementation must be Remote and must be able to create a proxy. In its constructor it is handed a configuration array, so this may as well be used to find an exporter to get the proxy. The class starter.FileClassifierStarterImpl inherits from rmi.FileClassifierImpl which adds Remote to the basic file classifier:


	package starter;

import rmi.FileClassifierImpl;

import com.sun.jini.start.ServiceProxyAccessor;
import com.sun.jini.start.LifeCycle;

import net.jini.config.*; 
import net.jini.export.*; 
import java.rmi.Remote;
import java.rmi.RemoteException;

public class FileClassifierStarterImpl extends FileClassifierImpl
    implements ServiceProxyAccessor {
    String[] configArgs;
    Remote proxy;

    public FileClassifierStarterImpl(String[] configArgs, LifeCycle lifeCycle)
	throws RemoteException {
	super();
	this.configArgs = configArgs;
    }

    public Object getServiceProxy() {
	if (configArgs.length == 0) {
	    return null;
	}
	try {
	    // get the configuration (by default a FileConfiguration) 
	    Configuration config = ConfigurationProvider.getInstance(configArgs); 
	    
	    // and use this to construct an exporter
	    Exporter exporter = (Exporter) config.getEntry( "FileClassifierServer", 
							    "exporter", 
							    Exporter.class); 
	    // export an object of this class
	    proxy = exporter.export(this);
	} catch(Exception e) {
	    return null;
	}
	return proxy;
    }
}

      

A configuration file suitable for using Jeri with the above FileClassifierStarterImpl is resources/starter/file_classifier.config:


	import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;

FileClassifierServer {
    exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                     new BasicILFactory()); 
}

      

The server to start this service needs to set various parameters for the ServiceDescriptor. These can be set in another configuration file such as resources/starter/serviceDesc.config:


	import net.jini.core.discovery.LookupLocator;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.entry.Entry;
import java.io.File;

ServiceDescription {
    codebase =    
	"http://192.168.1.13:8080/classes/starter.ServiceDescription-dl.jar";
    policy = "policy.all";
    classpath = "starter.ServiceDescription-start.jar";
    implClass = "starter.FileClassifierStarterImpl";
    serverConfigArgs = new String[] {
          "/home/httpd/html/java/jini/tutorial/resources/starter/file_classifier.config"
       };
}

AdvertDescription {
    entries = new Entry[] {};
    groups = LookupDiscovery.ALL_GROUPS;
    unicastLocators = new LookupLocator[] { // empty
                                          };    
    serviceIdFile = new File("serviceId.id"); 
}

      
This configuration file contains two sets of configurations: one for the ServiceDescription component and one for the AdvertDescription component (discussed shortly).

The resources/starter/serviceDesc.config configuration file uses two jar files, starter.ServiceDescription-dl.jarfor the service codebase and starter.ServiceDescription-start.jar for the server's classpath. The contents of these files are

  1. For starter.ServiceDescription-dl.jar

    
    rmi/FileClassifierImpl.class
    rmi/RemoteFileClassifier.class
    	    

  2. For starter.ServiceDescription-start.jar

    
    common/FileClassifier.class
    common/MIMEType.class
    rmi/FileClassifierImpl.class
    rmi/RemoteFileClassifier.class
    starter/FileClassifierStarterImpl.class
    starter/ServiceDescription.class
    	    

A server which picks up the values from this configuration file and creates the service and its proxy follows. The program essentially uses two parts: one to build the service using a ServiceDescriptor and its configuration entries, the other to advertise the service using JoinManager and its associated AdvertDescription configuration entries. (Although JoinManager has a constructor which will take a configuration, this does not support any of the entries we specified above.)


	package starter;

import java.rmi.RMISecurityManager;

import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;

import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor.Created;

import net.jini.lookup.JoinManager;
import net.jini.core.lookup.ServiceID;
import net.jini.lookup.ServiceIDListener;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.lease.LeaseRenewalManager;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.discovery.LookupDiscovery;

import java.rmi.Remote;

import java.io.*;

/**
 * ServiceDescription.java
 */

public class ServiceDescription implements ServiceIDListener {
    
    private Object impl;
    private Remote proxy;
    private File serviceIdFile;
    private Configuration config;
    private ServiceID serviceID;

    public static void main(String args[]) {
	if (System.getSecurityManager() == null) {
	    System.setSecurityManager(new RMISecurityManager());
	}

	ServiceDescription s = 
	    new ServiceDescription(args);
	
        // keep server running forever to 
	// - allow time for locator discovery and
	// - keep re-registering the lease
	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

    public ServiceDescription(String[] args) {
        if (args.length == 0) {
            System.err.println("No configuration specified");
            System.exit(1);
        }

	try {
	    config = ConfigurationProvider.getInstance(args); 
	} catch(ConfigurationException e) {
	    System.err.println("Configuration error: " + e.toString() +
			       " in file " + args[0]);
	    System.exit(1);
	}
	startService();
	advertiseService();
    }

    private void startService() {
	String codebase = null;
	String policy = null;
	String classpath = null;
	String implClass = null;
	String[] serverConfigArgs = null;

	try {

	    codebase = (String) config.getEntry("ServiceDescription", 
						"codebase", 
						String.class); 
	    policy = (String) config.getEntry("ServiceDescription", 
					      "policy", 
					      String.class); 
	    classpath = (String) config.getEntry("ServiceDescription", 
						 "classpath", 
						 String.class); 
	    implClass = (String) config.getEntry("ServiceDescription", 
						 "implClass", 
						 String.class); 
	    serverConfigArgs = (String[]) config.getEntry("ServiceDescription", 
							  "serverConfigArgs", 
							  String[].class); 
	} catch(ConfigurationException e) {
	    System.err.println("Configuration error: " + e.toString());
	    System.exit(1);
	}

	// Create the new service descriptor
	ServiceDescriptor desc = 
	    new NonActivatableServiceDescriptor(codebase,
						policy,
						classpath,
						implClass,
						serverConfigArgs);

	// and create the service and its proxy
	Created created = null;
	try {
	    created = (Created) desc.create(config);
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
	impl = created.impl;
	proxy = (Remote) created.proxy;
    }

    private void advertiseService() {
	Entry[] entries = null;
	LookupLocator[] unicastLocators = null;
	File serviceIdFile = null;
	String[] groups = null;

	// Now go on to register the proxy with lookup services, using
	// e.g. JoinManager.
	// This will need additional parameters: entries, unicast
	// locators, group and service ID
        try {
            unicastLocators = (LookupLocator[]) 
                config.getEntry("AdvertDescription", 
                                "unicastLocators", 
                                LookupLocator[].class,
                                null); // default
            
            entries = (Entry[]) 
                config.getEntry("AdvertDescription", 
                                "entries", 
                                Entry[].class,
                                null); // default
            groups = (String[]) 
                config.getEntry("AdvertDescription", 
                                "groups", 
                                String[].class,
                                LookupDiscovery.ALL_GROUPS); // default
            serviceIdFile = (File) 
                config.getEntry("AdvertDescription", 
                                "serviceIdFile", 
                                File.class,
                                null); // default 
        } catch(Exception e) {
            System.err.println(e.toString());
            e.printStackTrace();
            System.exit(2);
        }
        JoinManager joinMgr = null;
        try {
            LookupDiscoveryManager mgr = 
                new LookupDiscoveryManager(groups,
                                           unicastLocators,  // unicast locators
                                           null); // DiscoveryListener
            if (serviceID != null) {
                joinMgr = new JoinManager(proxy, // service proxy
                                          entries,  // attr sets
                                          serviceID,  // ServiceID
                                          mgr,   // DiscoveryManager
                                          new LeaseRenewalManager());
            } else {
                joinMgr = new JoinManager(proxy, // service proxy
                                          entries,  // attr sets
                                          this,  // ServiceIDListener
                                          mgr,   // DiscoveryManager
                                          new LeaseRenewalManager());
            }
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }



    public void tryRetrieveServiceId() {
        // Try to load the service ID from file.
        // It isn't an error if we can't load it, because
        // maybe this is the first time this service has run
        DataInputStream din = null;
        try {
            din = new DataInputStream(new FileInputStream(serviceIdFile));
            serviceID = new ServiceID(din);
            System.out.println("Found service ID in file " + serviceIdFile);
            din.close();
        } catch(Exception e) {
            // ignore
        }
    }

    public void serviceIDNotify(ServiceID serviceID) {
        // called as a ServiceIDListener
        // Should save the id to permanent storage
        System.out.println("got service ID " + serviceID.toString());
        
        // try to save the service ID in a file
        if (serviceIdFile != null) {
            DataOutputStream dout = null;
            try {
                dout = new DataOutputStream(new FileOutputStream(serviceIdFile));
                serviceID.writeBytes(dout);
                dout.flush();
            dout.close();
            System.out.println("Service id saved in " +  serviceIdFile);
            } catch(Exception e) {
                // ignore
            } 
        }
    }

} // ServiceDescription

      

This server may be run from a command line such as

 
java starter.ServiceDescription resources/starter/serviceDesc.config 
      
using a classpath that includes starter.ServiceDescription

To summarise what is going on here

  1. The service is started by running a service.ServiceDescription

  2. The classpath for service.ServiceDescription must include (for example) a jar file starter.ServiceDescription.jar that contains starter.ServiceDescription.class as well as the standard Jini classes

  3. service.ServiceDescription uses a configuration file such as serviceDesc.config which includes a description of the codebase, etc, which are suitable parameters for the constructor of a ServiceDescriptor

  4. The serviceDesc.config configuration also contains an advertisement description to register the service with lookup services

  5. When the service is started by ServiceDescriptor.create() it uses its own configuration file file_classifier.config which specifies the exporter

  6. The classpath used to start the service includes the files in the jar file starter.ServiceDescription-start.jar

  7. The codebase used by clients to download the service includes the jar file starter.ServiceDescription-dl.jar

The ant file to build and run this is antBuildFiles/starter.ServiceDescription.xml


	
      

21.3. Starting a nonactivatable server

The standard tools supplied by Sun such as reggie all use the ServiceDescription class. But instead of starting a service, they start a server. So in this case an application is needed to start the server, which in turn will start the service.

Sun supply a class ServiceStarter in the com.sun.jini.start package. This package is not specified by Jini, and may change or even disappear in later versions of Jini. It has a public main() method, which will take command line arguments. A configuration can be given as a command line argument and will be searched for an array of ServiceDescriptor's. The search is in component com.sun.jini.start and the descriptors are labelled as serviceDescriptors. The create() method will be called on each descriptor to create a server.

To use the ServiceStarter, you need to write a server and also a configuration file containing a ServiceDescriptor for that server. The server does not need to have a main() method, since the server is started by ServiceStarter, not the server itself.

For example, we could use the FileClasifierServerConfig from the chapter on "Configuration". The configuration file for this server remains unaltered as


	import java.io.*;

ServiceIdDemo {
    serviceIdFile = new File("serviceId.id");
}

      
The server itself need not be altered - its main() method is just not called. If it were being written from scratch, there would be no need to include this method. Repeating this from the "Configuration" chapter,

	cant open config/FileClassifierServerConfig.java

      

What is new is a configuration file for ServiceStarter. This could be in resources/starter/start-transient-fileclassifier.config:


	import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;

ServiceIdDemo {
    private static codebase =    
        "http://192.168.1.13:8080/file-classifier-dl.jar";
    private static policy = "policy.all";
    private static classpath = "file-classifier.jar";
    private static config = "resources/starter/file_classifier.config";

    static serviceDescriptors = new ServiceDescriptor[] {
                new NonActivatableServiceDescriptor(
                        codebase, policy, classpath,
                        "config.FileClassifierServerConfig",
                         new String[] { config })
    };
}


      

The server is set running by


	java -jar start.jar ServiceStarter resources/start-transient-fileclassifier.config
      

A typical descriptor for the reggie service might be


String codebase = "http://192.168.1.13:8080/reggie-dl.jar";
String policy = "/usr/local/reggie/reggie.policy";
String classpath = "/usr/local/jini2_0/lib/reggie.jar";
String config = "/usr/local/reggie/transient-reggie.config";

ServiceDescriptor desc = 
                new NonActivatableServiceDescriptor(
                        codebase, policy, classpath,
                        "com.sun.jini.reggie.TransientRegistrarImpl",
                         new String[] {config})
      

21.4. Reggie

We can now see what is going in when a standard Sun service such as reggie is started. A typical command line is


java -Djava.security.policy=reggie/start.policy -jar \
      start.jar start-transient-reggie.config
The configuration file contains information required to start the reggie server, such as

	import com.sun.jini.start.ServiceDescriptor;
import com.sun.jini.start.NonActivatableServiceDescriptor;
import com.sun.jini.config.ConfigUtil;

com.sun.jini.start {
    private static codebase = 
			ConfigUtil.concat(new Object[] {
						"http://",
						 ConfigUtil.getHostName(),
						":8080/reggie-dl.jar"
					      }
					);
    private static policy = "/usr/local/reggie/reggie.policy";
    private static classpath = "/usr/local/jini2_0/lib/reggie.jar";
    private static config = "/usr/local/reggie/transient-reggie.config";

    static serviceDescriptors = new ServiceDescriptor[] {
                new NonActivatableServiceDescriptor(
                        codebase, policy, classpath,
                        "com.sun.jini.reggie.TransientRegistrarImpl",
                         new String[] { config })
    };
}


      
This particular configuration starts a TransientRegistrarImpl

When the TransientRegistrarImpl begins, it uses a configuration file too. This was specified to be /usr/local/reggie/transient-reggie.config and contains


	com.sun.jini.reggie {
    initialMemberGroups = new String[] {};
}


      

21.5. Conclusion

A ServiceDescriptor allows a service to be created with given parameters. It can be used directly, or by using the Sun-supplied ServiceStarter. Sun use this to startup their own services such as reggie. These classes are in the com.sun.jini.start package, and as such are not guaranteed to be stable or even to exist in future versions of Jini. Alternatively, like JoinManager, they might migrate to the core Jini package in future.

We have seen that using ServiceStarter for a server essentially involves no changes to the server or its service. What is the point of it, then? Typically, starting a service involves a combination of factors from a number of sources

  1. Some values may come from the environment, such as CLASSPATH

  2. Some values may be defined in Java properties, such as security policy

  3. Some values may be given as command line arguments

The ServiceStarter allows all of these to be placed in a configuration, so that a single mechanism can be used.

21.6. Copyright

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 . The current edition of the book does not yet deal with Jini 2.0, but the next edition will.

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

Creative Commons License This work is licensed under a Creative Commons License, the replacement for the earlier Open Content License.