56.

previous chapter table of contents next chapter
  

A Service Using Activation

The major concepts in Activation are the activatable object itself (which extends java.rmi.activation.Activatable ) and the environment in which it runs, an ActivationGroup .

A JVM may have an activation group associated with it. If an object needs to be activated and there is already a JVM running its group, then it is restarted within that JVM. Otherwise, a new JVM is started. An activation group may hold a number of cooperating objects.

The next sections show how to create a service as an activatable object that starts life in a server that sets up the activation group. Issues related to activation, such as security and state maintenance, will also be discussed.

The Service

An activable object subclasses from Activatable and uses a special two-argument constructor that will be called when the object needs to be reconstructed. There is a standard implementation of this constructor that just calls the superclass constructor:

 public ActivatableImpl(ActivationID id, MarshalledObject data)     throws RemoteException {     super(id, 0); } 

(The use of the marshalled object parameter is discussed later in the "Maintaining State" section). Adding this constructor is all that is normally needed to change a remote service (that implements UnicastRemoteObject ) into an activatable service. For example, an activatable version of the remote file classifier described in RMI Proxy for FileClassifier" section is as follows :

 package activation; import java.rmi.activation.Activatable; import java.rmi.activation.ActivationID; import java.rmi.MarshalledObject; import common.MIMEType; import common.FileClassifier; import rmi.RemoteFileClassifier; /**  * FileClassifierImpl.java  */ public class FileClassifierImpl extends Activatable                                 implements RemoteFileClassifier {     public MIMEType getMIMEType(String fileName)         throws java.rmi.RemoteException {         if (fileName.endsWith(".gif")) {             return new MIMEType("image", "gif");         } else if (fileName.endsWith(".jpeg")) {             return new MIMEType("image", "jpeg");         } else if (fileName.endsWith(".mpg")) {             return new MIMEType("video", "mpeg");         } else if (fileName.endsWith(".txt")) {             return new MIMEType("text", "plain");         } else if (fileName.endsWith(".html")) {             return new MIMEType("text", "html");         } else             // fill in lots of other types,             // but eventually give up and             return new MIMEType(null, null);     }     public FileClassifierImpl(ActivationID id, MarshalledObject data)         throws java.rmi.RemoteException {         super(id, 0);     } } // FileClassifierImpl 

Note that an activatable object cannot have a default no-args constructor to initialize itself, since this new constructor is required for the object to be constructed by the activation system.

The Server

The server needs to create an activation group for the objects to run in. The main issue involved here is to set a security policy file. There are two security policies in activatable objects: the policy used to create the server and export the service, and the policy used to run the service. The activation group sets a policy file for running methods of the service object. The policy file for the server is set using the normal -Djava.security.policy=... argument to start the server. After setting various parameters, the activation group is set for the JVM by ActivationGroup.createGroup() .

Remote objects that subclass UnicastRemoteObject are created in the normal way using a constructor on the server. Activatable objects are not constructed in the server but are instead registered with rmid , which will look after construction on an as-needed basis.

In order to create activatable objects, rmid needs to know the class name and the location of the class files. The server wraps these up in an ActivationDesc , and registers this with rmid by using Activatable.register() . This returns an RMI stub object that can be registered with lookup services using the ServiceRegistrar.register() methods. This is also a little different from subclasses of UnicastRemoteObject , which pass an object that is converted to a stub by the RMI runtime. The required actions, in point form, are as follows:

  • A service creates a subclass of UnicastRemoteObject using its constructor.
  • A subclass of Activatable is created by rmid using a special constructor.
  • For a UnicastRemoteObject object, the server needs to know the class files for the class in its CLASSPATH and the client needs to know the class files for the stub from an HTTP server.
  • For an Activatable object, rmid needs to know the class files from an HTTP server, the server must be able to find the stub files from its CLASSPATH , and the client must be able to get the stub files from an HTTP server.
  • A server hands a UnicastRemoteObject object to the ServiceRegistrar.register() . This is converted to the stub by the RMI runtime.
  • A server gets a stub for an Activatable object from Activatable.register() . This stub is given directly to ServiceRegistrar.register() .

Changes need to be made to servers that export activatable objects instead of unicast remote objects. The server in RMI Proxy for FileClassifier" section, creates a unicast remote object and exports its RMI proxy to lookup services by passing the remote object to the ServiceRegistrar.register() method. The changes for such servers to export activatable objects are as follows:

  • An activation group has to be created with a security policy file.
  • The service is not created explicitly but is instead registered with rmid .
  • The return object from the registration is a stub that can be registered with lookup services.
  • Leasing vanishes ”the server just exits. The service will just expire after a while. See the "LeaseRenewalService" section later in the chapter for more details on how to keep the service alive .

The file classifier server using an activatable service would look like this:

 package activation; import rmi.RemoteFileClassifier; import net.jini.discovery.LookupDiscovery; import net.jini.discovery.DiscoveryListener; import net.jini.discovery.DiscoveryEvent; import net.jini.core.lookup.ServiceRegistrar; import net.jini.core.lookup.ServiceItem; import net.jini.core.lookup.ServiceRegistration; import net.jini.core.lease.Lease; import java.rmi.RMISecurityManager; import java.rmi.MarshalledObject; import java.rmi.activation.ActivationDesc; import java.rmi.activation.ActivationGroupDesc; import java.rmi.activation.ActivationGroupDesc.CommandEnvironment; import java.rmi.activation.Activatable; import java.rmi.activation.ActivationGroup; import java.rmi.activation.ActivationGroupID; import java.util.Properties; import java.rmi.activation.UnknownGroupException; import java.rmi.activation.ActivationException; import java.rmi.RemoteException; /**  * FileClassifierServer.java  */ public class FileClassifierServer implements DiscoveryListener {     static final protected String SECURITY_POLICY_FILE =         "/home/jan/projects/jini/doc/policy.all";     // Don't forget the trailing '/'!     static final protected String CODEBASE = "http://localhost/classes/";     // protected FileClassifierImpl impl;     protected RemoteFileClassifier stub;     public static void main(String argv[]) {         new FileClassifierServer(argv);         // stick around while lookup services are found         try {             Thread.sleep(10000L);         } catch(InterruptedException e) {             // do nothing         }         // the server doesn't need to exist anymore         System.exit(0);     }     public FileClassifierServer(String[] argv) {         // install suitable security manager         System.setSecurityManager(new RMISecurityManager());         // Install an activation group         Properties props = new Properties();         props.put("java.security.policy",                 SECURITY_POLICY_FILE);         ActivationGroupDesc.CommandEnvironment ace = null;         ActivationGroupDesc group = new ActivationGroupDesc(props, ace);         ActivationGroupID groupID = null;         try {             groupID = ActivationGroup.getSystem().registerGroup(group);         } catch(RemoteException e) {             e.printStackTrace();             System.exit(1);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         }         try {             ActivationGroup.createGroup(groupID, group, 0);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         }         String codebase = CODEBASE;         MarshalledObject data = null;         ActivationDesc desc = null;         try {             desc = new ActivationDesc("activation.FileClassifierImpl",                                                  codebase, data);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         }         try {             stub = (RemoteFileClassifier) Activatable.register(desc);         } catch(UnknownGroupException e) {             e.printStackTrace();             System.exit(1);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         } catch(RemoteException e) {             e.printStackTrace();             System.exit(1);         }         LookupDiscovery discover = null;         try {             discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);         } catch(Exception e) {             System.err.println(e.toString());             System.exit(1);         }         discover.addDiscoveryListener(this);     }     public void discovered(DiscoveryEvent evt) {         ServiceRegistrar[] registrars = evt.getRegistrars();         RemoteFileClassifier service;         for (int n = 0; n < registrars.length; n++) {             ServiceRegistrar registrar = registrars[n];             // export the proxy service             ServiceItem item = new ServiceItem(null,                                                stub,                                                null);             ServiceRegistration reg = null;             try {                 reg = registrar.register(item, Lease.FOREVER);             } catch(java.rmi.RemoteException e) {                 System.err.print("Register exception: ");                 e.printStackTrace();                 // System.exit(2);                 continue;             }             try {                 System.out.println("service registered at " +                                    registrar.getLocator().getHost());             } catch(Exception e) {             }         }     }     public void discarded(DiscoveryEvent evt) {     } } // FileClassifierServer 

Running the Service

The service backend and the server must be compiled as usual, and in addition, an RMI stub object must be created for the service backend using the rmic compiler (in JDK 1.2, at least). The class files for the stub must be copied to somewhere where an HTTP server can deliver them to clients . This is the same as for any other RMI stubs.

There is an extra step that must be performed for Activatable objects: the activation server rmid must be able to reconstruct a copy of the service backend (the client must be able to reconstruct a copy of the service's stub). This means that rmid must have access to the class files of the service backend, either from an HTTP server or from the file system. In the previous server, the codebase property in the ActivationDesc is set to an HTTP URL, so the class files for the service backend must be accessible to an HTTP server. Note that rmid does not get the class files for a service backend from the CLASSPATH , but from the codebase of the service. The HTTP server need not be on the same machine as the service backend.

Before starting the service provider, an rmid process must be set running on the same machine as the service provider. An HTTP server must be running on a machine specified by the codebase property on the service. The service provider can then be started. This will register the service with rmid and will copy a stub object to any lookup services that are found. The server can then terminate. (As mentioned earlier, this will cause the service's lease to expire, but techniques to handle this are described later).

In summary, there are typically three processes involved in getting an activatable service running:

  • The service provider, which specifies the location of class files in its codebase.
  • rmid , which must be running on the same machine as the service provider and must be started before the service provider. It gets class files using the codebase of the service.
  • An HTTP server, which can be on a different machine and is pointed to by the codebase.

While the service remains registered with lookup services, clients can download its RMI stub. The service will be created on demand by rmid . You only need to run the server once, since rmid keeps information about the service in its own log files.

Security

The JVM for the service will be created by rmid and will be running in the same environment as rmid . Such things as the current directory for the service will be the same as for rmid , not from where the server ran. Similarly, the user ID for the service will be the user ID of rmid . This is a potential security problem in multi-user systems. For example, any user on a Unix system could write a service that attempts to read the shadow password file on the system, as an activatable service. Once registered with rmid , this same user could write a client that calls the appropriate methods on the service. If rmid is running in privileged mode, owned by the super-user of the system, then the service will run in that same mode and will happily read any file in the entire file system! For safety, rmid should probably be run using the user ID nobody , much like the recommendations for HTTP servers.

Some of the security issues with rmid have been addressed in JDK 1.3. These were discussed in Chapter 12, and they allow a security policy to be associated with each activatable service.

Non-Lazy Services

The types of services discussed in this chapter so far are "lazy" services, activated on demand when their methods are called. This reduces memory use at the expense of starting up a new JVM when required. Some services need to be continuously alive but can still benefit from the logging mechanism of rmid . If rmid crashes and is restarted, or the machine is rebooted and rmid restarts, then the server is able to use its log files to restart any "active" services registered with it, as well as to restore "lazy" services on demand. By making services non-lazy and ensuring that rmid is started on reboot, you can avoid messing around with boot configuration files.

Maintaining State

An activatable object is created afresh each time a method is called on it, using its two-argument constructor. The default action, calling super(id, O) will result in the object being created in the same state on each activation. However, method calls on objects (apart from get...() methods) usually result in a change of state of the object. Activatable objects will need some way of reflecting this change on each activation, and saving and restoring state using a disk file typically does this.

When an object is activated, one of the parameters passed to it is a MarshalledObject instance. This is the same object that was passed to the activation system in the ActivationDesc parameter to Activation.register() . This object does not change between different activations, so it cannot hold changing state, but only data, which is fixed for all activations. A simple use for it is to hold the name of a file that can be used for state. Then, on each activation the object can restore state by reading stored information. On each subsequent method call that changes state, the information in the file can be overwritten.

The mutable file classifier example was discussed in Chapter 14 ”it could be sent addType() and removeType() messages. It begins with a given set of MIME type/ file extension mappings. State here is very simple; it is just a matter of storing all the file extensions and their corresponding MIME types in a Map . If we turn this into an activatable object, we store the state by just storing the map. This map can be saved to disk using ObjectOutputStream.writeObject() , and it can be retrieved by ObjectInputStream.readObject() . More complex cases might need more complex storage methods.

The very first time a mutable file classifier starts on a particular host, it should build its initial state file. There are a variety of methods that could be used. For example, if the state file does not exist, then the first activation could detect this and construct the initial state at that time. Alternatively, a method such as init() could be defined, to be called once after the object has been registered with the activation system.

The "normal" way of instantiating an object ”through a constructor ”doesn't work very well with activatable objects. If a constructor for a class doesn't start by calling another constructor with this(...) or super(...) , then the no-argument superclass constructor super() is called. However, the class Activatable doesn't have a no-args constructor, so you can't subclass from Activatable and have a constructor such as FileClassifierMutable(String stateFile) that doesn't use the activation system.

You can avoid this problem by not inheriting from Activatable and registering explicitly with the activation system, like this:

 public FileClassifierMutable(ActivationID id, MarshalledObject data)         throws java.rmi.RemoteException {         Activatable.exportObject(this, id, 0);         // continue with instantiation 

Nevertheless, this is a bit clumsy: you create an object solely to build up initial state, and then discard it because the activation system will recreate it on demand.

The technique we'll use here is to create initial state if the attempt to restore state from the state file fails for any reason when the object is activated. This is done in the restoreMap() method called from the constructor FileClassifierMutable(ActivationID id , MarshalledObject data) . The name of the file is extracted from the marshalled object passed in as parameter.

 package activation; import java.io.*; import java.rmi.activation.Activatable; import java.rmi.activation.ActivationID; import java.rmi.MarshalledObject; import net.jini.core.event.RemoteEventListener; import net.jini.core.event.RemoteEvent; import net.jini.core.event.EventRegistration; import java.rmi.RemoteException; import net.jini.core.event.UnknownEventException ; import javax.swing.event.EventListenerList; import common.MIMEType; import common.MutableFileClassifier; import mutable.RemoteFileClassifier; import java.util.Map; import java.util.HashMap; /**  * FileClassifierMutable.java  */ public class FileClassifierMutable extends Activatable                                 implements RemoteFileClassifier {     /**      * Map of String extensions to MIME types      */     protected Map map = new HashMap();     /**      * Permanent storage for the map while inactive      */     protected String mapFile;     /**      * Listeners for change events      */     protected EventListenerList listenerList = new EventListenerList();     public MIMEType getMIMEType(String fileName)         throws java.rmi.RemoteException {         System.out.println("Called with " + fileName);         MIMEType type;         String fileExtension;         int dotIndex = fileName.lastIndexOf('.');         if (dotIndex == 1  dotIndex + 1 == fileName.length()) {             // can't find suitable suffix             return null;         }         fileExtension= fileName.substring(dotIndex + 1);         type = (MIMEType) map.get(fileExtension);         return type;     }     public void addType(String suffix, MIMEType type)         throws java.rmi.RemoteException {         map.put(suffix, type);         fireNotify(MutableFileClassifier.ADD_TYPE);         saveMap();     }     public void removeMIMEType(String suffix, MIMEType type)         throws java.rmi.RemoteException {         if (map.remove(suffix) != null) {             fireNotify(MutableFileClassifier.REMOVE_TYPE);             saveMap();         }     }     public EventRegistration addRemoteListener(RemoteEventListener listener)         throws java.rmi.RemoteException {         listenerList.add(RemoteEventListener.class, listener);         return new EventRegistration(0, this, null, 0);     }     // Notify all listeners that have registered interest for     // notification on this event type. The event instance     // is lazily created using the parameters passed into     // the fire method.     protected void fireNotify(long eventID) {         RemoteEvent remoteEvent = null;         // Guaranteed to return a non-null array         Object[] listeners = listenerList.getListenerList();         // Process the listeners last to first, notifying         // those that are interested in this event         for (int i = listeners.length - 2; i >= 0; i -= 2) {             if (listeners[i] == RemoteEventListener.class) {                RemoteEventListener listener = (RemoteEventListener)listeners[i+1];                 if (remoteEvent == null) {                     remoteEvent = new RemoteEvent(this, eventID,                                                   0L, null);                 }                 try {                     listener.notify(remoteEvent);                 } catch(UnknownEventException e) {                     e.printStackTrace();                 } catch(RemoteException e) {                     e.printStackTrace();                 }             }         }     }     /**      * Restore map from file.      * Install default map if any errors occur      */     public void restoreMap() {         try {             FileInputStream istream = new FileInputStream(mapFile);             ObjectInputStream p = new ObjectInputStream(istream);             map = (Map) p.readObject();             istream.close();         } catch(Exception e) {             e.printStackTrace();             // restoration of state failed, so             // load a predefined set of MIME type mappings             map.put("gif", new MIMEType("image", "gif"));             map.put("jpeg", new MIMEType("image", "jpeg"));             map.put("mpg", new MIMEType("video", "mpeg"));             map.put("txt", new MIMEType("text", "plain"));             map.put("html", new MIMEType("text", "html"));             this.mapFile = mapFile;             saveMap();         }     }     /**      * Save map to file.      */     public void saveMap() {         try {             FileOutputStream ostream = new FileOutputStream(mapFile);             ObjectOutputStream p = new ObjectOutputStream(ostream);             p.writeObject(map);             p.flush();             ostream.close();         } catch(Exception e) {             e.printStackTrace();         }     }     public FileClassifierMutable(ActivationID id, MarshalledObject data)         throws java.rmi.RemoteException {         super(id, 0);         try {             mapFile = (String) data.get();         } catch(Exception e) {             e.printStackTrace();         }         restoreMap();     } } // FileClassifierMutable 

The difference between the server for this service and the last one is that we now have to prepare a marshalled object for the state file and register it with the activation system. Here the filename is hard-coded, but it could be given as a command line argument (as services such as reggie do).

 package activation; import mutable.RemoteFileClassifier; import net.jini.discovery.LookupDiscovery; import net.jini.discovery.DiscoveryListener; import net.jini.discovery.DiscoveryEvent; import net.jini.core.lookup.ServiceRegistrar; import net.jini.core.lookup.ServiceItem; import net.jini.core.lookup.ServiceRegistration; import net.jini.core.lease.Lease; import java.rmi.RMISecurityManager; import java.rmi.MarshalledObject; import java.rmi.activation.ActivationDesc; import java.rmi.activation.ActivationGroupDesc; import java.rmi.activation.ActivationGroupDesc.CommandEnvironment; import java.rmi.activation.Activatable; import java.rmi.activation.ActivationGroup; import java.rmi.activation.ActivationGroupID; import java.util.Properties; import java.rmi.activation.UnknownGroupException; import java.rmi.activation.ActivationException; import java.rmi.RemoteException; /**  * FileClassifierServerMutable.java  */ public class FileClassifierServerMutable implements DiscoveryListener {     static final protected String SECURITY_POLICY_FILE =         "/home/jan/projects/jini/doc/policy.all";     // Don't forget the trailing '/'!     static final protected String CODEBASE = "http://localhost/classes/";     static final protected String LOG_FILE = "/tmp/file_classifier";     // protected FileClassifierImpl impl;     protected RemoteFileClassifier stub;     public static void main(String argv[]) {         new FileClassifierServerMutable(argv);         // stick around while lookup services are found         try {             Thread.sleep(10000L);         } catch(InterruptedException e) {             // do nothing         }         // the server doesn't need to exist anymore         System.exit(0);     }     public FileClassifierServerMutable(String[] argv) {         // install suitable security manager         System.setSecurityManager(new RMISecurityManager());         // Install an activation group         Properties props = new Properties();         props.put("java.security.policy",                 SECURITY_POLICY_FILE);         ActivationGroupDesc.CommandEnvironment ace = null;         ActivationGroupDesc group = new ActivationGroupDesc(props, ace);         ActivationGroupID groupID = null;         try {             groupID = ActivationGroup.getSystem().registerGroup(group);         } catch(RemoteException e) {             e.printStackTrace();             System.exit(1);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         }         try {             ActivationGroup.createGroup(groupID, group, 0);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         }         String codebase = CODEBASE;         MarshalledObject data = null;         try {             data = new MarshalledObject(LOG_FILE);         } catch(java.io.IOException e) {             e.printStackTrace();         }         ActivationDesc desc = null;         try {             desc = new ActivationDesc("activation.FileClassifierMutable",                                                  codebase, data);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         }         try {             stub = (RemoteFileClassifier) Activatable.register(desc);         } catch(UnknownGroupException e) {             e.printStackTrace();             System.exit(1);         } catch(ActivationException e) {             e.printStackTrace();             System.exit(1);         } catch(RemoteException e) {             e.printStackTrace();             System.exit(1);         }         LookupDiscovery discover = null;         try {             discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);         } catch(Exception e) {             System.err.println(e.toString());             System.exit(1);         }         discover.addDiscoveryListener(this);     }     public void discovered(DiscoveryEvent evt) {         ServiceRegistrar[] registrars = evt.getRegistrars();         RemoteFileClassifier service;         for (int n = 0; n < registrars.length; n++) {             ServiceRegistrar registrar = registrars[n];             // export the proxy service             ServiceItem item = new ServiceItem(null,                                                stub,                                                null);             ServiceRegistration reg = null;             try {                 reg = registrar.register(item, Lease.FOREVER);             } catch(java.rmi.RemoteException e) {                 System.err.print("Register exception: ");                 e.printStackTrace();                 // System.exit(2);                 continue;             }             try {                 System.out.println("service registered at " +                                    registrar.getLocator().getHost());             } catch(Exception e) {             }         }     }     public void discarded(DiscoveryEvent evt) {     } } // FileClassifierServerMutable 

This example used a simple way of storing state. Sun uses a far more complex system in many of its services, such as reggie ”a "reliable log" in the package com.sun.jini.reliableLog . However, this package is not a part of standard Jini, so it may change or even be removed in later versions of Jini. There is nothing to stop you from using it, though, if you need a robust storage mechanism.

  


A Programmer[ap]s Guide to Jini Technology
A Programmer[ap]s Guide to Jini Technology
ISBN: 1893115801
EAN: N/A
Year: 2000
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net