`
`Leased F1'1eC1ass1'fier
`
`A dynamically extensible version of a file classifier will have methods to add and
`remove MIME mappings:
`
`package common;
`
`import java.io.Serializable;
`
`/>|<*
`
`* LeaseFileClassifier.java
`*/
`
`import net.jini.core.1ease.Lease;
`
`public interface LeaseFileClassifier extends Serializable {
`
`public MIMEType getMIMEType(String fileName)
`throws java.Imi.RemoteException;
`
`Add the MIME type for the given suffix.
`The suffix does not contain '.' e.g. "gif”.
`
`@exception net.jini.core.lease.LeaseDeniedException
`a previous MIME type for that suffix exists.
`This type is removed on expiration or cancellation
`of the lease.
`
`*/
`
`public Lease addType(String suffix, MIMEType type)
`throws java.Imi.RemoteException,
`net.jini.core.lease.LeaseDeniedException;
`
`/*>l<
`
`* Remove the MIME type for the suffix.
`
`*/
`
`public void removeType(String suffix)
`throws java.rmi.RemoteException;
`} // LeaseFileClasssifier
`
`The addType() method returns a lease. We shall use the landlord leasing system
`discussed in Chapter 7. The client and the service will be in different ]avaVMs, prob-
`ably on different machines. Figure 13-5 gives the object structure on the service side.
`
`251
`
`
`
`This should be compared to Figure 7-3 where we considered the “foo” implementa-
`tion of landlord leasing.
`
`More Complex Examples
`
`1 Fileclassifier
`Server
`
`Fileclassifier
`Leasedfiesource
`
`Figure 13-5. Class diagram for leasing on the server
`
`On the client side, the lease object will be a copy of the lease created on the
`
`server (normally RM1 semantics), but the other objects from the service will be
`
`stubs that call into the real objects on the service. This is shown in Figure 13-6.
`
`TestFile
`ClassifierLease
`
`Fileclassifier
`
`|mpl_Stub
`
`7
`
`7
`
`Fileclassifier
`
`Land|ord_Stub
`
`Figure 13-6. Class diagram for leasing on the client
`
`252
`
`
`
`The FileClassifierLeasedResource Class
`
`The FileclassifierLeasedResource class acts as a wrapper around the actual
`resource, adding cookie and time expiration fields around the resource. It adds a
`
`unique cookie mechanism, in addition to making the wrapped resource visible.
`
`/>i<*
`
`* FileClassifierLeasedResource.java
`*/
`
`package lease;
`
`import common.LeaseFileClassifier;
`
`import com.sun.jini.lease.landlord.LeasedResource;
`
`public class FileClassifierLeasedResource implements LeasedResource
`
`{
`
`static protected int cookie = 0;
`protected int thisCookie;
`protected LeaseFileClassifier fi1eClassifier;
`protected long expiration = 0;
`protected String suffix = null;
`
`public FileClassifierLeasedResource(LeaseFileClassifier fileclassifier;
`String suffix) {
`this.fileClassifier = fileClassifier;
`this.suffix = suffix;
`thiscookie = cookie++;
`
`public void setExpiration(long newExpiIation)
`this.expiration = newExpiration;
`
`{
`
`}
`
`public long getExpiration() {
`return expiration;
`
`} p
`
`ublic Object getCookie() {
`return new Integer(thisCookie);
`
`public LeaseFileClassifier getFileClassifier() {
`return fileclassifier;
`
`CWapwr13
`
`253
`
`
`
`More Complex Examples
`
`public String getSuffix() {
`return suffix;
`
`}
`
`} // FileClassifierLeasedResource
`
`The Fi1eClassifierLeaseManager Class
`
`The FileClassifierLeaseManager class is Very similar to the code given for the
`FooLeaseManager in Chapter 7:
`
`/*>i<
`
`* FileClassifierLeaseManager.java
`*/
`
`package lease;
`
`import java.util.*;
`import common.LeaseFileClassifier;
`
`import net.jini.core.lease.Lease;
`
`import com.sun.jini.lease.landlord.LeaseManager;
`import com.sun.jini.lease.landlord.LeasedResource;
`
`import com.sun.jini.1ease.landlord.LeaseDurationPolicy;
`import com.sun.jini.lease.landlord.Landlord;
`import com.sun.jini.1ease.landlord.LandlordLease;
`
`import com.sun.jini.lease.land1ord.LeasePolicy;
`
`public class FileClassifierLeaseManager implements LeaseManager {
`
`protected static long DEFAULT_TIME = 30*1000L;
`
`protected Vector tileClassifierResources = new Vector();
`protected LeaseDurationPolicy policy;
`
`public FileClassifierLeaseManager(Landlord landlord) {
`policy = new LeaseDurationPolicy(Lease.FOREVER,
`DEFAULT_TIME,
`landlord,
`this,
`
`new LeaseReaper().start();
`
`new LandlordLease.Factory());
`
`public void register(LeasedResource r,
`
`long duration) {
`
`254
`
`
`
`Chapter 13
`
`tileClassitierResouIces.add(r);
`
`} p
`
`long duration,
`ublic void renewed(LeasedResource I,
`// no smarts in the scheduling, so do nothing
`
`long olddur)
`
`{
`
`} p
`
`ublic void cancelAl1(Object[] cookies) {
`for (int n = cookies.1ength;
`--n >= 0;
`cancel(cookies[ ]);
`
`)
`
`{
`
`public void cancel(0bject cookie) {
`{
`)
`for (int n = fileC1assitierResources.si2e(); --n >= 0;
`FileClassitierLeasedResource I = (FileClassitierLeasedResource)
`fileClassifieIResources.elementAt(n);
`
`if (Ipo1icy.ensureCurrent(r)) {
`System.out.println("Lease expired for cookie = " +
`r.getCookie());
`
`try {
`r.getFileClassitieI().removeType(r.getSuffix());
`} catch(java.Imi.RemoteException e) {
`e.printStackTrace();
`
`} f
`
`i1eClassifierResources.removeElementAt(n);
`
`public LeasePolicy getPolicy() {
`return policy;
`
`} p
`
`ublic LeasedResource getResource(0bject cookie) {
`) {
`for (int n = fileClassifierResources.size()3 ~-n >= 0;
`FileClassifierLeasedResource I = (FileClassitierLeasedResource)
`fileC1assifierResources.elementAt(n);
`
`if (r.getCookie().equals(cookie)) {
`return I;
`
`} r
`
`eturn null;
`
`255
`
`
`
`More Complex Examples
`
`class Leasekeaper extends Thread {
`public void run() {
`while (true) {
`try {
`Thread.s1eep(DEFAULT_TIME)
`
`;
`
`} c
`
`atch (InterruptedException e) {
`
`} f
`
`n >= 0; n—-) {
`or (int n = fileClassifierResources.size()—1;
`Fi1eClassifierLeasedResource r = (FileclassifierLeasedResource)
`fileClassifierResources.elementAt(n)
`
`if (Ipolicy.ensuIeCurrent(r)) {
`System.out.println("Lease expired for cookie = " +
`r.getCookie()) ;
`
`try {
`
`r.getFileClassifier().removeType(r.getSuffix())5
`} catch(java.rmi.RemoteException e)
`{
`e.printStackTrace();
`
`} f
`
`ileC1assifierResouIces.removeElementAt(n);
`
`} // FileClassifierLeaseManager
`
`The F1'leClass1'f1'erLand1ord Class
`
`The Fi1eClassifierLandlord class is very similar to the FooLandlord in Chapter 7:
`
`/**
`
`* FileClassifierLandloId.java
`*/
`
`package lease;
`
`import common.LeaseFileClassifier;
`
`256
`
`
`
`Chapwr13
`
`import com.sun.jini.lease.landlord.*;
`
`import net.jini.core.lease.LeaseDeniedException;
`import net.jini.core.lease.Lease;
`import java.rmi.server.UnicastRemote0bject;
`import java.rmi.Remote;
`
`public class FileClassitierLandlord extends UnicastRemoteObject
`lord, Remote {
`
`implements Land-
`
`FileClassitierLeaseManager manager = null;
`
`public FileClassifierLandlord() throws java.rmi.RemoteException {
`manager = new FileClassifierLeaseManager(this);
`
`public void cancel(0bject cookie) {
`manager.cancel(cookie);
`
`public void cancelAll(Object[] cookies) {
`manager.cancelAll(cookies);
`
`public long renew(java.lang.Object cookie,
`long extension)
`throws net.jini.core.lease.LeaseDeniedException,
`net.jini.core.lease.UnknownLeaseException {
`LeasedResource resource = manager.getResource(cookie);
`if (resource != null) {
`return manager.getPolicy().renew(resource, extension);
`
`} r
`
`eturn -1;
`
`public Lease newFileClassifierLease(LeaseFileClassifier tileClassifier,
`String sutfixKey,
`long duration)
`
`throws LeaseDeniedException {
`FileclassifierLeasedResource r = new
`
`Fi1eClassifierLeasedResource(tileClassitier,
`
`return manager.getPolicy().leaseFor(r, duration);
`
`suffixKey);
`
`public Landlord.RenewResults renewAll(java.lang.Object[] cookie,
`
`257
`
`
`
`long[] extension) {
`
`More Complex Examples
`
`return null;
`
`}
`
`} // FileClassifierLandloId
`
`Summary
`
`Iini provides a framework for building distributed applications. Nevertheless,
`there is still room for variation in how services and clients are written, and some of
`
`these are better than others. This chapter has looked at some of the variations that
`can occur and how to deal with them.
`
`258
`
`
`
`CHAPTER 14
`
`Remote Events
`
`COMPONENTS OF A SYSTEM CAN CHANGE STATE and may need to inform other compo-
`nents that this change has happened. Java Beans and user—interface elements such
`
`as AWT or Swing objects use events to signal these changes. Iini also has an event
`
`mechanism, and this chapter looks at the distributed event model that is part of
`Jini. It looks at how remote event listeners are registered with objects, and how
`
`these objects notify their listeners of changes. Event listeners may disappear, and
`so the Iini event mechanism uses leases to manage listener lists.
`
`This chapter also looks at how leases are managed by event sources. Finally,
`
`we’ll look at how events can be used by applications to monitor when services are
`
`registered or discarded from service locators.
`
`Event Models
`
`lava has a number of event models, differing in various subtle ways. All of these
`
`involve an object (an eventsource) generating an event in response to some
`change of state, either in the object itself (for example, if someone has changed a
`field), or in the external environment (such as when a user moves the mouse). At
`
`some earlier stage, a listener (or set of listeners) will have registered interest in this
`
`event. When the event source generates an event, it will call suitable methods on
`the listeners with the event as parameter. The event models all have their origin
`
`in the Observer pattern from Design Patterns, by Eric Gamma et al., but this is mod-
`
`ified by other pressures, such as Iava Beans.
`There are low—level input events, which are generated by user actions when
`they control an application with a graphical user interface. These events—of type
`
`KeyEvent and MouseEvent—are placed in an event queue. They are removed from the
`queue by a separate thread and dispatched to the relevant objects. In this case, the
`object that is responsible for generating the event is not responsible for dispatching
`it to listeners, and the creation and dispatch of events occurs in different threads.
`Input events are a special case caused by the need to listen to user interactions
`
`and always deal with them without losing response time. Most events are dealt
`
`with in a simpler manner: an object maintains its own list of listeners, generates its
`
`own events, and dispatches them directly to its listeners. In this category fall all the
`semantic events generated by the AWT and Swing toolkits, such as ActionEvent,
`ListSe1ectionEvent, etc. There is a large range of these event types, and they all call
`
`259
`
`
`
`Chapwr14
`
`different methods in the listeners, based on the event name. For example, an
`
`ActionEvent is used in a listener’s actionPeItormed () method of an ActionListeneI.
`
`There are naming conventions involved in this, specified by Java Beans.
`Java Beans is also the influence behind Propertychange events, which get deliv-
`ered whenever a Bean changes a “bound" or “constrained” property value. These
`are delivered by the event source calling the listener’s Propertychange Listener's
`propeItyChange() method or the VetoableChangeListeneI’s vetoab1eChange()
`method. These are usually used to signal a change in a field of an object, where this
`change may be of interest to the listeners either for information or for vetoing.
`Iini objects may also be interested in changes in other Iini objects, and might
`like to be listeners for such changes. The networked nature of Jim has led to a
`particular event model that differs slightly from the other models already in Java.
`The differences are caused by several factors:
`
`9' Network delivery is unreliable—rnessages may be lost. Synchronous methods
`requiring a reply may not work here.
`
`° Network delivery is time—dependent—messages may arrive at different
`times to different listeners. As a result, the state of an object as perceived by
`a listener at any time may be inconsistent with the state of that object as
`perceived by others. Passing complex object state across the network may
`be more complex to manage than passing simpler information.
`
`9 A remote listener may have disappeared by the time the event occurs.
`Listeners have to be allowed to time out, like services do.
`
`Java Beans can require method names and event types that vary and can use
`many classes. This requires a large number of classes to be available across
`the network, which is more complex than a single class with a single method
`with a single event type as parameter (the original Observer pattern used a
`single class with only one method, for simplicity).
`
`Remote Events
`
`Unlike the large number of event classes in the AWT and Swing, for example, Iini uses
`events of one type, the RemoteEvent, or a small number of subclasses of RemoteEvent.
`The RemoteEvent class has these public methods (and some inherited methods):
`
`package net.jini.core.event;
`
`public class RemoteEvent
`public long getID();
`public long getSequenceNumbeI();
`
`implements java.io.Serializable {
`
`260
`
`
`
`Remote Events
`
`public java .Imi .MaIshal1ed0bject getkegistrationobject ( );
`
`Events in Beans and AWT convey complex object state information, and this is
`
`enough for the listeners to act with full knowledge of the changes that have caused
`the event to be generated. Iini events avoid this, and convey just enough informa-
`tion to allow state information to be found if needed. A remote event is serializable
`and is moved around the network to its listeners. The listeners then have to decide
`
`whether or not they need more detailed information than the simple information
`
`in each remote event. If they do need more information, they will have to contact
`the event source to get it.
`AWT events, such as MouseEvent, contain an id field that is set to values such as
`
`MOUSE_PRESSED or MOUSE_RELEASED. These are not seen by the AWT programmer
`because the AWT event dispatch system uses the id field to choose appropriate
`methods, such as mousePressed () or mouseReleased ( ). Iini does not make these
`
`assumptions about event dispatch, and just gives you the identifier. Either the
`source or the listener (or both) will know what this value means. For example, a file
`
`classifier that can update its knowledge of MIME types could have message types
`
`ADD_TYPE and REMOVE_TYPE to reflect the sort of changes it is going through.
`In a synchronous system with no losses, both sides of an interaction can keep
`consistent ideas of state and order of events. In a network system this is not so
`
`easy. Iini makes no assumptions about guarantees of delivery and does not even
`assume that events are delivered in order. The Jim event mechanism does not
`
`specify how events get from producer to listener—it could be by RMI calls, but it
`may be through an unreliable third party. The event source supplies a sequence
`number that could be used to construct state and ordering information if needed,
`
`and this generalizes things such as time—stamps on mouse events. For example, a
`message with id of ADD_TYPE and sequence number of 10 could correspond to the
`
`state change “added MIME type text/xml for files with suffix .xml.” Another event
`
`with id of REMOVE_TYPE and sequence number of 11 would be taken as a later event,
`even if it arrived earlier. The listener will receive the event with id and sequence
`
`number only. Either this will be meaningful to the listener, or it will need to contact
`the event source and ask for more information about that sequence number. The
`
`event source should be able to supply state information upon request, given the
`sequence number.
`
`An idea borrowed from systems such as the Xt Intrinsics and Motif is called
`handback data. This is a piece of data that is given by the listener to the event
`
`source at the time it registers itself for events. The event source records this hand-
`back and then returns it to the listener with each event. This handback can be a
`
`reminder of listener state at the time of registration.
`This can be a little difficult to understand at first. The listener is basically
`
`saying to the event source that it wants to be told whenever something interesting
`happens, but when that does happen, the listener may have forgotten why it was
`
`261
`
`
`
`CWapwr14
`
`interested in the first place, or what it intended to do with the information. So the
`listener also the gives the event source some extra information that it wants
`returned as a “reminder.”
`
`For example, a lini taxi-driver might register interest in taxi—booking events
`from the base station while passing through a geographical area. It registers itself
`as a listener for booking events, and as part of its registration, it could include its
`current location. Then, when it receives a booking event, it is told its old location,
`and it could check to see if it is still interested in events from that old location. A
`
`more novel possibility is that one object could register a different object for events,
`so your stockbroker could register you for events about stock movements, and
`when you receive an event, you would also get a reminder about who registered
`your interest (plus a request for commission...).
`
`Event Registration
`
`Iini does not say how to register listeners with objects that can generate events.
`This is unlike other event models in Java that specify methods, like this
`
`public void addActionListener(Actionlistener listener);
`
`for ActionEvent generators. What Iini does do is to specify a convenience class as a
`return value from this registration. This is the convenience class Eventkegistrat ion:
`
`package net.jini.core.event;
`import net . j ini . core . lease . Lease;
`
`public class EventRegistIation implements java.io.Serializable {
`public EventRegistration(long eventID, Object source,
`Lease lease,
`long seqNum);
`
`public long getID();
`public Object getSouIce();
`public Lease getLease();
`public long getSequenceNumber();
`
`This return object contains information that may be of value to the object that
`registered a listener. Each registration will typically only be for a limited amount of
`time, and this information may be returned in the Lease object. If the event regis-
`tration was for a particular type, this may be returned in the id field. A sequence
`number may also be given. The meaning of these values may depend on the par-
`ticular system—in other words, Iini gives you a class that is optional in use, and
`whose fields are not tightly specified. This gives you the freedom to choose your
`own meanings to some extent.
`
`262
`
`
`
`Remote Events
`
`This means that as the programmer of a event producer, you have to define
`
`(and implement) methods such as these:
`
`public EventRegistration addRemoteEventListener(RemoteEventListener listener);
`
`There is no standard interface for this.
`
`Listener List
`
`Each listener for remote events must implement the RemoteEventListener
`interface:
`
`public interface RemoteEventListener
`java.util.EventListener {
`extends java.rmi.Remote,
`public void notity(RemoteEvent theEvent)
`throws UnknownEventException,
`java.rmi.RemoteException;
`
`Because it extends Remote, the listener will most likely be something like an RMI
`stub for a remote object, so that calling notify() will result in a call on the remote
`
`object, with the event being passed across to it.
`
`In event generators, there are multiple implementations for handling lists of
`
`event listeners all the way through the Java core and extensions. There is no public
`API for dealing with event—listener lists, and so the programmer has to reinvent (or
`copy) code to pass events to listeners. There are basically two cases:
`
`° Only one listener can be in the list.
`
`- Any number of listeners can be in the list.
`
`Single Listener
`
`The case where there is only one listener allowed in the list can be implemented by
`using a single-valued variable, as shown in Figure 14— 1.
`This is the simplest case of event registration:
`
`protected RemoteEventListener listener = null;
`
`public Eventkegistration addRemoteListener(RemoteEventListener listener)
`throws java.util.TooManyListenersException {
`
`263
`
`
`
`Chapter 1 4
`
`listener
`
`_
`RemoteEventLIstener
`
`Figure 14-1. A single listener‘
`
`if (this.listener == null {
`this.listener = listener;
`
`} else {
`
`throw new java.util.TooManyListenersException();
`
`} r
`
`eturn new EventRegistration(0L, this, null, OL);
`
`This is close to the ordinary Java event registration—no really useful informa-
`
`tion is returned that wasn’t known before. In particular, there is no lease object, so
`
`you could probably assume that the lease is being granted ‘‘forever,’' as would be
`the case with non—networked objects.
`When an event occurs, the listener can be informed by the event generator
`
`calling fireNot ify( ):
`
`protected void tireNotity(long eventID,
`long seqNum)
`if (listener == null) {
`return;
`
`{
`
`RemoteEvent remoteEvent = new RemoteEvent(this, eventID,
`
`seqNum, null);
`
`listener. notify(remoteEvent);
`
`It is easy to add a handback to this: just add another field to the object, and set
`and return this object in the registration and notify methods. Far more complex is
`
`adding a non—null lease. Firstly, the event source has to decide on a lease policy,
`that is, for what periods of time it will grant leases. Then it has to implement a
`
`timeout mechanism to discard listeners when their leases expire. And finally, it has
`
`to handle lease renewal and cancellation requests, possibly using its lease policy
`again to make decisions. The landlord package would be of use here.
`
`264
`
`
`
`Remote Events
`
`Multiple Listeners
`
`For the case where there can be any number of listeners, the convenience class
`
`javax. swing.event . Eventtistenerlist can be used. The object delegates some of
`the list handling to the convenience class, as shown in Figure 14-2.
`
`EventGenerator
`
`addRemoteListener()
`
`Figure 14-2. Multiple listeners
`
`|istenerList
`
`_
`v RemoteEventL|stener
`
`A version of event registration suitable for ordinary events is as follows:
`
`import javax.swing.event.EventListenerList;
`
`EventListenerList listenerList = new EventListenerList();
`
`public EventRegistration addRemoteListener(RemoteEventListener l) {
`listenerList.add(RemoteListener.class, 1);
`return new EventRegistration(0L, this, null, OL);
`
`public void removeRemoteListener(RemoteEventListener l) {
`listenerList.remove(RemoteListener.class, l);
`
`// 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,
`long seqNum)
`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
`
`265
`
`
`
`{
`n -= 2)
`for (int n = listeners.1ength — 2; n >= 0;
`if (listeners[n] == RemoteEventListener.class) {
`RemoteEventListener listener =
`
`(RemoteEventListener) listeners[n+1];
`if (IemoteEvent == null) {
`remoteEvent = new RemoteEvent(this, eventID,
`
`seqNum, null);
`
`{
`
`} t
`
`ry {
`listener.notify(remoteEvent);
`} catch(UnknownEventException e)
`
`e . pIintStackTIace();
`} catch(java.rmi.RemoteException e) {
`e.printStackTrace();
`
`Chapmr14
`
`} I
`
`n this case, a source object need only call fireNotify() to send the event to all
`listeners. (You may decide that it is easier to simply use a Vector of listeners.)
`
`It is again straightforward to add hanclbacks to this. The only tricky point is
`that each listener can have its own handback, so they will need to be stored in
`some kind of map (say a HashMap) keyed on the listener. Then, before not ify() is
`called for each listener, the handback will need to be retrieved for the listener and
`a new remote event created with that handback.
`
`Listener Source
`
`The ordinary Java event model has all objects in a single address space, so that
`registration of event listeners and notifying these listeners all takes place using
`objects in the one space. We have already seen that this is not the case with Jim.
`Jim is a networked federation of objects, and in many cases one is dealing with
`proxy objects, not the real objects.
`This is the same with remote events, except that in this case we often have the
`direction of proxies reversed. To see what I mean by this, consider what happens if
`a client wants to monitor any changes in the service. The client will already have a
`proxy object for the service, and it will use this proxy to register itself as a listener.
`However, the service proxy will most likely just hand this listener back off to the
`service itself (that is what proxies, such as RMI proxies, do). So we need to get a
`
`proxy for the client over to the service.
`
`266
`
`
`
`Remote Events
`
`Consider the file classification problems we looked at in earlier chapters. The
`file classifier had a hard—coded set of filename extensions built in. However, it
`
`would be possible to extend these, if applications come along that know how to
`
`define (and maybe handle) such extensions. For example, an application would
`locate the file classification server, and using an exported method from the file
`classification interface would add the new MIME type and file extension. This is
`
`no departure from any standard Java or earlier Iini stuff. It only affects the imple-
`mentation level of the file classifier, changing it from a static list of filename
`extensions to a more dynamic one.
`
`What it does affect is the poor application that has been blocked (and is prob—
`ably sleeping) on an unknown filename extension.When the classifier installs a
`
`new file type, it can send an event saying so. The blocked application could then
`try again to see if the extension is now known. If so, it uses it, and if not, it blocks
`
`again. Note that we don’t bother with identifying the actual state change, since it is
`just as easy to make another query once you know that the state has changed.
`More complex situations may require more information to be maintained. How-
`
`ever, in order to get to this situation, the application must have registered its
`interest in events, and the event producer must be able to find the listener.
`
`How this gets resolved is for the client to first find the service in the same way
`as we discussed in Chapter 6. The client ends up with a proxy object for the service
`
`in the client's address space. One of the methods on the proxy will add an event lis-
`tener, and this method will be called by the client.
`For simplicity, assume that the client is being added as a listener to the service.
`
`The client will call the add listener method of the proxy, with the client as parame—
`ter. The proxy will then call the real object's add listener method, back on its server
`side. But in doing this, we have made a remote call across the network, and the cli~
`
`ent, which was local to the call on the proxy, is now remote to the real object, so
`what the real object is getting is a proxy to the client. When the service makes noti-
`fication calls to the proxy listeners, the client’s proxy can make a remote call back
`
`to the client itself. These proxies are shown in Figure 14-3.
`
`Client
`
`Service
`
`proxy
`
`Service
`
`Figure 14—3. Proxies for services and listeners
`
`267
`
`
`
`Chapter 1 4
`
`File Classifier with Events
`
`Let’s make this discussion more concrete by looking at a new file classifier applica-
`
`tion that can have its set of mappings dynamically updated.
`The first thing to be modified is the FileC1assifier interface. This needs to be
`extended to a MutableFileClassifier interface, known to all objects. This new
`
`interface adds methods that will add and remove types, and that will also register
`listeners for events. The event types are labeled with two constants. The listener
`model is simple, and does not include handbacks or leases. The sequence identifier
`must be increasing, so we just add 1 on each event generation, although we don’t
`really need it here: it is easy for a listener to just make MIME type queries again.
`
`package common;
`
`import java.io.Serializable;
`
`/*>i<
`
`* MutableFi1eClassifier .java
`*/
`
`import net . j ini . core . event . RemoteEventListener;
`import net.jini.coIe.event.EventRegistration;
`
`public interface MutableFileClassifier extends FileClassifier {
`
`static final public long ADD_TYPE = 1;
`static final public long REMOVE_TYPE = 2;
`
`/*
`
`* Add the MIME type for the given suffix.
`* The suffix does not contain '.' e.g. "gif".
`* Overrides any previous MIME type for that suffix
`*/
`
`public void addType(String suffix, MIMEType type)
`throws java.rmi.RemoteException;
`
`/*
`
`* Delete the MIME type for the given suffix.
`* The suffix does not contain '.' e.g. "gif".
`* Does nothing if the suffix is not known
`*/
`
`public void removeMIMEType(String suffix, MIMEType type)
`throws java.rmi.RemoteException;
`
`268
`
`
`
`Remote E1/en ts
`
`public EventRegistration addRemoteListener(RemoteEventListener listener)
`throws java.rmi.RemoteException;
`
`} // MutableFi1eClasssifier
`
`The RemoteFi1eC1ass:'n‘ie:r interface is known only to services, and it just
`changes its package and inheritance for any service implementation:
`
`package mutable;
`
`import common.MutableFileClassifier;
`import
`java.Imi.Remote;
`
`/*>l<
`
`* RemoteFi1eC1assifier.java
`*/
`
`public interface RemoteFi1eClassifieI extends MutableFileClassifier, Remote {
`
`} // RemoteFileC1asssifier
`
`Previous implementations of file classifier services (such as in Chapter 8) use a
`static list of if. . .then statements because they deal with a fixed set of types. For
`this implementation, where the set of mappings can change, we change the imple-
`
`mentation to a dynamic map keyed on file suffixes. It manages the event listener
`
`list for multiple listeners in the simple way discussed earlier in this chapter, and it
`
`generates events whenever a new suffix/ type is added or successfully removed.
`The following code is an implementation of the file classifier service with this
`alternative implementation and an event list:
`
`package mutable;
`
`import java.rmi.server.UnicastRemoteObject;
`import java.Imi.MarshalledObject;
`import net.jini.coIe.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;
`
`269
`
`
`
`Chapmr14
`
`import common.MutableFileClassifier;
`import java.util.Map;
`import java.util.HashMap;
`
`/>i<*
`
`* FileClassifierImpl.java
`*/
`
`public class FileClassifieIImp1 extends Unicastkemoteobject
`implements RemoteFileClassifier {
`
`/**
`
`* Map of String extensions to MIME types
`*/
`
`protected Map map = new HashMap();
`
`/*>k
`
`* Listeners for change events
`*/
`
`protected EventListenerList listenerList = new EventListenerList();
`
`protected long seqNum = 0L;
`
`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 {
`
`270
`
`
`
`Remote Events
`
`type);
`map.put(suffix;
`fireNotify(ADD_TYPE);
`
`} p
`
`ublic void removeMIMEType(String suffix, MIMEType type)
`throws java.rmi.RemoteException {
`
`!= null) {
`if (map.remove(suffix)
`fireNotify(REMOVE_TYPE);
`
`} p
`
`ublic Eventkegistration 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
`
`0bject[] 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,
`seqNum++, null);
`
`{
`
`} t
`
`ry {
`listener.notify(remoteEvent);
`} catch(UnknownEventException e)
`
`e.printStackTrace();
`} catch(RemoteException e)
`e.printStackTrace();
`
`{
`
`271
`
`
`
`} p
`
`throws java.rmi.RemoteException {
`ublic FileClassifierImpl()
`// 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"));
`
`}
`} // FileClassifierImpl
`
`The proxy changes its inheritance, and as a result has more methods to imple-
`ment, which it just delegates to its server object. The following class is for the proxy:
`
`package mutable;
`
`im