View Javadoc

1   /*
2    * 
3    * Copyright 2005 Sun Microsystems, Inc.
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * 
9    *  http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   * 
17   * Note: Changes by the Jgroup team are marked ** GREG **.
18   * 
19   */
20  package com.sun.jini.reggie;
21  
22  import java.io.DataInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InterruptedIOException;
26  import java.io.ObjectInputStream;
27  import java.io.ObjectOutputStream;
28  import java.io.OutputStream;
29  import java.io.Serializable;
30  import java.lang.reflect.Array;
31  import java.net.DatagramPacket;
32  import java.net.InetAddress;
33  import java.net.InetSocketAddress;
34  import java.net.MulticastSocket;
35  import java.net.NetworkInterface;
36  import java.net.ServerSocket;
37  import java.net.Socket;
38  import java.net.SocketException;
39  import java.net.SocketTimeoutException;
40  import java.net.UnknownHostException;
41  import java.nio.BufferUnderflowException;
42  import java.nio.ByteBuffer;
43  import java.rmi.MarshalledObject;
44  import java.rmi.NoSuchObjectException;
45  import java.rmi.RemoteException;
46  import java.rmi.activation.ActivationException;
47  import java.rmi.activation.ActivationID;
48  import java.rmi.activation.ActivationSystem;
49  import java.security.PrivilegedActionException;
50  import java.security.PrivilegedExceptionAction;
51  import java.util.ArrayList;
52  import java.util.Arrays;
53  import java.util.Collections;
54  import java.util.Enumeration;
55  import java.util.HashMap;
56  import java.util.Iterator;
57  import java.util.List;
58  import java.util.Map;
59  import java.util.NoSuchElementException;
60  import java.util.Random;
61  import java.util.TreeMap;
62  import java.util.logging.Level;
63  import java.util.logging.Logger;
64  
65  import javax.security.auth.Subject;
66  import javax.security.auth.login.LoginContext;
67  import javax.security.auth.login.LoginException;
68  
69  import net.jini.activation.ActivationExporter;
70  import net.jini.activation.ActivationGroup;
71  import net.jini.config.Configuration;
72  import net.jini.config.ConfigurationException;
73  import net.jini.config.ConfigurationProvider;
74  import net.jini.config.NoSuchEntryException;
75  import net.jini.constraint.BasicMethodConstraints;
76  import net.jini.core.constraint.InvocationConstraints;
77  import net.jini.core.constraint.MethodConstraints;
78  import net.jini.core.constraint.RemoteMethodControl;
79  import net.jini.core.discovery.LookupLocator;
80  import net.jini.core.entry.Entry;
81  import net.jini.core.event.EventRegistration;
82  import net.jini.core.event.RemoteEventListener;
83  import net.jini.core.lease.Lease;
84  import net.jini.core.lease.UnknownLeaseException;
85  import net.jini.core.lookup.ServiceID;
86  import net.jini.core.lookup.ServiceItem;
87  import net.jini.core.lookup.ServiceRegistrar;
88  import net.jini.core.lookup.ServiceRegistration;
89  import net.jini.discovery.Constants;
90  import net.jini.discovery.ConstrainableLookupLocator;
91  import net.jini.discovery.DiscoveryGroupManagement;
92  import net.jini.discovery.DiscoveryLocatorManagement;
93  import net.jini.discovery.DiscoveryManagement;
94  import net.jini.discovery.LookupDiscoveryManager;
95  import net.jini.export.Exporter;
96  import net.jini.export.ProxyAccessor;
97  import net.jini.id.ReferentUuid;
98  import net.jini.id.Uuid;
99  import net.jini.id.UuidFactory;
100 import net.jini.io.MarshalledInstance;
101 import net.jini.io.UnsupportedConstraintException;
102 import net.jini.jeri.BasicILFactory;
103 import net.jini.jeri.BasicJeriExporter;
104 import net.jini.jeri.tcp.TcpServerEndpoint;
105 import net.jini.lookup.JoinManager;
106 import net.jini.lookup.entry.ServiceInfo;
107 import net.jini.security.BasicProxyPreparer;
108 import net.jini.security.ProxyPreparer;
109 import net.jini.security.TrustVerifier;
110 import net.jini.security.proxytrust.ServerProxyTrust;
111 
112 import com.sun.jini.config.Config;
113 import com.sun.jini.constants.ThrowableConstants;
114 import com.sun.jini.discovery.ClientSubjectChecker;
115 import com.sun.jini.discovery.Discovery;
116 import com.sun.jini.discovery.DiscoveryConstraints;
117 import com.sun.jini.discovery.DiscoveryProtocolException;
118 import com.sun.jini.discovery.EncodeIterator;
119 import com.sun.jini.discovery.MulticastAnnouncement;
120 import com.sun.jini.discovery.MulticastRequest;
121 import com.sun.jini.discovery.UnicastResponse;
122 import com.sun.jini.logging.Levels;
123 import com.sun.jini.lookup.entry.BasicServiceType;
124 import com.sun.jini.proxy.MarshalledWrapper;
125 import com.sun.jini.reliableLog.LogHandler;
126 import com.sun.jini.reliableLog.ReliableLog;
127 import com.sun.jini.start.LifeCycle;
128 import com.sun.jini.thread.InterruptedStatusThread;
129 import com.sun.jini.thread.ReadersWriter;
130 import com.sun.jini.thread.ReadyState;
131 import com.sun.jini.thread.TaskManager;
132 import com.sun.jini.thread.ReadersWriter.ConcurrentLockException;
133 
134 /**
135  * Base server-side implementation of a lookup service, subclassed by
136  * TransientRegistrarImpl and PersistentRegistrarImpl.  Multiple client-side
137  * proxy classes are used, for the ServiceRegistrar interface as well as for
138  * leases and administration; their methods transform the parameters and then
139  * make corresponding calls on the Registrar interface implemented on the
140  * server side.
141  *
142  * @author Sun Microsystems, Inc.
143  * 
144  */
145 class RegistrarImpl implements Registrar, ProxyAccessor, ServerProxyTrust
146 {
147 
148   /** Maximum minMax lease duration for both services and events */
149   private static final long MAX_LEASE = 1000L * 60 * 60 * 24 * 365 * 1000;
150 
151   /** Maximum minimum renewal interval */
152   private static final long MAX_RENEW = 1000L * 60 * 60 * 24 * 365;
153 
154   /** Default maximum size of multicast packets to send and receive */
155   private static final int DEFAULT_MAX_PACKET_SIZE = 512;
156 
157   /** Default time to live value to use for sending multicast packets */
158   private static final int DEFAULT_MULTICAST_TTL = 15;
159 
160   /** Default timeout to set on sockets used for unicast discovery */
161   private static final int DEFAULT_SOCKET_TIMEOUT = 1 * 60 * 1000;
162 
163   /** Log format version */
164   private static final int LOG_VERSION = 3;
165 
166   /** Logger and configuration component name */
167   private static final String COMPONENT = "com.sun.jini.reggie";
168 
169   /** Lease ID always assigned to self */
170   private static final Uuid myLeaseID = UuidFactory.create(0L, 0L);
171 
172   /** Logger used by this service */
173   private static final Logger logger = Logger.getLogger(COMPONENT);
174 
175   /** Base set of initial attributes for self */
176   /** ** GREG ** Removed dependency to sun-util.jar */
177   private static final Entry[] baseAttrs = {
178       new ServiceInfo("Lookup", "Sun Microsystems, Inc.", "Sun Microsystems, Inc.",
179           "2.1", "", ""), new BasicServiceType("Lookup") };
180 
181   /** Empty attribute set */
182   private static final EntryRep[] emptyAttrs = {};
183 
184   /** Proxy for myself */
185   private RegistrarProxy proxy;
186 
187   /** Exporter for myself */
188   private Exporter serverExporter;
189 
190   /** Remote reference for myself */
191   private Registrar myRef;
192 
193   /** Our service ID */
194   private ServiceID myServiceID;
195 
196   /** Our activation id, or null if not activatable */
197   private ActivationID activationID;
198 
199   /** Associated activation system, or null if not activatable */
200   private ActivationSystem activationSystem;
201 
202   /** Our LookupLocator */
203   private volatile LookupLocator myLocator;
204 
205   /** Our login context, for logging out */
206   private LoginContext loginContext;
207 
208   /** Shutdown callback object, or null if no callback needed */
209   private LifeCycle lifeCycle;
210 
211   /**
212    * Map from group identifiers to group compositions. ** GREG **
213    */
214   private final Map groupMap = new HashMap();
215   
216   /**
217    * Map from ServiceID to SvcReg.  Every service is in this map under
218    * its serviceID.
219    */
220   private final HashMap serviceByID = new HashMap();
221 
222   /**
223    * Identity map from SvcReg to SvcReg, ordered by lease expiration.
224    * Every service is in this map.
225    */
226   private final TreeMap serviceByTime = new TreeMap();
227 
228   /**
229    * Map from String to HashMap mapping ServiceID to SvcReg.  Every service 
230    * is in this map under its types.
231    */
232   private final HashMap serviceByTypeName = new HashMap();
233 
234   /**
235    * Map from EntryClass to HashMap[] where each HashMap is a map from
236    * Object (field value) to ArrayList(SvcReg).  The HashMap array has as
237    * many elements as the EntryClass has fields (including fields defined
238    * by superclasses).  Services are in this map multiple times, once
239    * for each field of each entry it has.  The outer map is indexed by the
240    * first (highest) superclass that defines the field.  This means that a
241    * HashMap[] has null elements for fields defined by superclasses, but
242    * this is a small memory hit and is simpler than subtracting off base
243    * index values when accessing the arrays.
244    */
245   private final HashMap serviceByAttr = new HashMap(23);
246 
247   /**
248    * Map from EntryClass to ArrayList(SvcReg).  Services are in this map
249    * multiple times, once for each no-fields entry it has (no fields meaning
250    * none of the superclasses have fields either).  The map is indexed by
251    * the exact type of the entry.
252    */
253   private final HashMap serviceByEmptyAttr = new HashMap(11);
254 
255   /** All EntryClasses with non-zero numInstances */
256   private final ArrayList entryClasses = new ArrayList();
257 
258   /**
259    * Map from Long(eventID) to EventReg.  Every event registration is in
260    * this map under its eventID.
261    */
262   private final HashMap eventByID = new HashMap(11);
263 
264   /**
265    * Identity map from EventReg to EventReg, ordered by lease expiration.
266    * Every event registration is in this map.
267    */
268   private final TreeMap eventByTime = new TreeMap();
269 
270   /**
271    * Map from ServiceID to EventReg or EventReg[].  An event
272    * registration is in this map if its template matches on (at least)
273    * a specific serviceID.
274    */
275   private final HashMap subEventByService = new HashMap(11);
276 
277   /**
278    * Map from Long(eventID) to EventReg.  An event registration is in
279    * this map if its template matches on ANY_SERVICE_ID.
280    */
281   private final HashMap subEventByID = new HashMap(11);
282 
283   /** Generator for resource (e.g., registration, lease) Uuids */
284   private UuidGenerator resourceIdGenerator = new UuidGenerator();
285 
286   /** Generator for service IDs */
287   private UuidGenerator serviceIdGenerator = resourceIdGenerator;
288 
289   /** Event ID */
290   private long eventID = 0;
291 
292   /** Random number generator for use in lookup */
293   private final Random random = new Random();
294 
295   /** Preparer for received remote event listeners */
296   private ProxyPreparer listenerPreparer = new BasicProxyPreparer();
297 
298   /** Preparer for remote event listeners recovered from state log */
299   private ProxyPreparer recoveredListenerPreparer = listenerPreparer;
300 
301   /** Preparer for received lookup locators */
302   private ProxyPreparer locatorPreparer = listenerPreparer;
303 
304   /** Preparer for lookup locators recovered from state log */
305   private ProxyPreparer recoveredLocatorPreparer = listenerPreparer;
306 
307   /** ArrayList of pending EventTasks */
308   private final ArrayList newNotifies = new ArrayList();
309 
310   /** Current maximum service lease duration granted, in milliseconds. */
311   private long maxServiceLease;
312 
313   /** Current maximum event lease duration granted, in milliseconds. */
314   private long maxEventLease;
315 
316   /** Earliest expiration time of a SvcReg */
317   private long minSvcExpiration = Long.MAX_VALUE;
318 
319   /** Earliest expiration time of an EventReg */
320   private long minEventExpiration = Long.MAX_VALUE;
321 
322   /** Manager for discovering other lookup services */
323   private DiscoveryManagement discoer;
324 
325   /** Manager for joining other lookup services */
326   private JoinManager joiner;
327 
328   /** Task manager for sending events and discovery responses */
329   private TaskManager tasker;
330 
331   /** Service lease expiration thread */
332   private Thread serviceExpirer;
333 
334   /** Event lease expiration thread */
335   private Thread eventExpirer;
336 
337   /** Unicast discovery request packet receiving thread */
338   private UnicastThread unicaster;
339 
340   /** Multicast discovery request packet receiving thread */
341   private Thread multicaster;
342 
343   /** Multicast discovery announcement sending thread */
344   private Thread announcer;
345 
346   /** Snapshot-taking thread */
347   private Thread snapshotter;
348 
349   /** Concurrent object to control read and write access */
350   private final ReadersWriter concurrentObj = new ReadersWriter();
351 
352   /** Object for synchronizing with the service expire thread */
353   private final Object serviceNotifier = new Object();
354 
355   /** Object for synchronizing with the event expire thread */
356   private final Object eventNotifier = new Object();
357 
358   /** Object on which the snapshot-taking thread will synchronize */
359   private final Object snapshotNotifier = new Object();
360 
361   /** Canonical ServiceType for java.lang.Object */
362   private ServiceType objectServiceType;
363 
364   /** Log for recovering/storing state, or null if running as transient */
365   private ReliableLog log;
366 
367   /** Flag indicating whether system is in a state of recovery */
368   private boolean inRecovery;
369 
370   /** Flag indicating whether system state was recovered from a snapshot */
371   private boolean recoveredSnapshot = false;
372 
373   /** Current number of records in the Log File since the last snapshot */
374   private int logFileSize = 0;
375 
376   /** Log file must contain this many records before snapshot allowed */
377   private int persistenceSnapshotThreshold = 200;
378 
379   /** Weight factor applied to snapshotSize when deciding to take snapshot */
380   private float persistenceSnapshotWeight = 10;
381 
382   /** Minimum value for maxServiceLease. */
383   private long minMaxServiceLease = 1000 * 60 * 5;
384 
385   /** Minimum value for maxEventLease. */
386   private long minMaxEventLease = 1000 * 60 * 30;
387 
388   /** Minimum average time between lease renewals, in milliseconds. */
389   private long minRenewalInterval = 100;
390 
391   /** Port for unicast discovery */
392   private int unicastPort = 0;
393 
394   /** The groups we are a member of */
395   private volatile String[] memberGroups = { "" };
396 
397   /** The groups we should join */
398   private String[] lookupGroups = DiscoveryGroupManagement.NO_GROUPS;
399 
400   /** The locators of other lookups we should join */
401   private LookupLocator[] lookupLocators = {};
402 
403   /** The attributes to use when joining (including with myself) */
404   private Entry[] lookupAttrs;
405 
406   /** Interval to wait in between sending multicast announcements */
407   private long multicastAnnouncementInterval = 1000 * 60 * 2;
408 
409   /** Multicast announcement sequence number */
410   private volatile long announcementSeqNo = 0L;
411 
412   /** Network interfaces to use for multicast discovery */
413   private NetworkInterface[] multicastInterfaces;
414 
415   /** Flag indicating whether network interfaces were explicitly specified */
416   private boolean multicastInterfacesSpecified;
417 
418   /** Interval to wait in between retrying failed interfaces */
419   private int multicastInterfaceRetryInterval = 1000 * 60 * 5;
420 
421   /** Utility for participating in version 2 of discovery protocols */
422   private Discovery protocol2;
423 
424   /** Cached raw constraints associated with unicastDiscovery method*/
425   private InvocationConstraints rawUnicastDiscoveryConstraints;
426 
427   /** Constraints specified for incoming multicast requests */
428   private DiscoveryConstraints multicastRequestConstraints;
429 
430   /** Constraints specified for outgoing multicast announcements */
431   private DiscoveryConstraints multicastAnnouncementConstraints;
432 
433   /** Constraints specified for handling unicast discovery */
434   private DiscoveryConstraints unicastDiscoveryConstraints;
435 
436   /** Client subject checker to apply to incoming multicast requests */
437   private ClientSubjectChecker multicastRequestSubjectChecker;
438 
439   /** Maximum time to wait for calls to finish before forcing unexport */
440   private volatile long unexportTimeout = 1000 * 60 * 2;
441 
442   /** Time to wait between unexport attempts */
443   private volatile long unexportWait = 1000;
444 
445   /** Client subject checker to apply to unicast discovery attempts */
446   private ClientSubjectChecker unicastDiscoverySubjectChecker;
447 
448   /** Lock protecting startup and shutdown */
449   private final ReadyState ready = new ReadyState();
450 
451   /**
452    * Constructs RegistrarImpl based on a configuration obtained using the
453    * provided string arguments.  If activationID is non-null, the created
454    * RegistrarImpl runs as activatable; if persistent is true, it
455    * persists/recovers its state to/from disk.  A RegistrarImpl instance
456    * cannot be constructed as both activatable and non-persistent.  If
457    * lifeCycle is non-null, its unregister method is invoked during shutdown.
458    */
459   RegistrarImpl(String[] configArgs, final ActivationID activationID,
460       final boolean persistent, final LifeCycle lifeCycle) throws Exception
461   {
462     if (activationID != null && !persistent) {
463       throw new IllegalArgumentException();
464     }
465     try {
466       final Configuration config = ConfigurationProvider.getInstance(configArgs, getClass()
467           .getClassLoader());
468       loginContext = (LoginContext) config.getEntry(COMPONENT, "loginContext",
469           LoginContext.class, null);
470 
471       PrivilegedExceptionAction init = new PrivilegedExceptionAction() {
472         public Object run() throws Exception
473         {
474           init(config, activationID, persistent, lifeCycle);
475           return null;
476         }
477       };
478       if (loginContext != null) {
479         loginContext.login();
480         try {
481           Subject.doAsPrivileged(loginContext.getSubject(), init, null);
482         } catch (PrivilegedActionException e) {
483           throw e.getCause();
484         }
485       } else {
486         init.run();
487       }
488     } catch (Throwable t) {
489       logger.log(Level.SEVERE, "Reggie initialization failed", t);
490       if (t instanceof Exception) {
491         throw (Exception) t;
492       } else {
493         throw (Error) t;
494       }
495     }
496   }
497 
498   /** A service item registration record. */
499   private final static class SvcReg implements Comparable, Serializable
500   {
501 
502     private static final long serialVersionUID = 2L;
503 
504     /**
505      * The service item.
506      *
507      * @serial
508      */
509     public final Item item;
510 
511     /**
512      * The lease id.
513      *
514      * @serial
515      */
516     public final Uuid leaseID;
517 
518     /**
519      * The lease expiration time.
520      *
521      * @serial
522      */
523     public long leaseExpiration;
524 
525     /** Simple constructor */
526     public SvcReg(Item item, Uuid leaseID, long leaseExpiration)
527     {
528       this.item = item;
529       this.leaseID = leaseID;
530       this.leaseExpiration = leaseExpiration;
531     }
532 
533     /**
534      * Primary sort by leaseExpiration, secondary by leaseID.  The
535      * secondary sort is immaterial, except to ensure a total order
536      * (required by TreeMap).
537      */
538     public int compareTo(Object obj)
539     {
540       SvcReg reg = (SvcReg) obj;
541       if (this == reg)
542         return 0;
543       int i = compare(leaseExpiration, reg.leaseExpiration);
544       if (i != 0) {
545         return i;
546       }
547       i = compare(leaseID.getMostSignificantBits(), reg.leaseID.getMostSignificantBits());
548       if (i != 0) {
549         return i;
550       }
551       return compare(leaseID.getLeastSignificantBits(), reg.leaseID.getLeastSignificantBits());
552     }
553 
554     /**
555      * Compares long values, returning -1, 0, or 1 if l1 is less than,
556      * equal to or greater than l2, respectively.
557      */
558     private static int compare(long l1, long l2)
559     {
560       return (l1 < l2) ? -1 : ((l1 > l2) ? 1 : 0);
561     }
562   }
563 
564   /** An event registration record. */
565   private final static class EventReg implements Comparable, Serializable
566   {
567 
568     private static final long serialVersionUID = 2L;
569 
570     /**
571      * The event id.
572      * @serial
573      */
574     public final long eventID;
575 
576     /**
577      * The lease id.
578      * @serial
579      */
580     public final Uuid leaseID;
581 
582     /**
583      * The template to match.
584      * @serial
585      */
586     public final Template tmpl;
587 
588     /**
589      * The transitions.
590      *
591      * @serial
592      */
593     public final int transitions;
594 
595     /**
596      * The current sequence number.
597      *
598      * @serial
599      */
600     public long seqNo;
601 
602     /**
603      * The event listener.
604      */
605     public transient RemoteEventListener listener;
606 
607     /**
608      * The handback object.
609      *
610      * @serial
611      */
612     public final MarshalledObject handback;
613 
614     /**
615      * The lease expiration time.
616      *
617      * @serial
618      */
619     public long leaseExpiration;
620 
621     /** Simple constructor */
622     public EventReg(long eventID, Uuid leaseID, Template tmpl, int transitions,
623         RemoteEventListener listener, MarshalledObject handback, long leaseExpiration)
624     {
625       this.eventID = eventID;
626       this.leaseID = leaseID;
627       this.tmpl = tmpl;
628       this.transitions = transitions;
629       this.seqNo = 0;
630       this.listener = listener;
631       this.handback = handback;
632       this.leaseExpiration = leaseExpiration;
633     }
634 
635     /**
636      * Primary sort by leaseExpiration, secondary by eventID.  The
637      * secondary sort is immaterial, except to ensure a total order
638      * (required by TreeMap).
639      */
640     public int compareTo(Object obj)
641     {
642       EventReg reg = (EventReg) obj;
643       if (this == reg)
644         return 0;
645       if (leaseExpiration < reg.leaseExpiration
646           || (leaseExpiration == reg.leaseExpiration && eventID < reg.eventID))
647         return -1;
648       return 1;
649     }
650 
651     /**
652      * Prepares listener (if non-null) using the given proxy preparer.  If
653      * preparation fails, the listener field is set to null.
654      */
655     public void prepareListener(ProxyPreparer preparer)
656     {
657       if (listener != null) {
658         try {
659           listener = (RemoteEventListener) preparer.prepareProxy(listener);
660         } catch (Exception e) {
661           if (logger.isLoggable(Level.WARNING)) {
662             logThrow(Level.WARNING, getClass().getName(), "prepareListener",
663                 "failed to prepare event listener {0}", new Object[] { listener }, e);
664           }
665           listener = null;
666         }
667       }
668     }
669 
670     /**
671      * @serialData RemoteEventListener as a MarshalledInstance
672      */
673     private void writeObject(ObjectOutputStream stream) throws IOException
674     {
675       stream.defaultWriteObject();
676       stream.writeObject(new MarshalledInstance(listener));
677     }
678 
679     /**
680      * Unmarshals the event listener.
681      */
682     private void readObject(ObjectInputStream stream) throws IOException,
683         ClassNotFoundException
684     {
685       stream.defaultReadObject();
686       MarshalledInstance mi = (MarshalledInstance) stream.readObject();
687       try {
688         listener = (RemoteEventListener) mi.get(false);
689       } catch (Throwable e) {
690         if (e instanceof Error
691             && ThrowableConstants.retryable(e) == ThrowableConstants.BAD_OBJECT) {
692           throw (Error) e;
693         }
694         logger.log(Level.WARNING, "failed to recover event listener", e);
695       }
696     }
697   }
698 
699   /**
700    * Interface defining the method(s) that must be implemented by each of
701    * the concrete LogObj classes. This allows for the definition of
702    * object-dependent invocations of the appropriate implementation of
703    * the method(s) declared in this interface.
704    */
705   private static interface LogRecord extends Serializable
706   {
707     void apply(RegistrarImpl regImpl);
708   }
709 
710   /**
711    * LogObj class whose instances are recorded to the log file whenever
712    * a new service is registered.
713    * 
714    * @see RegistrarImpl.LocalLogHandler
715    */
716   private static class SvcRegisteredLogObj implements LogRecord
717   {
718 
719     private static final long serialVersionUID = 2L;
720 
721     /**
722      * The service registration.
723      *
724      * @serial
725      */
726     private SvcReg reg;
727 
728     /** Simple constructor */
729     public SvcRegisteredLogObj(SvcReg reg)
730     {
731       this.reg = reg;
732     }
733 
734     /**
735      * Modifies the state of the Registrar by registering the service
736      * stored in the reg object. Also needs to delete any existing
737      * service with the same serviceID; this can happen if a service
738      * re-registers while an existing registration is in effect, because
739      * we don't log a separate lease cancellation record for the existing
740      * registration in that case.
741      * 
742      * @see RegistrarImpl.LocalLogHandler#applyUpdate
743      */
744     public void apply(RegistrarImpl regImpl)
745     {
746       SvcReg oldReg = (SvcReg) regImpl.serviceByID.get(reg.item.serviceID);
747       if (oldReg != null)
748         regImpl.deleteService(oldReg, 0);
749       regImpl.addService(reg);
750     }
751   }
752 
753   /**
754    * LogObj class whose instances are recorded to the log file whenever
755    * new attributes are added to an existing service in the Registrar.
756    * 
757    * @see RegistrarImpl.LocalLogHandler
758    */
759   private static class AttrsAddedLogObj implements LogRecord
760   {
761 
762     private static final long serialVersionUID = 2L;
763 
764     /**
765      * The service id.
766      *
767      * @serial
768      */
769     private ServiceID serviceID;
770 
771     /**
772      * The lease id.
773      *
774      * @serial
775      */
776     private Uuid leaseID;
777 
778     /**
779      * The attributes added.
780      *
781      * @serial
782      */
783     private EntryRep[] attrSets;
784 
785     /** Simple constructor */
786     public AttrsAddedLogObj(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSets)
787     {
788       this.serviceID = serviceID;
789       this.leaseID = leaseID;
790       this.attrSets = attrSets;
791     }
792 
793     /**
794      * Modifies the state of the Registrar by adding to all of the
795      * services matching the template, the attributes stored in
796      * attributeSets.
797      * 
798      * @see RegistrarImpl.LocalLogHandler#applyUpdate
799      */
800     public void apply(RegistrarImpl regImpl)
801     {
802       try {
803         regImpl.addAttributesDo(serviceID, leaseID, attrSets);
804       } catch (UnknownLeaseException e) {
805         /* this exception should never occur when recovering  */
806         throw new AssertionError("an UnknownLeaseException should"
807             + " never occur during recovery");
808       }
809     }
810   }
811 
812   /**
813    * LogObj class whose instances are recorded to the log file whenever
814    * existing attributes of an existing service in the Registrar are
815    * modified.
816    * 
817    * @see RegistrarImpl.LocalLogHandler
818    */
819   private static class AttrsModifiedLogObj implements LogRecord
820   {
821 
822     private static final long serialVersionUID = 2L;
823 
824     /**
825      * The service id.
826      *
827      * @serial
828      */
829     private ServiceID serviceID;
830 
831     /**
832      * The lease id.
833      *
834      * @serial
835      */
836     private Uuid leaseID;
837 
838     /**
839      * The templates to match.
840      * @serial
841      */
842     private EntryRep[] attrSetTmpls;
843 
844     /**
845      * The new attributes.
846      *
847      * @serial
848      */
849     private EntryRep[] attrSets;
850 
851     /** Simple constructor */
852     public AttrsModifiedLogObj(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSetTmpls,
853         EntryRep[] attrSets)
854     {
855       this.serviceID = serviceID;
856       this.leaseID = leaseID;
857       this.attrSetTmpls = attrSetTmpls;
858       this.attrSets = attrSets;
859     }
860 
861     /**
862      * Modifies the state of the Registrar by modifying the attributes
863      * of the services that match the template with the attributes 
864      * stored in attributeSets.
865      * 
866      * @see RegistrarImpl.LocalLogHandler#applyUpdate
867      */
868     public void apply(RegistrarImpl regImpl)
869     {
870       try {
871         regImpl.modifyAttributesDo(serviceID, leaseID, attrSetTmpls, attrSets);
872       } catch (UnknownLeaseException e) {
873         /* this exception should never occur when recovering  */
874         throw new AssertionError("an UnknownLeaseException should"
875             + " never occur during recovery");
876       }
877     }
878   }
879 
880   /**
881    * LogObj class whose instances are recorded to the log file whenever
882    * new attributes are set on an existing service in the Registrar.
883    * 
884    * @see RegistrarImpl.LocalLogHandler
885    */
886   private static class AttrsSetLogObj implements LogRecord
887   {
888 
889     private static final long serialVersionUID = 2L;
890 
891     /**
892      * The service id.
893      *
894      * @serial
895      */
896     private ServiceID serviceID;
897 
898     /**
899      * The lease id.
900      *
901      * @serial
902      */
903     private Uuid leaseID;
904 
905     /**
906      * The new attributes.
907      *
908      * @serial
909      */
910     private EntryRep[] attrSets;
911 
912     /** Simple constructor */
913     public AttrsSetLogObj(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSets)
914     {
915       this.serviceID = serviceID;
916       this.leaseID = leaseID;
917       this.attrSets = attrSets;
918     }
919 
920     /**
921      * Modifies the state of the Registrar by replacing the attributes
922      * of the services matching the template with the attributes stored
923      * in attributeSets.
924      * 
925      * @see RegistrarImpl.LocalLogHandler#applyUpdate
926      */
927     public void apply(RegistrarImpl regImpl)
928     {
929       try {
930         regImpl.setAttributesDo(serviceID, leaseID, attrSets);
931       } catch (UnknownLeaseException e) {
932         /* this exception should never occur when recovering  */
933       }
934     }
935   }
936 
937   /**
938    * LogObj class whose instances are recorded to the log file whenever
939    * a new event is registered.
940    * 
941    * @see RegistrarImpl.LocalLogHandler
942    */
943   private static class EventRegisteredLogObj implements LogRecord
944   {
945 
946     private static final long serialVersionUID = 2L;
947 
948     /**
949      * The event registration.
950      *
951      * @serial
952      */
953     private EventReg eventReg;
954 
955     /** Simple constructor */
956     public EventRegisteredLogObj(EventReg eventReg)
957     {
958       this.eventReg = eventReg;
959     }
960 
961     /**
962      * Modifies the state of the Registrar by registering the event
963      * stored in the eventReg object; and by updating both the event
964      * sequence number and the event ID.
965      * 
966      * @see RegistrarImpl.LocalLogHandler#applyUpdate
967      */
968     public void apply(RegistrarImpl regImpl)
969     {
970       eventReg.prepareListener(regImpl.recoveredListenerPreparer);
971       eventReg.seqNo += Integer.MAX_VALUE;
972       regImpl.addEvent(eventReg);
973       regImpl.eventID++;
974     }
975   }
976 
977   /**
978    * LogObj class whose instances are recorded to the log file whenever
979    * a lease on an existing service in the Registrar is cancelled.
980    * 
981    * @see RegistrarImpl.LocalLogHandler
982    */
983   private static class ServiceLeaseCancelledLogObj implements LogRecord
984   {
985 
986     private static final long serialVersionUID = 2L;
987 
988     /**
989      * The service id.
990      *
991      * @serial
992      */
993     private ServiceID serviceID;
994 
995     /**
996      * The lease id.
997      *
998      * @serial
999      */
1000     private Uuid leaseID;
1001 
1002     /** Simple constructor */
1003     public ServiceLeaseCancelledLogObj(ServiceID serviceID, Uuid leaseID)
1004     {
1005       this.serviceID = serviceID;
1006       this.leaseID = leaseID;
1007     }
1008 
1009     /**
1010      * Modifies the state of the Registrar by cancelling the lease
1011      * having ID equal to the contents of the leaseID field; and
1012      * corresponding to the service with ID equal to the contents of
1013      * the serviceID field.
1014      * 
1015      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1016      */
1017     public void apply(RegistrarImpl regImpl)
1018     {
1019       try {
1020         regImpl.cancelServiceLeaseDo(serviceID, leaseID);
1021       } catch (UnknownLeaseException e) {
1022         /* this exception should never occur when recovering  */
1023       }
1024     }
1025   }
1026 
1027   /**
1028    * LogObj class whose instances are recorded to the log file whenever
1029    * a lease on an existing service in the Registrar is renewed.
1030    * 
1031    * @see RegistrarImpl.LocalLogHandler
1032    */
1033   private static class ServiceLeaseRenewedLogObj implements LogRecord
1034   {
1035 
1036     private static final long serialVersionUID = 2L;
1037 
1038     /**
1039      * The service id.
1040      *
1041      * @serial
1042      */
1043     private ServiceID serviceID;
1044 
1045     /**
1046      * The lease id.
1047      *
1048      * @serial
1049      */
1050     private Uuid leaseID;
1051 
1052     /**
1053      * The new lease expiration time.
1054      *
1055      * @serial
1056      */
1057     private long leaseExpTime;
1058 
1059     /** Simple constructor */
1060     public ServiceLeaseRenewedLogObj(ServiceID serviceID, Uuid leaseID, long leaseExpTime)
1061     {
1062       this.serviceID = serviceID;
1063       this.leaseID = leaseID;
1064       this.leaseExpTime = leaseExpTime;
1065     }
1066 
1067     /**
1068      * Modifies the state of the Registrar by renewing the lease
1069      * having ID equal to the contents of the leaseID field; and
1070      * corresponding to the service with ID equal to the contents
1071      * of the serviceID field.
1072      * 
1073      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1074      */
1075     public void apply(RegistrarImpl regImpl)
1076     {
1077       regImpl.renewServiceLeaseAbs(serviceID, leaseID, leaseExpTime);
1078     }
1079   }
1080 
1081   /**
1082    * LogObj class whose instances are recorded to the log file whenever
1083    * a lease on a registered event is cancelled.
1084    * 
1085    * @see RegistrarImpl.LocalLogHandler
1086    */
1087   private static class EventLeaseCancelledLogObj implements LogRecord
1088   {
1089 
1090     private static final long serialVersionUID = 2L;
1091 
1092     /**
1093      * The event id.
1094      *
1095      * @serial
1096      */
1097     private long eventID;
1098 
1099     /**
1100      * The lease id.
1101      *
1102      * @serial
1103      */
1104     private Uuid leaseID;
1105 
1106     /** Simple constructor */
1107     public EventLeaseCancelledLogObj(long eventID, Uuid leaseID)
1108     {
1109       this.eventID = eventID;
1110       this.leaseID = leaseID;
1111     }
1112 
1113     /**
1114      * Modifies the state of the Registrar by cancelling the lease
1115      * corresponding to the event with ID equal to the contents of
1116      * the eventID field.
1117      * 
1118      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1119      */
1120     public void apply(RegistrarImpl regImpl)
1121     {
1122       try {
1123         regImpl.cancelEventLeaseDo(eventID, leaseID);
1124       } catch (UnknownLeaseException e) {
1125         /* this exception should never occur when recovering */
1126       }
1127     }
1128   }
1129 
1130   /**
1131    * LogObj class whose instances are recorded to the log file whenever
1132    * a lease on a registered event is renewed.
1133    * 
1134    * @see RegistrarImpl.LocalLogHandler
1135    */
1136   private static class EventLeaseRenewedLogObj implements LogRecord
1137   {
1138 
1139     private static final long serialVersionUID = 2L;
1140 
1141     /**
1142      * The event id.
1143      *
1144      * @serial
1145      */
1146     private long eventID;
1147 
1148     /**
1149      * The lease id.
1150      *
1151      * @serial
1152      */
1153     private Uuid leaseID;
1154 
1155     /**
1156      * The new lease expiration time.
1157      *
1158      * @serial
1159      */
1160     private long leaseExpTime;
1161 
1162     /** Simple constructor */
1163     public EventLeaseRenewedLogObj(long eventID, Uuid leaseID, long leaseExpTime)
1164     {
1165       this.eventID = eventID;
1166       this.leaseID = leaseID;
1167       this.leaseExpTime = leaseExpTime;
1168     }
1169 
1170     /**
1171      * Modifies the state of the Registrar by renewing the lease
1172      * corresponding to the event with ID equal to the contents of
1173      * the eventID field.
1174      * 
1175      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1176      */
1177     public void apply(RegistrarImpl regImpl)
1178     {
1179       regImpl.renewEventLeaseAbs(eventID, leaseID, leaseExpTime);
1180     }
1181   }
1182 
1183   /**
1184    * LogObj class whose instances are recorded to the log file whenever
1185    * a leases in the Registrar is renewed via a LeaseMap.
1186    * 
1187    * @see RegistrarImpl.LocalLogHandler
1188    */
1189   private static class LeasesRenewedLogObj implements LogRecord
1190   {
1191 
1192     private static final long serialVersionUID = 2L;
1193 
1194     /**
1195      * The service and event ids.
1196      *
1197      * @serial
1198      */
1199     private Object[] regIDs;
1200 
1201     /**
1202      * The lease ids.
1203      *
1204      * @serial
1205      */
1206     private Uuid[] leaseIDs;
1207 
1208     /**
1209      * The new lease expiration times.
1210      *
1211      * @serial
1212      */
1213     private long[] leaseExpTimes;
1214 
1215     /** Simple constructor */
1216     public LeasesRenewedLogObj(Object[] regIDs, Uuid[] leaseIDs, long[] leaseExpTimes)
1217     {
1218       this.regIDs = regIDs;
1219       this.leaseIDs = leaseIDs;
1220       this.leaseExpTimes = leaseExpTimes;
1221     }
1222 
1223     /**
1224      * Modifies the state of the Registrar by renewing the specified
1225      * leases.
1226      * 
1227      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1228      */
1229     public void apply(RegistrarImpl regImpl)
1230     {
1231       regImpl.renewLeasesAbs(regIDs, leaseIDs, leaseExpTimes);
1232     }
1233   }
1234 
1235   /**
1236    * LogObj class whose instances are recorded to the log file whenever
1237    * lease are cancelled via a LeaseMap.
1238    * 
1239    * @see RegistrarImpl.LocalLogHandler
1240    */
1241   private static class LeasesCancelledLogObj implements LogRecord
1242   {
1243 
1244     private static final long serialVersionUID = 2L;
1245 
1246     /**
1247      * The service and event ids.
1248      *
1249      * @serial
1250      */
1251     private Object[] regIDs;
1252 
1253     /**
1254      * The lease ids.
1255      *
1256      * @serial
1257      */
1258     private Uuid[] leaseIDs;
1259 
1260     /** Simple constructor */
1261     public LeasesCancelledLogObj(Object[] regIDs, Uuid[] leaseIDs)
1262     {
1263       this.regIDs = regIDs;
1264       this.leaseIDs = leaseIDs;
1265     }
1266 
1267     /**
1268      * Modifies the state of the Registrar by cancelling the specified
1269      * leases.
1270      * 
1271      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1272      */
1273     public void apply(RegistrarImpl regImpl)
1274     {
1275       /* Exceptions can be returned, since we didn't weed out unknown
1276        * leases before logging, but we can just ignore them anyway.
1277        */
1278       regImpl.cancelLeasesDo(regIDs, leaseIDs);
1279     }
1280   }
1281 
1282   /**
1283    * LogObj class whose instances are recorded to the log file whenever
1284    * the Unicast Port Number is set to a new value.
1285    * <p>
1286    * Note: the apply() method of this class merely sets the private field
1287    *       unicastPort. This means that during a recovery, the unicaster
1288    *       thread will be created with this new port number ONLY IF that
1289    *       thread is created AFTER recovery is complete. Thus, it is 
1290    *       important that at re-initialization during a re-activation
1291    *       of the Registrar, the recovery() method is invoked before
1292    *       the unicaster thread is created.
1293    * 
1294    * @see RegistrarImpl.LocalLogHandler
1295    */
1296   private static class UnicastPortSetLogObj implements LogRecord
1297   {
1298 
1299     private static final long serialVersionUID = 2L;
1300 
1301     /**
1302      * The new port number.
1303      *
1304      * @serial
1305      */
1306     private int newPort;
1307 
1308     /** Simple constructor */
1309     public UnicastPortSetLogObj(int newPort)
1310     {
1311       this.newPort = newPort;
1312     }
1313 
1314     /**
1315      * Modifies the state of the Registrar by setting the value of the
1316      * private unicastPort field to the value of the newPort field.
1317      * 
1318      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1319      */
1320     public void apply(RegistrarImpl regImpl)
1321     {
1322       regImpl.unicastPort = newPort;
1323     }
1324   }
1325 
1326   /**
1327    * LogObj class whose instances are recorded to the log file whenever
1328    * the set of groups to join is changed.
1329    * 
1330    * @see RegistrarImpl.LocalLogHandler
1331    */
1332   private static class LookupGroupsChangedLogObj implements LogRecord
1333   {
1334 
1335     private static final long serialVersionUID = 2L;
1336 
1337     /**
1338      * The new groups to join.
1339      *
1340      * @serial
1341      */
1342     private String[] groups;
1343 
1344     /** Simple constructor */
1345     public LookupGroupsChangedLogObj(String[] groups)
1346     {
1347       this.groups = groups;
1348     }
1349 
1350     /**
1351      * Modifies the state of the Registrar by setting the private
1352      * field lookupGroups to the reference to the groups field.
1353      * 
1354      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1355      */
1356     public void apply(RegistrarImpl regImpl)
1357     {
1358       regImpl.lookupGroups = groups;
1359     }
1360   }
1361 
1362   /**
1363    * LogObj class whose instances are recorded to the log file whenever
1364    * the set of locators of lookup services to join is changed.
1365    * 
1366    * @see RegistrarImpl.LocalLogHandler
1367    */
1368   private static class LookupLocatorsChangedLogObj implements LogRecord
1369   {
1370 
1371     private static final long serialVersionUID = 2L;
1372 
1373     /**
1374      * The new locators to join.
1375      */
1376     private transient LookupLocator[] locators;
1377 
1378     /** Simple constructor */
1379     public LookupLocatorsChangedLogObj(LookupLocator[] locators)
1380     {
1381       this.locators = locators;
1382     }
1383 
1384     /**
1385      * Modifies the state of the Registrar by setting the private
1386      * field lookupLocators to the reference to the locators field.
1387      * 
1388      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1389      */
1390     public void apply(RegistrarImpl regImpl)
1391     {
1392       try {
1393         regImpl.lookupLocators = prepareLocators(locators, regImpl.recoveredLocatorPreparer,
1394             true);
1395       } catch (RemoteException e) {
1396         throw new AssertionError(e);
1397       }
1398     }
1399 
1400     /**
1401      * Writes locators as a null-terminated list of MarshalledInstances.
1402      */
1403     private void writeObject(ObjectOutputStream stream) throws IOException
1404     {
1405       stream.defaultWriteObject();
1406       marshalLocators(locators, stream);
1407     }
1408 
1409     /**
1410      * Reads in null-terminated list of MarshalledInstances, from which
1411      * locators are unmarshalled.
1412      */
1413     private void readObject(ObjectInputStream stream) throws IOException,
1414         ClassNotFoundException
1415     {
1416       stream.defaultReadObject();
1417       locators = unmarshalLocators(stream);
1418     }
1419   }
1420 
1421   /**
1422    * LogObj class whose instances are recorded to the log file whenever
1423    * the memberGroups array is set to reference a new array of strings.
1424    * 
1425    * @see RegistrarImpl.LocalLogHandler
1426    */
1427   private static class MemberGroupsChangedLogObj implements LogRecord
1428   {
1429 
1430     private static final long serialVersionUID = 2L;
1431 
1432     /**
1433      * The new groups to be a member of.
1434      *
1435      * @serial
1436      */
1437     private String[] groups;
1438 
1439     /** Simple constructor */
1440     public MemberGroupsChangedLogObj(String[] groups)
1441     {
1442       this.groups = groups;
1443     }
1444 
1445     /**
1446      * Modifies the state of the Registrar by setting the private
1447      * memberGroups field to the reference to the groups field.
1448      * 
1449      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1450      */
1451     public void apply(RegistrarImpl regImpl)
1452     {
1453       regImpl.memberGroups = groups;
1454     }
1455   }
1456 
1457   /**
1458    * LogObj class whose instances are recorded to the log file whenever
1459    * the attributes for the lookup service are changed.
1460    *
1461    * @see RegistrarImpl.LocalLogHandler
1462    */
1463   private static class LookupAttributesChangedLogObj implements LogRecord
1464   {
1465 
1466     private static final long serialVersionUID = 1L;
1467 
1468     /**
1469      * The new lookup service attributes.
1470      */
1471     private transient Entry[] attrs;
1472 
1473     /** Simple constructor */
1474     public LookupAttributesChangedLogObj(Entry[] attrs)
1475     {
1476       this.attrs = attrs;
1477     }
1478 
1479     /**
1480      * Modifies the state of the Registrar by setting the private
1481      * field lookupAttrs to the reference to the attrs field.
1482      *
1483      * @see RegistrarImpl.LocalLogHandler#applyUpdate
1484      */
1485     public void apply(RegistrarImpl regImpl)
1486     {
1487       regImpl.lookupAttrs = attrs;
1488     }
1489 
1490     /**
1491      * Writes attributes as a null-terminated list of MarshalledInstances.
1492      */
1493     private void writeObject(ObjectOutputStream stream) throws IOException
1494     {
1495       stream.defaultWriteObject();
1496       marshalAttributes(attrs, stream);
1497     }
1498 
1499     /**
1500      * Reads in null-terminated list of MarshalledInstances, from which
1501      * attributes are unmarshalled.
1502      */
1503     private void readObject(ObjectInputStream stream) throws IOException,
1504         ClassNotFoundException
1505     {
1506       stream.defaultReadObject();
1507       attrs = unmarshalAttributes(stream);
1508     }
1509   }
1510 
1511   /**
1512    * Handler class for the persistent storage facility.
1513    * <p>
1514    * At any point during processing in a persistent Registrar instance, there
1515    * will exist both a 'snapshot' of the Registrar's state and a set of
1516    * records detailing each significant change that has occurred to the state
1517    * since the snapshot was taken. The snapshot information and the
1518    * incremental change information will be stored in separate files called,
1519    * respectively, the snapshot file and the log file. Together, these files
1520    * are used to recover the state of the Registrar when it is restarted or
1521    * reactivated (for example, after a crash or network outage).
1522    * <p>
1523    * This class contains the methods that are used to record and recover
1524    * the snapshot of the Registrar's state; as well as the method used to
1525    * apply the state changes that were recorded in the log file.
1526    * <p>
1527    * When the ReliableLog class is instantiated, a new instance of this
1528    * class is passed to its constructor so that the methods of this
1529    * class may be invoked by the methods defined in the ReliableLog.
1530    * Because this class extends the LogHandler class associated with
1531    * the ReliableLog class, this class must provide implementations of
1532    * the abstract methods declared in the LogHandler. Also, some of the
1533    * methods defined in this class override the methods of the LogHandler
1534    * in order to customize the handling of snapshot creation and
1535    * retrieval.
1536    * <p>
1537    * Each significant change to the persistent Registrar's state is written
1538    * to the log file as an individual record (when addLogRecord() is
1539    * invoked).  After the number of records logged exceeds a pre-defined
1540    * threshold, a snapshot of the state is recorded by invoking -- through
1541    * the ReliableLog and its LogHandler -- the snapshot() method defined in
1542    * this class. After the snapshot is taken, the log file is cleared and the
1543    * incremental log process starts over.
1544    * <p>
1545    * The contents of the snapshot file reflect the DATA contained in
1546    * the fields making up the current state of the Registrar. That data
1547    * represents many changes -- over time -- to the Registrar's state.
1548    * On the other hand, each record written to the log file is an object
1549    * that reflects both the data used and the ACTIONS taken to make one
1550    * change to the Registrar's state at a particular point in time.
1551    * <p>
1552    * The data written to the snapshot file is shown below:
1553    * <ul>
1554    * <li> our service ID
1555    * <li> current event ID
1556    * <li> current values of administrable parameters
1557    * <li> contents of the container holding the current registered services
1558    * <li> null (termination 'marker' for the set of registered services)
1559    * <li> contents of the container holding the current registered events
1560    * <li> null (termination 'marker' for the set of registered events)
1561    * </ul>
1562    * The type of state changes that will generate a new record in the log
1563    * file are:
1564    * <ul>
1565    * <li> a new service was registered
1566    * <li> a new event was registered
1567    * <li> new attributes were added to an existing service
1568    * <li> existing attributes of a service were modified
1569    * <li> a service lease was cancelled
1570    * <li> a service lease was renewed
1571    * <li> an event lease was cancelled
1572    * <li> an event lease was renewed
1573    * <li> an administrable parameter was changed
1574    * </ul>
1575    * During recovery, the state of the Registrar at the time of a crash
1576    * or outage is re-constructed by first retrieving the 'base' state from
1577    * the snapshot file; and then modifying that base state according to
1578    * the records retrieved from the log file. The reconstruction of the
1579    * base state is achieved by invoking the recover() method defined in
1580    * this class. The modifications recorded in the log file are then
1581    * applied to the base state by invoking the applyUpdate() method
1582    * defined in this class. Both recover() and applyUpdate() are invoked
1583    * through the ReliableLog and its associated LogHandler.
1584    * <p>
1585    * NOTE: The following lines must be added to the Registrar's policy file
1586    * <pre>
1587    *     permission java.io.FilePermission "dirname",   "read,write,delete";
1588    *     permission java.io.FilePermission "dirname/-", "read,write,delete";
1589    * </pre>
1590    *     where 'dirname' is the name of the directory path (relative or
1591    *     absolute) where the snapshot and log file will be maintained.
1592    */
1593   private class LocalLogHandler extends LogHandler
1594   {
1595 
1596     /** Simple constructor */
1597     public LocalLogHandler()
1598     {
1599     }
1600 
1601     /* Overrides snapshot() defined in ReliableLog's LogHandler class. */
1602     public void snapshot(OutputStream out) throws IOException
1603     {
1604       takeSnapshot(out);
1605     }
1606 
1607     /* Overrides recover() defined in ReliableLog's LogHandler class. */
1608     public void recover(InputStream in) throws IOException, ClassNotFoundException
1609     {
1610       recoverSnapshot(in);
1611     }
1612 
1613     /**
1614      * Required method implementing the abstract applyUpdate()
1615      * defined in ReliableLog's associated LogHandler class.
1616      * <p>
1617      * During state recovery, the recover() method defined in the
1618      * ReliableLog class is invoked. That method invokes the method
1619      * recoverUpdates() which invokes the method readUpdates(). Both
1620      * of those methods are defined in ReliableLog. The method
1621      * readUpdates() retrieves a record from the log file and then
1622      * invokes this method.
1623      * <p>
1624      * This method invokes the version of the method apply() that
1625      * corresponds to the particular type of 'log record' object
1626      * that is input as the first argument. The log record object and its
1627      * corresponding apply() method are defined in one of the so-called
1628      * LogObj classes. Any instance of one the LogObj classes is an
1629      * implementation of the LogRecord interface. The particular
1630      * implementation that is input to this method is dependent on the
1631      * type of record that was originally logged. The apply() method
1632      * will then modify the state of the Registrar in a way dictated
1633      * by the type of record that was retrieved.
1634      */
1635     public void applyUpdate(Object logRecObj)
1636     {
1637       ((LogRecord) logRecObj).apply(RegistrarImpl.this);
1638     }
1639   }
1640 
1641   /** Base class for iterating over all Items that match a Template. */
1642   private abstract class ItemIter
1643   {
1644     /** Current time */
1645     public final long now = System.currentTimeMillis();
1646 
1647     /** True means duplicate items are possible */
1648     public boolean dupsPossible = false;
1649 
1650     /** Template to match */
1651     protected final Template tmpl;
1652 
1653     /** Next item to return */
1654     protected SvcReg reg;
1655 
1656     /** Subclass constructors must initialize reg */
1657     protected ItemIter(Template tmpl)
1658     {
1659       this.tmpl = tmpl;
1660     }
1661 
1662     /** Returns true if the iteration has more elements. */
1663     public boolean hasNext()
1664     {
1665       return reg != null;
1666     }
1667 
1668     /** Returns the next element in the iteration as an Item. */
1669     public Item next()
1670     {
1671       if (reg == null)
1672         throw new NoSuchElementException();
1673       Item item = reg.item;
1674       step();
1675       return item;
1676     }
1677 
1678     /** Returns the next element in the iteration as a SvcReg. */
1679     public SvcReg nextReg()
1680     {
1681       if (reg == null)
1682         throw new NoSuchElementException();
1683       SvcReg cur = reg;
1684       step();
1685       return cur;
1686     }
1687 
1688     /** Set reg to the next matching element, or null if none */
1689     protected abstract void step();
1690   }
1691 
1692   /** Iterate over all Items. */
1693   private class AllItemIter extends ItemIter
1694   {
1695     /** Iterator over serviceByID */
1696     private final Iterator iter;
1697 
1698     /** Assumes the empty template */
1699     public AllItemIter()
1700     {
1701       super(null);
1702       iter = serviceByID.values().iterator();
1703       step();
1704     }
1705 
1706     /** Set reg to the next matching element, or null if none */
1707     protected void step()
1708     {
1709       while (iter.hasNext()) {
1710         reg = (SvcReg) iter.next();
1711         if (reg.leaseExpiration > now)
1712           return;
1713       }
1714       reg = null;
1715     }
1716   }
1717 
1718   /** Iterates over all services that match template's service types */
1719   private class SvcIterator extends ItemIter
1720   {
1721     /** Iterator for list of matching services. */
1722     private final Iterator services;
1723 
1724     /**
1725      * tmpl.serviceID == null and
1726      * tmpl.serviceTypes is not empty
1727      */
1728     public SvcIterator(Template tmpl)
1729     {
1730       super(tmpl);
1731       Map map = (Map) serviceByTypeName.get(tmpl.serviceTypes[0].getName());
1732       services = map != null ? map.values().iterator() : Collections.EMPTY_LIST.iterator();
1733       step();
1734     }
1735 
1736     /** Set reg to the next matching element, or null if none. */
1737     protected void step()
1738     {
1739       if (tmpl.serviceTypes.length > 1) {
1740         while (services.hasNext()) {
1741           reg = (SvcReg) services.next();
1742           if (reg.leaseExpiration > now && matchType(tmpl.serviceTypes, reg.item.serviceType)
1743               && matchAttributes(tmpl, reg.item))
1744             return;
1745         }
1746       } else {
1747         while (services.hasNext()) {
1748           reg = (SvcReg) services.next();
1749           if (reg.leaseExpiration > now && matchAttributes(tmpl, reg.item))
1750             return;
1751         }
1752       }
1753       reg = null;
1754     }
1755   }
1756 
1757   /** Iterate over all matching Items by attribute value. */
1758   private class AttrItemIter extends ItemIter
1759   {
1760     /** SvcRegs obtained from serviceByAttr for chosen attr */
1761     protected ArrayList svcs;
1762 
1763     /** Current index into svcs */
1764     protected int svcidx;
1765 
1766     /**
1767      * tmpl.serviceID == null and
1768      * tmpl.serviceTypes is empty and
1769      * tmpl.attributeSetTemplates[setidx].fields[fldidx] != null
1770      */
1771     public AttrItemIter(Template tmpl, int setidx, int fldidx)
1772     {
1773       super(tmpl);
1774       EntryRep set = tmpl.attributeSetTemplates[setidx];
1775       HashMap[] attrMaps = (HashMap[]) serviceByAttr.get(getDefiningClass(set.eclass, fldidx));
1776       if (attrMaps != null && attrMaps[fldidx] != null) {
1777         svcs = (ArrayList) attrMaps[fldidx].get(set.fields[fldidx]);
1778         if (svcs != null) {
1779           svcidx = svcs.size();
1780           step();
1781         }
1782       }
1783     }
1784 
1785     /** Simple constructor */
1786     protected AttrItemIter(Template tmpl)
1787     {
1788       super(tmpl);
1789     }
1790 
1791     /** Set reg to the next matching element, or null if none. */
1792     protected void step()
1793     {
1794       while (--svcidx >= 0) {
1795         reg = (SvcReg) svcs.get(svcidx);
1796         if (reg.leaseExpiration > now && matchAttributes(tmpl, reg.item))
1797           return;
1798       }
1799       reg = null;
1800     }
1801   }
1802 
1803   /** Iterate over all matching Items by no-fields entry class. */
1804   private class EmptyAttrItemIter extends AttrItemIter
1805   {
1806 
1807     /**
1808      * tmpl.serviceID == null and
1809      * tmpl.serviceTypes is empty and
1810      * eclass has no fields
1811      */
1812     public EmptyAttrItemIter(Template tmpl, EntryClass eclass)
1813     {
1814       super(tmpl);
1815       svcs = (ArrayList) serviceByEmptyAttr.get(eclass);
1816       if (svcs != null) {
1817         svcidx = svcs.size();
1818         step();
1819       }
1820     }
1821   }
1822 
1823   /** Iterate over all matching Items by entry class, dups possible. */
1824   private class ClassItemIter extends ItemIter
1825   {
1826     /** Entry class to match on */
1827     private final EntryClass eclass;
1828 
1829     /** Current index into entryClasses */
1830     private int classidx;
1831 
1832     /** Values iterator for current HashMap */
1833     private Iterator iter;
1834 
1835     /** SvcRegs obtained from iter or serviceByEmptyAttr */
1836     private ArrayList svcs;
1837 
1838     /** Current index into svcs */
1839     private int svcidx = 0;
1840 
1841     /**
1842      * tmpl.serviceID == null and
1843      * tmpl.serviceTypes is empty and
1844      * tmpl.attributeSetTemplates is non-empty
1845      */
1846     public ClassItemIter(Template tmpl)
1847     {
1848       super(tmpl);
1849       dupsPossible = true;
1850       eclass = tmpl.attributeSetTemplates[0].eclass;
1851       classidx = entryClasses.size();
1852       step();
1853     }
1854 
1855     /** Set reg to the next matching element, or null if none */
1856     protected void step()
1857     {
1858       do {
1859         while (--svcidx >= 0) {
1860           reg = (SvcReg) svcs.get(svcidx);
1861           if (reg.leaseExpiration > now && matchAttributes(tmpl, reg.item))
1862             return;
1863         }
1864       } while (stepValue());
1865       reg = null;
1866     }
1867 
1868     /**
1869      * Step to the next HashMap value, if any, reset svcs and svcidx,
1870      * and return false if everything exhausted.
1871      */
1872     private boolean stepValue()
1873     {
1874       while (true) {
1875         if (iter != null && iter.hasNext()) {
1876           svcs = (ArrayList) iter.next();
1877           svcidx = svcs.size();
1878           return true;
1879         }
1880         if (!stepClass())
1881           return false;
1882         if (iter == null)
1883           return true;
1884       }
1885     }
1886 
1887     /**
1888      * Step to the next matching entry class, if any, reset iter
1889      * using the HashMap for the last field of the class (and reset
1890      * (svcs and svcidx if the entry class has no fields), and
1891      * return false if everything exhausted.
1892      */
1893     private boolean stepClass()
1894     {
1895       while (--classidx >= 0) {
1896         EntryClass cand = (EntryClass) entryClasses.get(classidx);
1897         if (!eclass.isAssignableFrom(cand))
1898           continue;
1899         if (cand.getNumFields() > 0) {
1900           cand = getDefiningClass(cand, cand.getNumFields() - 1);
1901           HashMap[] attrMaps = (HashMap[]) serviceByAttr.get(cand);
1902           iter = attrMaps[attrMaps.length - 1].values().iterator();
1903         } else {
1904           iter = null;
1905           svcs = (ArrayList) serviceByEmptyAttr.get(cand);
1906           svcidx = svcs.size();
1907         }
1908         return true;
1909       }
1910       return false;
1911     }
1912   }
1913 
1914   /** Iterate over a singleton matching Item by serviceID. */
1915   private class IDItemIter extends ItemIter
1916   {
1917 
1918     /** tmpl.serviceID != null */
1919     public IDItemIter(Template tmpl)
1920     {
1921       super(tmpl);
1922       reg = (SvcReg) serviceByID.get(tmpl.serviceID);
1923       if (reg != null && (reg.leaseExpiration <= now || !matchItem(tmpl, reg.item)))
1924         reg = null;
1925     }
1926 
1927     /** Set reg to null */
1928     protected void step()
1929     {
1930       reg = null;
1931     }
1932   }
1933 
1934   /** An event to be sent, and the listener to send it to. */
1935   private final class EventTask implements TaskManager.Task
1936   {
1937 
1938     /** The event registration */
1939     public final EventReg reg;
1940 
1941     /** The sequence number of this event */
1942     public final long seqNo;
1943 
1944     /** The service id */
1945     public final ServiceID sid;
1946 
1947     /** The new state of the item, or null if deleted */
1948     public final Item item;
1949 
1950     /** The transition that fired */
1951     public final int transition;
1952 
1953     /** Simple constructor, except increments reg.seqNo. */
1954     public EventTask(EventReg reg, ServiceID sid, Item item, int transition)
1955     {
1956       this.reg = reg;
1957       seqNo = ++reg.seqNo;
1958       this.sid = sid;
1959       this.item = item;
1960       this.transition = transition;
1961     }
1962 
1963     /** Send the event */
1964     public void run()
1965     {
1966       if (logger.isLoggable(Level.FINE)) {
1967         logger.log(Level.FINE, "notifying listener {0} of event {1}", new Object[] {
1968             reg.listener, new Long(reg.eventID) });
1969       }
1970       try {
1971         reg.listener.notify(new RegistrarEvent(proxy, reg.eventID, seqNo, reg.handback, sid,
1972             transition, item));
1973       } catch (Throwable e) {
1974         switch (ThrowableConstants.retryable(e)) {
1975           case ThrowableConstants.BAD_OBJECT:
1976             if (e instanceof Error) {
1977               logger.log(Levels.HANDLED, "exception sending event", e);
1978               throw (Error) e;
1979             }
1980           case ThrowableConstants.BAD_INVOCATION:
1981           case ThrowableConstants.UNCATEGORIZED:
1982             /* If the listener throws UnknownEvent or some other
1983              * definite exception, we can cancel the lease.
1984              */
1985             logger.log(Level.INFO, "exception sending event", e);
1986             try {
1987               cancelEventLease(reg.eventID, reg.leaseID);
1988             } catch (UnknownLeaseException ee) {
1989               logger.log(Levels.HANDLED, "exception canceling event lease", e);
1990             } catch (RemoteException ee) {
1991               logger.log(Levels.HANDLED, "The server has been shutdown", e);
1992             }
1993         }
1994       }
1995     }
1996 
1997     /** Keep events going to the same listener ordered. */
1998     public boolean runAfter(List tasks, int size)
1999     {
2000       for (int i = size; --i >= 0;) {
2001         Object obj = tasks.get(i);
2002         if (obj instanceof EventTask && reg.listener.equals(((EventTask) obj).reg.listener))
2003           return true;
2004       }
2005       return false;
2006     }
2007   }
2008 
2009   /** Task for decoding multicast request packets. */
2010   private final class DecodeRequestTask implements TaskManager.Task
2011   {
2012 
2013     /** The multicast packet to decode */
2014     private final DatagramPacket datagram;
2015 
2016     /** The decoder for parsing the packet */
2017     private final Discovery decoder;
2018 
2019     public DecodeRequestTask(DatagramPacket datagram, Discovery decoder)
2020     {
2021       this.datagram = datagram;
2022       this.decoder = decoder;
2023     }
2024 
2025     /**
2026      * Decodes this task's multicast request packet, spawning an
2027      * AddressTask if the packet satisfies the configured constraints,
2028      * matches this registrar's groups, and does not already contain this
2029      * registrar's service ID in its list of known registrars.  This method
2030      * assumes that the protocol version of the request has already been
2031      * checked.
2032      */
2033     public void run()
2034     {
2035       MulticastRequest req;
2036       try {
2037         req = decoder.decodeMulticastRequest(datagram, multicastRequestConstraints
2038             .getUnfulfilledConstraints(), multicastRequestSubjectChecker, true);
2039       } catch (Exception e) {
2040         if (!(e instanceof InterruptedIOException) && logger.isLoggable(Levels.HANDLED)) {
2041           logThrow(Levels.HANDLED, getClass().getName(), "run",
2042               "exception decoding multicast request from {0}:{1}", new Object[] {
2043                   datagram.getAddress(), new Integer(datagram.getPort()) }, e);
2044         }
2045         return;
2046       }
2047       String[] groups = req.getGroups();
2048       if ((groups.length == 0 || overlap(memberGroups, groups))
2049           && indexOf(req.getServiceIDs(), myServiceID) < 0) {
2050         try {
2051           req.checkConstraints();
2052         } catch (Exception e) {
2053           if (!(e instanceof InterruptedIOException) && logger.isLoggable(Levels.HANDLED)) {
2054             logThrow(Levels.HANDLED, getClass().getName(), "run",
2055                 "exception decoding multicast request from {0}:{1}", new Object[] {
2056                     datagram.getAddress(), new Integer(datagram.getPort()) }, e);
2057           }
2058           return;
2059         }
2060         tasker.addIfNew(new AddressTask(req.getHost(), req.getPort()));
2061       }
2062     }
2063 
2064     /** No ordering */
2065     public boolean runAfter(List tasks, int size)
2066     {
2067       return false;
2068     }
2069   }
2070 
2071   /** Address for unicast discovery response. */
2072   private final class AddressTask implements TaskManager.Task
2073   {
2074 
2075     /** The address */
2076     public final String host;
2077 
2078     /** The port */
2079     public final int port;
2080 
2081     /** Simple constructor */
2082     public AddressTask(String host, int port)
2083     {
2084       this.host = host;
2085       this.port = port;
2086     }
2087 
2088     public int hashCode()
2089     {
2090       return host.hashCode();
2091     }
2092 
2093     /** Two tasks are equal if they have the same address and port */
2094     public boolean equals(Object obj)
2095     {
2096       if (!(obj instanceof AddressTask))
2097         return false;
2098       AddressTask ua = (AddressTask) obj;
2099       return host.equals(ua.host) && port == ua.port;
2100     }
2101 
2102     /** Connect and then process a unicast discovery request */
2103     public void run()
2104     {
2105       InetAddress[] addr = new InetAddress[] {};
2106       try {
2107         try {
2108           addr = InetAddress.getAllByName(host);
2109           if (addr == null)
2110             addr = new InetAddress[] {};
2111         } catch (UnknownHostException e) {
2112           if (logger.isLoggable(Level.INFO)) {
2113             logThrow(Level.INFO, getClass().getName(), "run", "failed to resolve host {0};"
2114                 + " connection will still be attempted", new Object[] { host }, e);
2115           }
2116         }
2117         long deadline = DiscoveryConstraints.process(rawUnicastDiscoveryConstraints)
2118             .getConnectionDeadline(Long.MAX_VALUE);
2119         long now = System.currentTimeMillis();
2120         if (deadline <= now)
2121           throw new SocketTimeoutException("timeout expired before" + " connection attempt");
2122         long timeLeft = deadline - now;
2123         int timeout = timeLeft >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) timeLeft;
2124         // attempt connection even if host name was not resolved
2125         if (addr.length == 0) {
2126           attemptResponse(new InetSocketAddress(host, port), timeout);
2127           return;
2128         }
2129         for (int i = 0; i < addr.length; i++) {
2130           try {
2131             attemptResponse(new InetSocketAddress(addr[i], port), timeout);
2132             return;
2133           } catch (Exception e) {
2134             if (logger.isLoggable(Levels.HANDLED)) {
2135               logThrow(Levels.HANDLED, getClass().getName(), "run",
2136                   "exception responding to {0}:{1}",
2137                   new Object[] { addr[i], new Integer(port) }, e);
2138             }
2139           }
2140           timeLeft = deadline - System.currentTimeMillis();
2141           timeout = timeLeft >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) timeLeft;
2142           if (timeLeft <= 0)
2143             throw new SocketTimeoutException("timeout expired" + " before successful response");
2144         }
2145       } catch (Exception e) {
2146         if (logger.isLoggable(Level.INFO)) {
2147           logThrow(Level.INFO, getClass().getName(), "run",
2148               "failed to respond to {0} on port {1}", new Object[] { Arrays.asList(addr),
2149                   new Integer(port) }, e);
2150         }
2151       }
2152     }
2153 
2154     /** No ordering */
2155     public boolean runAfter(List tasks, int size)
2156     {
2157       return false;
2158     }
2159 
2160     /** attempt a connection to multicast request client */
2161     private void attemptResponse(InetSocketAddress addr, int timeout) throws Exception
2162     {
2163       Socket s = new Socket();
2164       try {
2165         s.connect(addr, timeout);
2166         respond(s);
2167       } finally {
2168         try {
2169           s.close();
2170         } catch (IOException e) {
2171           logger.log(Levels.HANDLED, "exception closing socket", e);
2172         }
2173       }
2174     }
2175   }
2176 
2177   /** Socket for unicast discovery response. */
2178   private final class SocketTask implements TaskManager.Task
2179   {
2180 
2181     /** The socket */
2182     public final Socket socket;
2183 
2184     /** Simple constructor */
2185     public SocketTask(Socket socket)
2186     {
2187       this.socket = socket;
2188     }
2189 
2190     /** Process a unicast discovery request */
2191     public void run()
2192     {
2193       try {
2194         respond(socket);
2195       } catch (Exception e) {
2196         if (logger.isLoggable(Levels.HANDLED)) {
2197           logThrow(Levels.HANDLED, getClass().getName(), "run",
2198               "exception handling unicast discovery from {0}:{1}", new Object[] {
2199                   socket.getInetAddress(), new Integer(socket.getPort()) }, e);
2200         }
2201       }
2202     }
2203 
2204     /** No ordering */
2205     public boolean runAfter(List tasks, int size)
2206     {
2207       return false;
2208     }
2209   }
2210 
2211   /** Service lease expiration thread code */
2212   private class ServiceExpireThread extends InterruptedStatusThread
2213   {
2214 
2215     /** Create a daemon thread */
2216     public ServiceExpireThread()
2217     {
2218       super("service expire");
2219       setDaemon(true);
2220     }
2221 
2222     public void run()
2223     {
2224       try {
2225         concurrentObj.writeLock();
2226       } catch (ConcurrentLockException e) {
2227         return;
2228       }
2229       try {
2230         while (!hasBeenInterrupted()) {
2231           long now = System.currentTimeMillis();
2232           while (true) {
2233             SvcReg reg = (SvcReg) serviceByTime.firstKey();
2234             minSvcExpiration = reg.leaseExpiration;
2235             if (minSvcExpiration > now)
2236               break;
2237             deleteService(reg, now);
2238             addLogRecord(new ServiceLeaseCancelledLogObj(reg.item.serviceID, reg.leaseID));
2239             if (logger.isLoggable(Level.FINE)) {
2240               logger.log(Level.FINE, "expired service registration {0}",
2241                   new Object[] { reg.item.serviceID });
2242             }
2243           }
2244           queueEvents();
2245           try {
2246             concurrentObj.writerWait(serviceNotifier, minSvcExpiration - now);
2247           } catch (ConcurrentLockException e) {
2248             return;
2249           }
2250         }
2251       } finally {
2252         concurrentObj.writeUnlock();
2253       }
2254     }
2255   }
2256 
2257   /** Event lease expiration thread code */
2258   private class EventExpireThread extends InterruptedStatusThread
2259   {
2260 
2261     /** Create a daemon thread */
2262     public EventExpireThread()
2263     {
2264       super("event expire");
2265       setDaemon(true);
2266     }
2267 
2268     public void run()
2269     {
2270       try {
2271         concurrentObj.writeLock();
2272       } catch (ConcurrentLockException e) {
2273         return;
2274       }
2275       try {
2276         while (!hasBeenInterrupted()) {
2277           long now = System.currentTimeMillis();
2278           minEventExpiration = Long.MAX_VALUE;
2279           while (!eventByTime.isEmpty()) {
2280             EventReg reg = (EventReg) eventByTime.firstKey();
2281             if (reg.leaseExpiration > now) {
2282               minEventExpiration = reg.leaseExpiration;
2283               break;
2284             }
2285             deleteEvent(reg);
2286             if (logger.isLoggable(Level.FINE)) {
2287               logger.log(Level.FINE, "expired event registration {0} for {1}", new Object[] {
2288                   reg.leaseID, reg.listener });
2289             }
2290           }
2291           try {
2292             concurrentObj.writerWait(eventNotifier, minEventExpiration - now);
2293           } catch (ConcurrentLockException e) {
2294             return;
2295           }
2296         }
2297       } finally {
2298         concurrentObj.writeUnlock();
2299       }
2300     }
2301   }
2302 
2303   /**
2304    * Termination thread code.  We do this in a separate thread to
2305    * avoid deadlock, because ActivationGroup.inactive will block until
2306    * in-progress RMI calls are finished.
2307    */
2308   private class DestroyThread extends InterruptedStatusThread
2309   {
2310 
2311     /** Create a non-daemon thread */
2312     public DestroyThread()
2313     {
2314       super("destroy");
2315       /* override inheritance from RMI daemon thread */
2316       setDaemon(false);
2317     }
2318 
2319     public void run()
2320     {
2321       long now = System.currentTimeMillis();
2322       long endTime = now + unexportTimeout;
2323       if (endTime < 0)
2324         endTime = Long.MAX_VALUE;
2325       boolean unexported = false;
2326       /* first try unexporting politely */
2327       while (!unexported && (now < endTime)) {
2328         unexported = serverExporter.unexport(false);
2329         if (!unexported) {
2330           try {
2331             final long sleepTime = Math.min(unexportWait, endTime - now);
2332             sleep(sleepTime);
2333             now = System.currentTimeMillis();
2334           } catch (InterruptedException e) {
2335             logger.log(Levels.HANDLED, "exception during unexport wait", e);
2336           }
2337         }
2338       }
2339       /* if still not unexported, forcibly unexport */
2340       if (!unexported) {
2341         serverExporter.unexport(true);
2342       }
2343 
2344       /* all daemons must terminate before deleting persistent store */
2345       serviceExpirer.interrupt();
2346       eventExpirer.interrupt();
2347       unicaster.interrupt();
2348       multicaster.interrupt();
2349       announcer.interrupt();
2350       snapshotter.interrupt();
2351       tasker.terminate();
2352       joiner.terminate();
2353       discoer.terminate();
2354       try {
2355         serviceExpirer.join();
2356         eventExpirer.join();
2357         unicaster.join();
2358         multicaster.join();
2359         announcer.join();
2360         snapshotter.join();
2361       } catch (InterruptedException e) {
2362       }
2363       closeRequestSockets(tasker.getPending());
2364       if (log != null) {
2365         log.deletePersistentStore();
2366         logger.finer("deleted persistence directory");
2367       }
2368       if (activationID != null) {
2369         try {
2370           ActivationGroup.inactive(activationID, serverExporter);
2371         } catch (Exception e) {
2372           logger.log(Level.INFO, "exception going inactive", e);
2373         }
2374       }
2375       if (lifeCycle != null) {
2376         lifeCycle.unregister(RegistrarImpl.this);
2377       }
2378       if (loginContext != null) {
2379         try {
2380           loginContext.logout();
2381         } catch (LoginException e) {
2382           logger.log(Level.INFO, "logout failed", e);
2383         }
2384       }
2385       logger.info("Reggie shutdown completed");
2386     }
2387   }
2388 
2389   /** Multicast discovery request thread code. */
2390   private class MulticastThread extends InterruptedStatusThread
2391   {
2392 
2393     /** Multicast group address used by multicast requests */
2394     private final InetAddress requestAddr;
2395 
2396     /** Multicast socket to receive packets */
2397     private final MulticastSocket socket;
2398 
2399     /** Interfaces for which configuration failed */
2400     private final List failedInterfaces = new ArrayList();
2401 
2402     /**
2403      * Create a high priority daemon thread.  Set up the socket now
2404      * rather than in run, so that we get any exception up front.
2405      */
2406     public MulticastThread() throws IOException
2407     {
2408       super("multicast request");
2409       setDaemon(true);
2410       if (multicastInterfaces != null && multicastInterfaces.length == 0) {
2411         requestAddr = null;
2412         socket = null;
2413         return;
2414       }
2415       requestAddr = Constants.getRequestAddress();
2416       socket = new MulticastSocket(Constants.discoveryPort);
2417       if (multicastInterfaces != null) {
2418         Level failureLogLevel = multicastInterfacesSpecified ? Level.WARNING : Levels.HANDLED;
2419         for (int i = 0; i < multicastInterfaces.length; i++) {
2420           NetworkInterface nic = multicastInterfaces[i];
2421           try {
2422             socket.setNetworkInterface(nic);
2423             socket.joinGroup(requestAddr);
2424           } catch (IOException e) {
2425             failedInterfaces.add(nic);
2426             if (logger.isLoggable(failureLogLevel)) {
2427               logThrow(failureLogLevel, getClass().getName(), "<init>",
2428                   "exception enabling {0}", new Object[] { nic }, e);
2429             }
2430           }
2431         }
2432       } else {
2433         try {
2434           socket.joinGroup(requestAddr);
2435         } catch (IOException e) {
2436           failedInterfaces.add(null);
2437           logger.log(Level.WARNING, "exception enabling default interface", e);
2438         }
2439       }
2440     }
2441 
2442     public void run()
2443     {
2444       if (multicastInterfaces != null && multicastInterfaces.length == 0) {
2445         return;
2446       }
2447       byte[] buf = new byte[multicastRequestConstraints
2448           .getMulticastMaxPacketSize(DEFAULT_MAX_PACKET_SIZE)];
2449       DatagramPacket dgram = new DatagramPacket(buf, buf.length);
2450       long retryTime = System.currentTimeMillis() + multicastInterfaceRetryInterval;
2451       while (!hasBeenInterrupted()) {
2452         try {
2453           int timeout = 0;
2454           if (!failedInterfaces.isEmpty()) {
2455             timeout = (int) (retryTime - System.currentTimeMillis());
2456             if (timeout <= 0) {
2457               retryFailedInterfaces();
2458               if (failedInterfaces.isEmpty()) {
2459                 timeout = 0;
2460               } else {
2461                 timeout = multicastInterfaceRetryInterval;
2462                 retryTime = System.currentTimeMillis() + timeout;
2463               }
2464             }
2465           }
2466           socket.setSoTimeout(timeout);
2467           dgram.setLength(buf.length);
2468           try {
2469             socket.receive(dgram);
2470           } catch (NullPointerException e) {
2471             break; // workaround for bug 4190513
2472           }
2473 
2474           int pv;
2475           try {
2476             pv = ByteBuffer.wrap(dgram.getData(), dgram.getOffset(), dgram.getLength())
2477                 .getInt();
2478           } catch (BufferUnderflowException e) {
2479             throw new DiscoveryProtocolException(null, e);
2480           }
2481           multicastRequestConstraints.checkProtocolVersion(pv);
2482           tasker.add(new DecodeRequestTask(dgram, getDiscovery(pv)));
2483 
2484           buf = new byte[buf.length];
2485           dgram = new DatagramPacket(buf, buf.length);
2486 
2487         } catch (SocketTimeoutException e) {
2488           // retry failed network interfaces in next iteration
2489         } catch (InterruptedIOException e) {
2490           break;
2491         } catch (Exception e) {
2492           if (hasBeenInterrupted()) {
2493             break;
2494           }
2495           logger.log(Levels.HANDLED, "exception receiving multicast request", e);
2496         }
2497       }
2498       socket.close();
2499     }
2500 
2501     /* This is a workaround for Thread.interrupt not working on
2502      * MulticastSocket.receive on all platforms.
2503      */
2504     public synchronized void interrupt()
2505     {
2506       socket.close();
2507       super.interrupt();
2508     }
2509 
2510     /**
2511      * Attempts to configure each interface contained in the
2512      * failedInterfaces list, removing it from the list if configuration
2513      * succeeds.  The null value is used to indicate the default network
2514      * interface.
2515      */
2516     private void retryFailedInterfaces()
2517     {
2518       for (Iterator i = failedInterfaces.iterator(); i.hasNext();) {
2519         NetworkInterface nic = (NetworkInterface) i.next();
2520         try {
2521           if (nic != null) {
2522             socket.setNetworkInterface(nic);
2523           }
2524           socket.joinGroup(requestAddr);
2525           i.remove();
2526 
2527           Level l = multicastInterfacesSpecified ? Level.INFO : Level.FINE;
2528           if (logger.isLoggable(l)) {
2529             if (nic != null) {
2530               logger.log(l, "enabled {0}", new Object[] { nic });
2531             } else {
2532               logger.log(l, "enabled default interface");
2533             }
2534           }
2535         } catch (IOException e) {
2536           // keep nic in failedInterfaces
2537         }
2538       }
2539     }
2540   }
2541 
2542   /** Unicast discovery request thread code. */
2543   private class UnicastThread extends InterruptedStatusThread
2544   {
2545     /** Server socket to accepts connections on. */
2546     private ServerSocket listen;
2547 
2548     /** Listen port */
2549     public int port;
2550 
2551     /**
2552      * Create a daemon thread.  Set up the socket now rather than in run,
2553      * so that we get any exception up front.
2554      */
2555     public UnicastThread(int port) throws IOException
2556     {
2557       super("unicast request");
2558       setDaemon(true);
2559       if (port == 0) {
2560         try {
2561           listen = new ServerSocket(Constants.discoveryPort);
2562         } catch (IOException e) {
2563           logger.log(Levels.HANDLED, "failed to bind to default port", e);
2564         }
2565       }
2566       if (listen == null) {
2567         listen = new ServerSocket(port);
2568       }
2569       this.port = listen.getLocalPort();
2570     }
2571 
2572     public void run()
2573     {
2574       while (!hasBeenInterrupted()) {
2575         try {
2576           Socket socket = listen.accept();
2577           if (hasBeenInterrupted()) {
2578             try {
2579               socket.close();
2580             } catch (IOException e) {
2581               logger.log(Levels.HANDLED, "exception closing socket", e);
2582             }
2583             break;
2584           }
2585           tasker.add(new SocketTask(socket));
2586         } catch (InterruptedIOException e) {
2587           break;
2588         } catch (Exception e) {
2589           logger.log(Levels.HANDLED, "exception listening on socket", e);
2590         }
2591         /* if we fail in any way, just forget about it */
2592       }
2593       try {
2594         listen.close();
2595       } catch (IOException e) {
2596         logger.log(Levels.HANDLED, "exception closing server socket", e);
2597       }
2598     }
2599 
2600     /* This is a workaround for Thread.interrupt not working on
2601      * ServerSocket.accept on all platforms.  ServerSocket.close
2602      * can't be used as a workaround, because it also doesn't work
2603      * on all platforms.
2604      */
2605     public synchronized void interrupt()
2606     {
2607       try {
2608         (new Socket(InetAddress.getLocalHost(), port)).close();
2609       } catch (IOException e) {
2610       }
2611       super.interrupt();
2612     }
2613 
2614   }
2615 
2616   /** Multicast discovery announcement thread code. */
2617   private class AnnounceThread extends InterruptedStatusThread
2618   {
2619     /** Multicast socket to send packets on */
2620     private final MulticastSocket socket;
2621 
2622     /** Cached datagram packets */
2623     private DatagramPacket[] dataPackets = null;
2624 
2625     /** LookupLocator associated with cached datagram packets */
2626     private LookupLocator lastLocator;
2627 
2628     /** Groups associated with cached datagram packets */
2629     private String[] lastGroups;
2630 
2631     /**
2632      * Create a daemon thread.  Set up the socket now rather than in run,
2633      * so that we get any exception up front.
2634      */
2635     public AnnounceThread() throws IOException
2636     {
2637       super("discovery announcement");
2638       setDaemon(true);
2639       if (multicastInterfaces == null || multicastInterfaces.length > 0) {
2640         socket = new MulticastSocket();
2641         socket.setTimeToLive(multicastAnnouncementConstraints
2642             .getMulticastTimeToLive(DEFAULT_MULTICAST_TTL));
2643       } else {
2644         socket = null;
2645       }
2646     }
2647 
2648     public synchronized void run()
2649     {
2650       if (multicastInterfaces != null && multicastInterfaces.length == 0) {
2651         return;
2652       }
2653       try {
2654         while (!hasBeenInterrupted() && announce(memberGroups)) {
2655           wait(multicastAnnouncementInterval);
2656         }
2657       } catch (InterruptedException e) {
2658       }
2659       if (memberGroups.length > 0)
2660         announce(new String[0]);//send NO_GROUPS just before shutdown
2661       socket.close();
2662     }
2663 
2664     /**
2665      * Announce membership in the specified groups, and return false if
2666      * interrupted, otherwise return true.  This method is run from
2667      * synchronized run method in thread.
2668      */
2669     private boolean announce(String[] groups)
2670     {
2671       if (dataPackets == null || !lastLocator.equals(myLocator)
2672           || !Arrays.equals(lastGroups, groups)) {
2673         List packets = new ArrayList();
2674         Discovery disco;
2675         try {
2676           disco = getDiscovery(multicastAnnouncementConstraints.chooseProtocolVersion());
2677         } catch (DiscoveryProtocolException e) {
2678           throw new AssertionError(e);
2679         }
2680         EncodeIterator ei = disco.encodeMulticastAnnouncement(
2681             new MulticastAnnouncement(announcementSeqNo++, myLocator.getHost(), myLocator
2682                 .getPort(), groups, myServiceID), multicastAnnouncementConstraints
2683                 .getMulticastMaxPacketSize(DEFAULT_MAX_PACKET_SIZE),
2684             multicastAnnouncementConstraints.getUnfulfilledConstraints());
2685         while (ei.hasNext()) {
2686           try {
2687             packets.addAll(Arrays.asList(ei.next()));
2688           } catch (Exception e) {
2689             logger.log((e instanceof UnsupportedConstraintException) ? Levels.HANDLED
2690                 : Level.INFO, "exception encoding multicast" + " announcement", e);
2691           }
2692         }
2693         lastLocator = myLocator;
2694         lastGroups = groups;
2695         dataPackets = (DatagramPacket[]) packets.toArray(new DatagramPacket[packets.size()]);
2696       }
2697       try {
2698         send(dataPackets);
2699       } catch (InterruptedIOException e) {
2700         return false;
2701       } catch (IOException e) {
2702         logger.log(Level.INFO, "exception sending multicast announcement", e);
2703       }
2704       return true;
2705     }
2706 
2707     /**
2708      * Attempts to multicast the given packets on each of the configured
2709      * network interfaces.
2710      */
2711     private void send(DatagramPacket[] packets) throws InterruptedIOException
2712     {
2713       if (multicastInterfaces != null) {
2714         Level failureLogLevel = multicastInterfacesSpecified ? Level.WARNING : Levels.HANDLED;
2715         for (int i = 0; i < multicastInterfaces.length; i++) {
2716           send(packets, multicastInterfaces[i], failureLogLevel);
2717         }
2718       } else {
2719         send(packets, null, Level.WARNING);
2720       }
2721     }
2722 
2723     /**
2724      * Attempts to multicast the given packets on the specified network
2725      * interface, logging failures at the given logging level.  If the
2726      * specified network interface is null, then the default interface is
2727      * used.
2728      */
2729     private void send(DatagramPacket[] packets, NetworkInterface nic, Level failureLogLevel)
2730         throws InterruptedIOException
2731     {
2732       if (nic != null) {
2733         try {
2734           socket.setNetworkInterface(nic);
2735         } catch (SocketException e) {
2736           if (logger.isLoggable(failureLogLevel)) {
2737             logThrow(failureLogLevel, getClass().getName(), "send", "exception setting {0}",
2738                 new Object[] { nic }, e);
2739           }
2740           return;
2741         }
2742       }
2743       for (int i = 0; i < packets.length; i++) {
2744         try {
2745           socket.send(packets[i]);
2746         } catch (InterruptedIOException e) {
2747           throw e;
2748         } catch (IOException e) {
2749           if (nic != null) {
2750             if (logger.isLoggable(failureLogLevel)) {
2751               logThrow(failureLogLevel, getClass().getName(), "send",
2752                   "exception sending packet on {0}", new Object[] { nic }, e);
2753             }
2754           } else {
2755             logger.log(failureLogLevel, "exception sending packet on default interface", e);
2756           }
2757         }
2758       }
2759     }
2760   }
2761 
2762   /**
2763    * Snapshot-taking thread. 
2764    * <p>
2765    * A snapshot is taken when -- after writing a new record to the 
2766    * log file -- it is determined that the size of the log file has 
2767    * exceeded a certain threshold. The code which adds the new record 
2768    * to the log file and which, in turn, decides that a snapshot
2769    * must be taken is "wrapped" in a writer mutex. That is, synchronization
2770    * of processing is achieved in the Registrar through a "reader/writer"
2771    * mutex construct. This construct allows only one writer at any one
2772    * time; but allows an unlimited number of simultaneous readers as
2773    * long as no writer has locked the mutex. During steady-state, it is
2774    * anticipated that there will be far more readers (e.g. lookups) in use 
2775    * than writers (e.g. add/mod/del Attributes). Since the process of
2776    * taking a snapshot can be time-consuming, if the whole snapshot-taking
2777    * process occupies that single writer mutex, then a significant number
2778    * of read requests will be un-necessarily blocked; possibly resulting
2779    * in an unacceptable degradation in response time. 
2780    * <p>
2781    * It is for the above reason that the process of taking a snapshot is
2782    * performed in a separate thread. The thread waits on the monitor
2783    * belonging to the snapshotNotifier instance until it is notified
2784    * (or "signalled") that a snapshot must be taken. The notification
2785    * is sent by another thread, created by the Registrar, which
2786    * determines when the conditions are right for a snapshot. The
2787    * notification takes the form of an interrupt indicating that the
2788    * Snapshot monitor is available. Although the interrupt is sent 
2789    * while the writer mutex is locked, the act of sending the notification
2790    * is less time-consuming than the act of taking the snapshot itself.
2791    * When the thread receives a notification, it awakens and requests a
2792    * lock on the reader mutex (this is all done in the readerWait() method).
2793    * Because a reader -- not a writer -- mutex is locked, read-only
2794    * processes still have access to the system state, so lookups can be
2795    * performed; and the reader mutex prevents changes to the data while
2796    * the snapshot is in progress.  
2797    * <p>
2798    * Note that the current snapshot is guaranteed to complete before the
2799    * next snapshot request is received. This is because even though
2800    * the act of taking a snapshot can be viewed as a writer process, 
2801    * the fact that the next snapshot notification will be wrapped in a
2802    * writer mutex, combined with the fact that a writer mutex can not
2803    * be locked while a reader mutex is locked, allows the snapshot to
2804    * be treated as a reader process.
2805    */
2806   private class SnapshotThread extends InterruptedStatusThread
2807   {
2808 
2809     /** Create a daemon thread */
2810     public SnapshotThread()
2811     {
2812       super("snapshot thread");
2813       setDaemon(true);
2814     }
2815 
2816     public void run()
2817     {
2818       if (log == null) {
2819         return;
2820       }
2821       try {
2822         concurrentObj.readLock();
2823       } catch (ConcurrentLockException e) {
2824         return;
2825       }
2826       try {
2827         while (!hasBeenInterrupted()) {
2828           try {
2829             concurrentObj.readerWait(snapshotNotifier, Long.MAX_VALUE);
2830             try {
2831               log.snapshot();
2832               logFileSize = 0;
2833             } catch (Exception e) {
2834               if (hasBeenInterrupted())
2835                 return;
2836               logger.log(Level.WARNING, "snapshot failed", e);
2837             }
2838           } catch (ConcurrentLockException e) {
2839             return;
2840           }
2841         }
2842       } finally {
2843         concurrentObj.readUnlock();
2844       }
2845     }
2846   }
2847 
2848   // This method's javadoc is inherited from an interface of this class
2849   public Object getServiceProxy() throws NoSuchObjectException
2850   {
2851     ready.check();
2852     return proxy;
2853   }
2854 
2855   // This method's javadoc is inherited from an interface of this class
2856   public Object getProxy()
2857   {
2858     /** locally-called method - no need to check initialization state */
2859     return myRef;
2860   }
2861 
2862   // This method's javadoc is inherited from an interface of this class
2863   public TrustVerifier getProxyVerifier() throws NoSuchObjectException
2864   {
2865     ready.check();
2866     return new ProxyVerifier(myRef, myServiceID);
2867   }
2868 
2869   // This method's javadoc is inherited from an interface of this class
2870   public ServiceRegistration register(Item nitem, long leaseDuration)
2871       throws NoSuchObjectException
2872   {
2873     concurrentObj.writeLock();
2874     try {
2875       ready.check();
2876       ServiceRegistration reg = registerDo(nitem, leaseDuration);
2877       if (logger.isLoggable(Level.FINE)) {
2878         logger.log(Level.FINE, "registered instance of {0} as {1}", new Object[] {
2879             nitem.serviceType.getName(), reg.getServiceID() });
2880       }
2881       return reg;
2882     } finally {
2883       concurrentObj.writeUnlock();
2884     }
2885   }
2886 
2887   // This method's javadoc is inherited from an interface of this class
2888   /** ** GREG ** Changed to return an array with service objects. */
2889   public MarshalledWrapper[] lookup(Template tmpl) throws NoSuchObjectException
2890   {
2891     concurrentObj.readLock();
2892     try {
2893       ready.check();
2894       return lookupDo(tmpl);
2895     } finally {
2896       concurrentObj.readUnlock();
2897     }
2898   }
2899 
2900   // This method's javadoc is inherited from an interface of this class
2901   public Matches lookup(Template tmpl, int maxMatches) throws NoSuchObjectException
2902   {
2903     concurrentObj.readLock();
2904     try {
2905       ready.check();
2906       return lookupDo(tmpl, maxMatches);
2907     } finally {
2908       concurrentObj.readUnlock();
2909     }
2910   }
2911 
2912   // This method's javadoc is inherited from an interface of this class
2913   public EventRegistration notify(Template tmpl, int transitions,
2914       RemoteEventListener listener, MarshalledObject handback, long leaseDuration)
2915       throws RemoteException
2916   {
2917     concurrentObj.writeLock();
2918     try {
2919       ready.check();
2920       EventRegistration reg = notifyDo(tmpl, transitions, listener, handback, leaseDuration);
2921       if (logger.isLoggable(Level.FINE)) {
2922         logger.log(Level.FINE, "registered event listener {0} as {1}", new Object[] {
2923             listener, ((ReferentUuid) reg.getLease()).getReferentUuid() });
2924       }
2925       return reg;
2926     } finally {
2927       concurrentObj.writeUnlock();
2928     }
2929   }
2930 
2931   // This method's javadoc is inherited from an interface of this class
2932   public EntryClassBase[] getEntryClasses(Template tmpl) throws NoSuchObjectException
2933   {
2934     concurrentObj.readLock();
2935     try {
2936       ready.check();
2937       return getEntryClassesDo(tmpl);
2938     } finally {
2939       concurrentObj.readUnlock();
2940     }
2941   }
2942 
2943   // This method's javadoc is inherited from an interface of this class
2944   public Object[] getFieldValues(Template tmpl, int setIndex, int field)
2945       throws NoSuchObjectException
2946   {
2947     concurrentObj.readLock();
2948     try {
2949       ready.check();
2950       return getFieldValuesDo(tmpl, setIndex, field);
2951     } finally {
2952       concurrentObj.readUnlock();
2953     }
2954   }
2955 
2956   // This method's javadoc is inherited from an interface of this class
2957   public ServiceTypeBase[] getServiceTypes(Template tmpl, String prefix)
2958       throws NoSuchObjectException
2959   {
2960     concurrentObj.readLock();
2961     try {
2962       ready.check();
2963       return getServiceTypesDo(tmpl, prefix);
2964     } finally {
2965       concurrentObj.readUnlock();
2966     }
2967   }
2968 
2969   // This method's javadoc is inherited from an interface of this class
2970   public LookupLocator getLocator() throws NoSuchObjectException
2971   {
2972     ready.check();
2973     return myLocator;
2974   }
2975 
2976   // This method's javadoc is inherited from an interface of this class
2977   public Object getAdmin() throws NoSuchObjectException
2978   {
2979     ready.check();
2980     return AdminProxy.getInstance(myRef, myServiceID);
2981   }
2982 
2983   // This method's javadoc is inherited from an interface of this class
2984   public void addAttributes(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSets)
2985       throws NoSuchObjectException, UnknownLeaseException
2986   {
2987     concurrentObj.writeLock();
2988     try {
2989       ready.check();
2990       if (serviceID.equals(myServiceID))
2991         throw new SecurityException("privileged service id");
2992       addAttributesDo(serviceID, leaseID, attrSets);
2993       addLogRecord(new AttrsAddedLogObj(serviceID, leaseID, attrSets));
2994       queueEvents();
2995     } finally {
2996       concurrentObj.writeUnlock();
2997     }
2998   }
2999 
3000   // This method's javadoc is inherited from an interface of this class
3001   public void modifyAttributes(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSetTmpls,
3002       EntryRep[] attrSets) throws NoSuchObjectException, UnknownLeaseException
3003   {
3004     concurrentObj.writeLock();
3005     try {
3006       ready.check();
3007       if (serviceID.equals(myServiceID))
3008         throw new SecurityException("privileged service id");
3009       modifyAttributesDo(serviceID, leaseID, attrSetTmpls, attrSets);
3010       addLogRecord(new AttrsModifiedLogObj(serviceID, leaseID, attrSetTmpls, attrSets));
3011       queueEvents();
3012     } finally {
3013       concurrentObj.writeUnlock();
3014     }
3015   }
3016 
3017   // This method's javadoc is inherited from an interface of this class
3018   public void setAttributes(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSets)
3019       throws NoSuchObjectException, UnknownLeaseException
3020   {
3021     concurrentObj.writeLock();
3022     try {
3023       ready.check();
3024       if (serviceID.equals(myServiceID))
3025         throw new SecurityException("privileged service id");
3026       setAttributesDo(serviceID, leaseID, attrSets);
3027       addLogRecord(new AttrsSetLogObj(serviceID, leaseID, attrSets));
3028       queueEvents();
3029     } finally {
3030       concurrentObj.writeUnlock();
3031     }
3032   }
3033 
3034   // This method's javadoc is inherited from an interface of this class
3035   public void cancelServiceLease(ServiceID serviceID, Uuid leaseID)
3036       throws NoSuchObjectException, UnknownLeaseException
3037   {
3038     concurrentObj.writeLock();
3039     try {
3040       ready.check();
3041       cancelServiceLeaseDo(serviceID, leaseID);
3042       addLogRecord(new ServiceLeaseCancelledLogObj(serviceID, leaseID));
3043       queueEvents();
3044       if (logger.isLoggable(Level.FINE)) {
3045         logger.log(Level.FINE, "cancelled service registration {0}",
3046             new Object[] { serviceID });
3047       }
3048     } finally {
3049       concurrentObj.writeUnlock();
3050     }
3051   }
3052 
3053   // This method's javadoc is inherited from an interface of this class
3054   public long renewServiceLease(ServiceID serviceID, Uuid leaseID, long renewDuration)
3055       throws NoSuchObjectException, UnknownLeaseException
3056   {
3057     concurrentObj.priorityWriteLock();
3058     try {
3059       ready.check();
3060       return renewServiceLeaseDo(serviceID, leaseID, renewDuration);
3061       /* addLogRecord is in renewServiceLeaseDo */
3062     } finally {
3063       concurrentObj.writeUnlock();
3064     }
3065   }
3066 
3067   // This method's javadoc is inherited from an interface of this class
3068   public void cancelEventLease(long eventID, Uuid leaseID) throws NoSuchObjectException,
3069       UnknownLeaseException
3070   {
3071     concurrentObj.writeLock();
3072     try {
3073       ready.check();
3074       cancelEventLeaseDo(eventID, leaseID);
3075       addLogRecord(new EventLeaseCancelledLogObj(eventID, leaseID));
3076       if (logger.isLoggable(Level.FINE)) {
3077         logger.log(Level.FINE, "cancelled event registration {0}", new Object[] { leaseID });
3078       }
3079     } finally {
3080       concurrentObj.writeUnlock();
3081     }
3082   }
3083 
3084   // This method's javadoc is inherited from an interface of this class
3085   public long renewEventLease(long eventID, Uuid leaseID, long renewDuration)
3086       throws NoSuchObjectException, UnknownLeaseException
3087   {
3088     concurrentObj.priorityWriteLock();
3089     try {
3090       ready.check();
3091       return renewEventLeaseDo(eventID, leaseID, renewDuration);
3092       /* addLogRecord is in renewEventLeaseDo */
3093     } finally {
3094       concurrentObj.writeUnlock();
3095     }
3096   }
3097 
3098   // This method's javadoc is inherited from an interface of this class
3099   public RenewResults renewLeases(Object[] regIDs, Uuid[] leaseIDs, long[] renewDurations)
3100       throws NoSuchObjectException
3101   {
3102     concurrentObj.priorityWriteLock();
3103     try {
3104       ready.check();
3105       return renewLeasesDo(regIDs, leaseIDs, renewDurations);
3106       /* addLogRecord is in renewLeasesDo */
3107     } finally {
3108       concurrentObj.writeUnlock();
3109     }
3110   }
3111 
3112   // This method's javadoc is inherited from an interface of this class
3113   public Exception[] cancelLeases(Object[] regIDs, Uuid[] leaseIDs)
3114       throws NoSuchObjectException
3115   {
3116     concurrentObj.writeLock();
3117     try {
3118       ready.check();
3119       Exception[] exceptions = cancelLeasesDo(regIDs, leaseIDs);
3120       addLogRecord(new LeasesCancelledLogObj(regIDs, leaseIDs));
3121       queueEvents();
3122       if (logger.isLoggable(Level.FINE)) {
3123         for (int i = 0; i < regIDs.length; i++) {
3124           if (exceptions != null && exceptions[i] != null) {
3125             continue;
3126           }
3127           if (regIDs[i] instanceof ServiceID) {
3128             logger.log(Level.FINE, "cancelled service registration {0}",
3129                 new Object[] { regIDs[i] });
3130           } else {
3131             logger.log(Level.FINE, "cancelled event registration {0}",
3132                 new Object[] { leaseIDs[i] });
3133           }
3134         }
3135       }
3136       return exceptions;
3137     } finally {
3138       concurrentObj.writeUnlock();
3139     }
3140   }
3141 
3142   // This method's javadoc is inherited from an interface of this class
3143   public Entry[] getLookupAttributes() throws NoSuchObjectException
3144   {
3145     concurrentObj.readLock();
3146     try {
3147       ready.check();
3148       /* no need to clone, never modified once created */
3149       return lookupAttrs;
3150     } finally {
3151       concurrentObj.readUnlock();
3152     }
3153   }
3154 
3155   // This method's javadoc is inherited from an interface of this class
3156   public void addLookupAttributes(Entry[] attrSets) throws RemoteException
3157   {
3158     concurrentObj.writeLock();
3159     try {
3160       ready.check();
3161       EntryRep[] attrs = EntryRep.toEntryRep(attrSets, true);
3162       addAttributesDo(myServiceID, myLeaseID, attrs);
3163       joiner.addAttributes(attrSets);
3164       lookupAttrs = joiner.getAttributes();
3165       addLogRecord(new LookupAttributesChangedLogObj(lookupAttrs));
3166       queueEvents();
3167     } catch (UnknownLeaseException e) {
3168       throw new AssertionError("Self-registration never expires");
3169     } finally {
3170       concurrentObj.writeUnlock();
3171     }
3172   }
3173 
3174   // This method's javadoc is inherited from an interface of this class
3175   public void modifyLookupAttributes(Entry[] attrSetTemplates, Entry[] attrSets)
3176       throws RemoteException
3177   {
3178     concurrentObj.writeLock();
3179     try {
3180       ready.check();
3181       EntryRep[] tmpls = EntryRep.toEntryRep(attrSetTemplates, false);
3182       EntryRep[] attrs = EntryRep.toEntryRep(attrSets, false);
3183       modifyAttributesDo(myServiceID, myLeaseID, tmpls, attrs);
3184       joiner.modifyAttributes(attrSetTemplates, attrSets, true);
3185       lookupAttrs = joiner.getAttributes();
3186       addLogRecord(new LookupAttributesChangedLogObj(lookupAttrs));
3187       queueEvents();
3188     } catch (UnknownLeaseException e) {
3189       throw new AssertionError("Self-registration never expires");
3190     } finally {
3191       concurrentObj.writeUnlock();
3192     }
3193   }
3194 
3195   // This method's javadoc is inherited from an interface of this class
3196   public String[] getLookupGroups() throws NoSuchObjectException
3197   {
3198     concurrentObj.readLock();
3199     try {
3200       ready.check();
3201       /* no need to clone, never modified once created */
3202       return lookupGroups;
3203     } finally {
3204       concurrentObj.readUnlock();
3205     }
3206   }
3207 
3208   // This method's javadoc is inherited from an interface of this class
3209   public void addLookupGroups(String[] groups) throws NoSuchObjectException
3210   {
3211     concurrentObj.writeLock();
3212     try {
3213       ready.check();
3214       DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
3215       try {
3216         dgm.addGroups(groups);
3217       } catch (IOException e) {
3218         throw new RuntimeException(e.toString());
3219       }
3220       lookupGroups = dgm.getGroups();
3221       addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
3222       if (logger.isLoggable(Level.CONFIG)) {
3223         logger.log(Level.CONFIG, "added lookup groups {0}", new Object[] { Arrays
3224             .asList(groups) });
3225       }
3226     } finally {
3227       concurrentObj.writeUnlock();
3228     }
3229   }
3230 
3231   // This method's javadoc is inherited from an interface of this class
3232   public void removeLookupGroups(String[] groups) throws NoSuchObjectException
3233   {
3234     concurrentObj.writeLock();
3235     try {
3236       ready.check();
3237       DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
3238       dgm.removeGroups(groups);
3239       lookupGroups = dgm.getGroups();
3240       addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
3241       if (logger.isLoggable(Level.CONFIG)) {
3242         logger.log(Level.CONFIG, "removed lookup groups {0}", new Object[] { Arrays
3243             .asList(groups) });
3244       }
3245     } finally {
3246       concurrentObj.writeUnlock();
3247     }
3248   }
3249 
3250   // This method's javadoc is inherited from an interface of this class
3251   public void setLookupGroups(String[] groups) throws NoSuchObjectException
3252   {
3253     concurrentObj.writeLock();
3254     try {
3255       ready.check();
3256       DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
3257       try {
3258         dgm.setGroups(groups);
3259       } catch (IOException e) {
3260         throw new RuntimeException(e.toString());
3261       }
3262       lookupGroups = dgm.getGroups();
3263       addLogRecord(new LookupGroupsChangedLogObj(lookupGroups));
3264       if (logger.isLoggable(Level.CONFIG)) {
3265         logger.log(Level.CONFIG, "set lookup groups {0}",
3266             new Object[] { (groups != null) ? Arrays.asList(groups) : null });
3267       }
3268     } finally {
3269       concurrentObj.writeUnlock();
3270     }
3271   }
3272 
3273   // This method's javadoc is inherited from an interface of this class
3274   public LookupLocator[] getLookupLocators() throws NoSuchObjectException
3275   {
3276     concurrentObj.readLock();
3277     try {
3278       ready.check();
3279       /* no need to clone, never modified once created */
3280       return lookupLocators;
3281     } finally {
3282       concurrentObj.readUnlock();
3283     }
3284   }
3285 
3286   // This method's javadoc is inherited from an interface of this class
3287   public void addLookupLocators(LookupLocator[] locators) throws RemoteException
3288   {
3289     ready.check();
3290     locators = prepareLocators(locators, locatorPreparer, false);
3291     concurrentObj.writeLock();
3292     try {
3293       ready.check();
3294       DiscoveryLocatorManagement dlm = (DiscoveryLocatorManagement) discoer;
3295       dlm.addLocators(locators);
3296       lookupLocators = dlm.getLocators();
3297       addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
3298       if (logger.isLoggable(Level.CONFIG)) {
3299         logger.log(Level.CONFIG, "added lookup locators {0}", new Object[] { Arrays
3300             .asList(locators) });
3301       }
3302     } finally {
3303       concurrentObj.writeUnlock();
3304     }
3305   }
3306 
3307   // This method's javadoc is inherited from an interface of this class
3308   public void removeLookupLocators(LookupLocator[] locators) throws RemoteException
3309   {
3310     ready.check();
3311     locators = prepareLocators(locators, locatorPreparer, false);
3312     concurrentObj.writeLock();
3313     try {
3314       ready.check();
3315       DiscoveryLocatorManagement dlm = (DiscoveryLocatorManagement) discoer;
3316       dlm.removeLocators(locators);
3317       lookupLocators = dlm.getLocators();
3318       addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
3319       if (logger.isLoggable(Level.CONFIG)) {
3320         logger.log(Level.CONFIG, "removed lookup locators {0}", new Object[] { Arrays
3321             .asList(locators) });
3322       }
3323     } finally {
3324       concurrentObj.writeUnlock();
3325     }
3326   }
3327 
3328   // This method's javadoc is inherited from an interface of this class
3329   public void setLookupLocators(LookupLocator[] locators) throws RemoteException
3330   {
3331     ready.check();
3332     locators = prepareLocators(locators, locatorPreparer, false);
3333     concurrentObj.writeLock();
3334     try {
3335       ready.check();
3336       DiscoveryLocatorManagement dlm = (DiscoveryLocatorManagement) discoer;
3337       dlm.setLocators(locators);
3338       lookupLocators = dlm.getLocators();
3339       addLogRecord(new LookupLocatorsChangedLogObj(lookupLocators));
3340       if (logger.isLoggable(Level.CONFIG)) {
3341         logger.log(Level.CONFIG, "set lookup locators {0}", new Object[] { Arrays
3342             .asList(locators) });
3343       }
3344     } finally {
3345       concurrentObj.writeUnlock();
3346     }
3347   }
3348 
3349   // This method's javadoc is inherited from an interface of this class
3350   public void addMemberGroups(String[] groups) throws NoSuchObjectException
3351   {
3352     concurrentObj.writeLock();
3353     try {
3354       ready.check();
3355       for (int i = 0; i < groups.length; i++) {
3356         if (indexOf(memberGroups, groups[i]) < 0)
3357           memberGroups = (String[]) arrayAdd(memberGroups, groups[i]);
3358       }
3359       synchronized (announcer) {
3360         announcer.notify();
3361       }
3362       addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
3363       if (logger.isLoggable(Level.CONFIG)) {
3364         logger.log(Level.CONFIG, "added member groups {0}", new Object[] { Arrays
3365             .asList(groups) });
3366       }
3367     } finally {
3368       concurrentObj.writeUnlock();
3369     }
3370   }
3371 
3372   // This method's javadoc is inherited from an interface of this class
3373   public void removeMemberGroups(String[] groups) throws NoSuchObjectException
3374   {
3375     concurrentObj.writeLock();
3376     try {
3377       ready.check();
3378       for (int i = 0; i < groups.length; i++) {
3379         int j = indexOf(memberGroups, groups[i]);
3380         if (j >= 0)
3381           memberGroups = (String[]) arrayDel(memberGroups, j);
3382       }
3383       synchronized (announcer) {
3384         announcer.notify();
3385       }
3386       addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
3387       if (logger.isLoggable(Level.CONFIG)) {
3388         logger.log(Level.CONFIG, "removed member groups {0}", new Object[] { Arrays
3389             .asList(groups) });
3390       }
3391     } finally {
3392       concurrentObj.writeUnlock();
3393     }
3394   }
3395 
3396   // This method's javadoc is inherited from an interface of this class
3397   public String[] getMemberGroups() throws NoSuchObjectException
3398   {
3399     concurrentObj.readLock();
3400     try {
3401       ready.check();
3402       /* no need to clone, never modified once created */
3403       return memberGroups;
3404     } finally {
3405       concurrentObj.readUnlock();
3406     }
3407   }
3408 
3409   // This method's javadoc is inherited from an interface of this class
3410   public void setMemberGroups(String[] groups) throws NoSuchObjectException
3411   {
3412     concurrentObj.writeLock();
3413     try {
3414       ready.check();
3415       memberGroups = (String[]) removeDups(groups);
3416       addLogRecord(new MemberGroupsChangedLogObj(memberGroups));
3417       synchronized (announcer) {
3418         announcer.notify();
3419       }
3420       if (logger.isLoggable(Level.CONFIG)) {
3421         logger.log(Level.CONFIG, "set member groups {0}",
3422             new Object[] { Arrays.asList(groups) });
3423       }
3424     } finally {
3425       concurrentObj.writeUnlock();
3426     }
3427   }
3428 
3429   // This method's javadoc is inherited from an interface of this class
3430   public int getUnicastPort() throws NoSuchObjectException
3431   {
3432     concurrentObj.readLock();
3433     try {
3434       ready.check();
3435       return unicastPort;
3436     } finally {
3437       concurrentObj.readUnlock();
3438     }
3439   }
3440 
3441   // This method's javadoc is inherited from an interface of this class
3442   public void setUnicastPort(int port) throws IOException, RemoteException
3443   {
3444     concurrentObj.writeLock();
3445     try {
3446       ready.check();
3447       if (port == unicastPort)
3448         return;
3449       if ((port == 0 && unicaster.port == Constants.discoveryPort) || port == unicaster.port) {
3450         unicastPort = port;
3451         addLogRecord(new UnicastPortSetLogObj(port));
3452         return;
3453       }
3454       /* create a UnicastThread that listens on the new port */
3455       UnicastThread newUnicaster = new UnicastThread(port);
3456       /* terminate the current UnicastThread listening on the old port */
3457       unicaster.interrupt();
3458       try {
3459         unicaster.join();
3460       } catch (InterruptedException e) {
3461       }
3462       /* start the UnicastThread listening on the new port */
3463       unicaster = newUnicaster;
3464       unicaster.start();
3465       unicastPort = port;
3466       myLocator = (proxy instanceof RemoteMethodControl) ? new ConstrainableLookupLocator(
3467           myLocator.getHost(), unicaster.port, null) : new LookupLocator(myLocator.getHost(),
3468           unicaster.port);
3469       synchronized (announcer) {
3470         announcer.notify();
3471       }
3472       addLogRecord(new UnicastPortSetLogObj(port));
3473       if (logger.isLoggable(Level.CONFIG)) {
3474         logger.log(Level.CONFIG, "changed unicast discovery port to {0}",
3475             new Object[] { new Integer(unicaster.port) });
3476       }
3477     } finally {
3478       concurrentObj.writeUnlock();
3479     }
3480   }
3481 
3482   // This method's javadoc is inherited from an interface of this class
3483   public void destroy() throws RemoteException
3484   {
3485     concurrentObj.priorityWriteLock();
3486     try {
3487       ready.check();
3488       logger.info("starting Reggie shutdown");
3489       /* unregister with activation system if activatable */
3490       if (activationID != null) {
3491         try {
3492           activationSystem.unregisterObject(activationID);
3493         } catch (ActivationException e) {
3494           logger.log(Levels.HANDLED, "exception unregistering activation ID", e);
3495         } catch (RemoteException e) {
3496           logger.log(Level.WARNING, "aborting Reggie shutdown", e);
3497           throw e;
3498         }
3499       }
3500       ready.shutdown();
3501       new DestroyThread().start();
3502     } finally {
3503       concurrentObj.writeUnlock();
3504     }
3505   }
3506 
3507   /**
3508    * Return a new array containing the elements of the given array
3509    * plus the given element added to the end.
3510    */
3511   private static Object[] arrayAdd(Object[] array, Object elt)
3512   {
3513     int len = array.length;
3514     Object[] narray = (Object[]) Array.newInstance(array.getClass().getComponentType(),
3515         len + 1);
3516     System.arraycopy(array, 0, narray, 0, len);
3517     narray[len] = elt;
3518     return narray;
3519   }
3520 
3521   /**
3522    * Return a new array containing all the elements of the given array
3523    * except the one at the specified index.
3524    */
3525   private static Object[] arrayDel(Object[] array, int i)
3526   {
3527     int len = array.length - 1;
3528     Object[] narray = (Object[]) Array.newInstance(array.getClass().getComponentType(), len);
3529     System.arraycopy(array, 0, narray, 0, i);
3530     System.arraycopy(array, i + 1, narray, i, len - i);
3531     return narray;
3532   }
3533 
3534   /** Returns the first index of elt in the array, else -1. */
3535   private static int indexOf(Object[] array, Object elt)
3536   {
3537     return indexOf(array, array.length, elt);
3538   }
3539 
3540   /** Returns the first index of elt in the array if < len, else -1. */
3541   private static int indexOf(Object[] array, int len, Object elt)
3542   {
3543     for (int i = 0; i < len; i++) {
3544       if (elt.equals(array[i]))
3545         return i;
3546     }
3547     return -1;
3548   }
3549 
3550   /** Return true if the array is null or zero length */
3551   private static boolean isEmpty(Object[] array)
3552   {
3553     return (array == null || array.length == 0);
3554   }
3555 
3556   /** Return true if some object is an element of both arrays */
3557   private static boolean overlap(Object[] arr1, Object[] arr2)
3558   {
3559     for (int i = arr1.length; --i >= 0;) {
3560       if (indexOf(arr2, arr1[i]) >= 0)
3561         return true;
3562     }
3563     return false;
3564   }
3565 
3566   /** Test if all elements of the array are null. */
3567   private static boolean allNull(Object[] array)
3568   {
3569     for (int i = array.length; --i >= 0;) {
3570       if (array[i] != null)
3571         return false;
3572     }
3573     return true;
3574   }
3575 
3576   /** Weed out duplicates. */
3577   private static Object[] removeDups(Object[] arr)
3578   {
3579     for (int i = arr.length; --i >= 0;) {
3580       if (indexOf(arr, i, arr[i]) >= 0)
3581         arr = arrayDel(arr, i);
3582     }
3583     return arr;
3584   }
3585 
3586   /** Delete item.attributeSets[i] and return the new array. */
3587   private static EntryRep[] deleteSet(Item item, int i)
3588   {
3589     item.attributeSets = (EntryRep[]) arrayDel(item.attributeSets, i);
3590     return item.attributeSets;
3591   }
3592 
3593   /**
3594    * Do a deep copy of the item, and substitute replacements for all
3595    * embedded EntryClass instances and null for the ServiceType and
3596    * codebase (since they aren't needed on the client side).
3597    */
3598   private static Item copyItem(Item item)
3599   {
3600     item = (Item) item.clone();
3601     item.serviceType = null;
3602     item.codebase = null;
3603     EntryRep[] attrSets = item.attributeSets;
3604     for (int i = attrSets.length; --i >= 0;) {
3605       attrSets[i].eclass = attrSets[i].eclass.getReplacement();
3606     }
3607     return item;
3608   }
3609 
3610   /**
3611    * Return the first (highest) class that defines the given field.  This
3612    * would be a method on EntryClass, but we want to minimize code
3613    * downloaded into the client.
3614    */
3615   private static EntryClass getDefiningClass(EntryClass eclass, int fldidx)
3616   {
3617     while (true) {
3618       EntryClass sup = eclass.getSuperclass();
3619       if (sup.getNumFields() <= fldidx)
3620         return eclass;
3621       eclass = sup;
3622     }
3623   }
3624 
3625   /** Adds a service registration to types in its hierarchy */
3626   private void addServiceByTypes(ServiceType type, SvcReg reg)
3627   {
3628     Map map = (Map) serviceByTypeName.get(type.getName());
3629     if (map == null) {
3630       map = new HashMap();
3631       serviceByTypeName.put(type.getName(), map);
3632     }
3633     map.put(reg.item.serviceID, reg);
3634     ServiceType[] ifaces = type.getInterfaces();
3635     for (int i = ifaces.length; --i >= 0;) {
3636       addServiceByTypes(ifaces[i], reg);
3637     }
3638     ServiceType sup = type.getSuperclass();
3639     if (sup != null)
3640       addServiceByTypes(sup, reg);
3641   }
3642 
3643   /** Deletes a service registration from types in its hierarchy */
3644   private void deleteServiceFromTypes(ServiceType type, SvcReg reg)
3645   {
3646     Map map = (Map) serviceByTypeName.get(type.getName());
3647     if (map != null) {
3648       map.remove(reg.item.serviceID);
3649       if ((map.isEmpty()) && !type.equals(objectServiceType))
3650         serviceByTypeName.remove(type.getName());
3651       ServiceType[] ifaces = type.getInterfaces();
3652       for (int j = ifaces.length; --j >= 0;) {
3653         deleteServiceFromTypes(ifaces[j], reg);
3654       }
3655       ServiceType sup = type.getSuperclass();
3656       if (sup != null)
3657         deleteServiceFromTypes(sup, reg);
3658     }
3659   }
3660 
3661   /**
3662    * Test if an item matches a template.  This would be a method on
3663    * Template, but we want to minimize code downloaded into the client.
3664    */
3665   private static boolean matchItem(Template tmpl, Item item)
3666   {
3667     return ((tmpl.serviceID == null || tmpl.serviceID.equals(item.serviceID))
3668         && matchType(tmpl.serviceTypes, item.serviceType) && matchAttributes(tmpl, item));
3669   }
3670 
3671   /** Test if a type is equal to or a subtype of every type in an array. */
3672   private static boolean matchType(ServiceType[] types, ServiceType type)
3673   {
3674     if (types != null) {
3675       for (int i = types.length; --i >= 0;) {
3676         if (!types[i].isAssignableFrom(type))
3677           return false;
3678       }
3679     }
3680     return true;
3681   }
3682 
3683   /**
3684    * Test if an entry matches a template.  This would be a method on
3685    * EntryRep, but we want to minimize code downloaded into the client.
3686    */
3687   private static boolean matchEntry(EntryRep tmpl, EntryRep entry)
3688   {
3689     if (!tmpl.eclass.isAssignableFrom(entry.eclass)
3690         || tmpl.fields.length > entry.fields.length)
3691       return false;
3692     for (int i = tmpl.fields.length; --i >= 0;) {
3693       if (tmpl.fields[i] != null && !tmpl.fields[i].equals(entry.fields[i]))
3694         return false;
3695     }
3696     return true;
3697   }
3698 
3699   /**
3700    * Test if there is at least one matching entry in the Item for
3701    * each entry template in the Template.
3702    */
3703   private static boolean matchAttributes(Template tmpl, Item item)
3704   {
3705     EntryRep[] tmpls = tmpl.attributeSetTemplates;
3706     if (tmpls != null) {
3707       EntryRep[] entries = item.attributeSets;
3708       outer: for (int i = tmpls.length; --i >= 0;) {
3709         EntryRep etmpl = tmpls[i];
3710         for (int j = entries.length; --j >= 0;) {
3711           if (matchEntry(etmpl, entries[j]))
3712             continue outer;
3713         }
3714         return false;
3715       }
3716     }
3717     return true;
3718   }
3719 
3720   /**
3721    * Test if an entry either doesn't match any template in an array,
3722    * or matches a template but is a subclass of the template type.
3723    */
3724   private static boolean attrMatch(EntryRep[] tmpls, EntryRep attrSet)
3725   {
3726     boolean good = true;
3727     if (tmpls != null) {
3728       for (int i = tmpls.length; --i >= 0;) {
3729         EntryRep tmpl = tmpls[i];
3730         if (matchEntry(tmpl, attrSet)) {
3731           if (tmpl.eclass.isAssignableFrom(attrSet.eclass)
3732               && !tmpl.eclass.equals(attrSet.eclass))
3733             return true;
3734           good = false;
3735         }
3736       }
3737     }
3738     return good;
3739   }
3740 
3741   /**
3742    * Test if the service has an entry of the given class or subclass
3743    * with a field of the given value.
3744    */
3745   private static boolean hasAttr(SvcReg reg, EntryClass eclass, int fldidx, Object value)
3746   {
3747     EntryRep[] sets = reg.item.attributeSets;
3748     for (int i = sets.length; --i >= 0;) {
3749       EntryRep set = sets[i];
3750       if (eclass.isAssignableFrom(set.eclass)
3751           && ((value == null && set.fields[fldidx] == null) || (value != null && value
3752               .equals(set.fields[fldidx]))))
3753         return true;
3754     }
3755     return false;
3756   }
3757 
3758   /**
3759    * Test if the service has an entry of the exact given class (assumed
3760    * to have no fields).
3761    */
3762   private static boolean hasEmptyAttr(SvcReg reg, EntryClass eclass)
3763   {
3764     EntryRep[] sets = reg.item.attributeSets;
3765     for (int i = sets.length; --i >= 0;) {
3766       if (eclass.equals(sets[i].eclass))
3767         return true;
3768     }
3769     return false;
3770   }
3771 
3772   /**
3773    * Find the most specific types (of type) that don't match prefix and
3774    * aren't equal to or a supertype of any types in bases, and add them
3775    * to types.
3776    */
3777   private static void addTypes(ArrayList types, ArrayList codebases, ServiceType[] bases,
3778       String prefix, ServiceType type, String codebase)
3779   {
3780     if (types.contains(type))
3781       return;
3782     if (bases != null) {
3783       for (int i = bases.length; --i >= 0;) {
3784         if (type.isAssignableFrom(bases[i]))
3785           return;
3786       }
3787     }
3788     if (prefix == null || type.getName().startsWith(prefix)) {
3789       types.add(type);
3790       codebases.add(codebase);
3791       return;
3792     }
3793     ServiceType[] ifs = type.getInterfaces();
3794     for (int i = ifs.length; --i >= 0;) {
3795       addTypes(types, codebases, bases, prefix, ifs[i], codebase);
3796     }
3797     ServiceType sup = type.getSuperclass();
3798     if (sup != null)
3799       addTypes(types, codebases, bases, prefix, sup, codebase);
3800   }
3801 
3802   /** Limit leaseDuration by limit, and check for negative value. */
3803   private static long limitDuration(long leaseDuration, long limit)
3804   {
3805     if (leaseDuration == Lease.ANY || leaseDuration > limit)
3806       leaseDuration = limit;
3807     else if (leaseDuration < 0)
3808       throw new IllegalArgumentException("negative lease duration");
3809     return leaseDuration;
3810   }
3811 
3812   /**
3813    * Writes reggie's attributes to ObjectOutputStream as a
3814    * null-terminated list of MarshalledInstances.
3815    */
3816   private static void marshalAttributes(Entry[] attrs, ObjectOutputStream out)
3817       throws IOException
3818   {
3819     for (int i = 0; i < attrs.length; i++) {
3820       out.writeObject(new MarshalledInstance(attrs[i]));
3821     }
3822     out.writeObject(null);
3823   }
3824 
3825   /**
3826    * Returns reggie's attributes unmarshalled from a null-terminated list of
3827    * MarshalledInstances read from the given stream, logging (but tolerating)
3828    * unmarshalling failures.
3829    */
3830   private static Entry[] unmarshalAttributes(ObjectInputStream in) throws IOException,
3831       ClassNotFoundException
3832   {
3833     ArrayList attributes = new ArrayList();
3834     MarshalledInstance mi = null;
3835     while ((mi = (MarshalledInstance) in.readObject()) != null) {
3836       try {
3837         attributes.add((Entry) mi.get(false));
3838       } catch (Throwable e) {
3839         if (e instanceof Error
3840             && ThrowableConstants.retryable(e) == ThrowableConstants.BAD_OBJECT) {
3841           throw (Error) e;
3842         }
3843         logger.log(Level.WARNING, "failed to recover LUS attribute", e);
3844       }
3845     }
3846     Entry[] attrs = new Entry[attributes.size()];
3847     return (Entry[]) attributes.toArray(attrs);
3848 
3849   }
3850 
3851   /**
3852    * Writes locators to the given stream as a null-terminated list of
3853    * MarshalledInstances.
3854    */
3855   private static void marshalLocators(LookupLocator[] locators, ObjectOutputStream out)
3856       throws IOException
3857   {
3858     for (int i = 0; i < locators.length; i++) {
3859       out.writeObject(new MarshalledInstance(locators[i]));
3860     }
3861     out.writeObject(null);
3862   }
3863 
3864   /**
3865    * Returns locators unmarshalled from a null-terminated list of
3866    * MarshalledInstances read from the given stream, logging (but tolerating)
3867    * unmarshalling failures.
3868    */
3869   private static LookupLocator[] unmarshalLocators(ObjectInputStream in) throws IOException,
3870       ClassNotFoundException
3871   {
3872     List l = new ArrayList();
3873     MarshalledInstance mi;
3874     while ((mi = (MarshalledInstance) in.readObject()) != null) {
3875       try {
3876         l.add((LookupLocator) mi.get(false));
3877       } catch (Throwable e) {
3878         if (e instanceof Error
3879             && ThrowableConstants.retryable(e) == ThrowableConstants.BAD_OBJECT) {
3880           throw (Error) e;
3881         }
3882         logger.log(Level.WARNING, "failed to recover lookup locator", e);
3883       }
3884     }
3885     return (LookupLocator[]) l.toArray(new LookupLocator[l.size()]);
3886   }
3887 
3888   /**
3889    * Returns new array containing locators from the given non-null array
3890    * prepared using the given proxy preparer.  If tolerateFailures is false,
3891    * then any proxy preparation exception is propagated to the caller.
3892    * Otherwise, such exceptions are logged, and only successfully prepared
3893    * locators are included in the returned array.
3894    */
3895   private static LookupLocator[] prepareLocators(LookupLocator[] locators,
3896       ProxyPreparer preparer, boolean tolerateFailures) throws RemoteException
3897   {
3898     List l = new ArrayList();
3899     for (int i = 0; i < locators.length; i++) {
3900       try {
3901         l.add((LookupLocator) preparer.prepareProxy(locators[i]));
3902       } catch (Exception e) {
3903         if (!tolerateFailures) {
3904           if (e instanceof RemoteException) {
3905             throw (RemoteException) e;
3906           } else {
3907             throw (RuntimeException) e;
3908           }
3909         }
3910         if (logger.isLoggable(Level.WARNING)) {
3911           logThrow(Level.WARNING, RegistrarImpl.class.getName(), "prepareLocators",
3912               "failed to prepare lookup locator {0}", new Object[] { locators[i] }, e);
3913         }
3914       }
3915     }
3916     return (LookupLocator[]) l.toArray(new LookupLocator[l.size()]);
3917   }
3918 
3919   /**
3920    * Logs a thrown exception.
3921    */
3922   private static void logThrow(Level level, String className, String methodName,
3923       String message, Object[] args, Throwable thrown)
3924   {
3925     java.util.logging.LogRecord lr = new java.util.logging.LogRecord(level, message);
3926     lr.setLoggerName(logger.getName());
3927     lr.setSourceClassName(className);
3928     lr.setSourceMethodName(methodName);
3929     lr.setParameters(args);
3930     lr.setThrown(thrown);
3931     logger.log(lr);
3932   }
3933 
3934   /**
3935    * Add a service to our state.  This includes putting it in the
3936    * serviceByID map under the serviceID, in the serviceByTime map,
3937    * in the serviceByType map under the service's most-specific
3938    * ServiceType, and in the serviceByAttr map under all of its
3939    * attribute values, incrementing the number of instances of each
3940    * EntryClass, and updating entryClasses as necessary.  If this is
3941    * the first instance of that ServiceType registered, then we need
3942    * to add concrete class information to the type and all supertypes.
3943    * 
3944    * ** GREG ** Adding it to a group if necessary.
3945    */
3946   private void addService(SvcReg reg)
3947   {
3948     serviceByID.put(reg.item.serviceID, reg);
3949     serviceByTime.put(reg, reg);
3950     addServiceByTypes(reg.item.serviceType, reg);
3951     EntryRep[] entries = reg.item.attributeSets;
3952     for (int i = entries.length; --i >= 0;) {
3953       addAttrs(reg, entries[i]);
3954     }
3955     computeMaxLeases();
3956 
3957     // Add object to the group ** GREG **
3958     if (reg.item.gid != null) {
3959        ArrayList members = (ArrayList) groupMap.get(reg.item.gid);
3960        logger.info("reg.item.gid: " + reg.item.gid);
3961        if (members == null) {
3962           members = new ArrayList();
3963           groupMap.put(reg.item.gid, members);
3964        }
3965        members.add(reg.item);
3966     }     
3967   }
3968 
3969   /**
3970    * Delete given service, generating events as necessary.  This includes
3971    * deleting from the serviceByID, serviceByTime, serviceByType, and
3972    * serviceByAttr maps, decrementing the number of instances of each
3973    * EntryClass, and updating entryClasses as necessary.  If this is the
3974    * last registered instance of the service type, then we delete the
3975    * concrete class information from the type and all supertypes.
3976    * 
3977    * ** GREG ** Removing it from a group, if necessary.
3978    */
3979   private void deleteService(SvcReg reg, long now)
3980   {
3981     Item item = reg.item;
3982     generateEvents(item, null, now);
3983     serviceByID.remove(item.serviceID);
3984     serviceByTime.remove(reg);
3985     deleteServiceFromTypes(item.serviceType, reg);
3986     EntryRep[] entries = item.attributeSets;
3987     for (int i = entries.length; --i >= 0;) {
3988       deleteAttrs(reg, entries[i], false);
3989     }
3990     computeMaxLeases();
3991 
3992     // Remove object from group ** GREG **
3993     if (item.gid != null) {
3994        ArrayList members = (ArrayList) groupMap.get(item.gid);
3995        int pos = members.indexOf(item);
3996        if (pos >= 0)
3997           members.remove(pos);
3998        if (members.size() == 0)
3999           groupMap.remove(item.gid);
4000     }
4001   }
4002 
4003   /**
4004    * Add an event registration to our state.  This includes adding a
4005    * template of each EntryClass, putting the registration in the
4006    * eventByID map, in the eventByTime map, and in either
4007    * subEventByService (if the template is for a specific service id)
4008    * or subEventByID.  Since we expect in most cases there will only
4009    * ever be a single event registration for a given service id, we
4010    * avoid creating a singleton array in that case.
4011    */
4012   private void addEvent(EventReg reg)
4013   {
4014     if (reg.listener == null)
4015       return; /* failed to recover from log */
4016     EntryRep[] tmpls = reg.tmpl.attributeSetTemplates;
4017     if (tmpls != null) {
4018       for (int i = tmpls.length; --i >= 0;) {
4019         EntryClass eclass = tmpls[i].eclass;
4020         eclass.setNumTemplates(eclass.getNumTemplates() + 1);
4021       }
4022     }
4023     Long id = new Long(reg.eventID);
4024     eventByID.put(id, reg);
4025     eventByTime.put(reg, reg);
4026     if (reg.tmpl.serviceID != null) {
4027       Object val = subEventByService.get(reg.tmpl.serviceID);
4028       if (val == null)
4029         val = reg;
4030       else if (val instanceof EventReg)
4031         val = new EventReg[] { (EventReg) val, reg };
4032       else
4033         val = arrayAdd((EventReg[]) val, reg);
4034       subEventByService.put(reg.tmpl.serviceID, val);
4035     } else {
4036       subEventByID.put(id, reg);
4037     }
4038     computeMaxLeases();
4039   }
4040 
4041   /**
4042    * Remove an event registration from our state.  This includes deleting
4043    * a template of each EntryClass, deleting the registration from the
4044    * eventByID map, the eventByTime map, and either the subEventByService
4045    * or subEventByID map.
4046    */
4047   private void deleteEvent(EventReg reg)
4048   {
4049     EntryRep[] tmpls = reg.tmpl.attributeSetTemplates;
4050     if (tmpls != null) {
4051       for (int i = tmpls.length; --i >= 0;) {
4052         EntryClass eclass = tmpls[i].eclass;
4053         eclass.setNumTemplates(eclass.getNumTemplates() - 1);
4054       }
4055     }
4056     Long id = new Long(reg.eventID);
4057     eventByID.remove(id);
4058     eventByTime.remove(reg);
4059     if (reg.tmpl.serviceID != null) {
4060       Object val = subEventByService.get(reg.tmpl.serviceID);
4061       if (val == reg) {
4062         subEventByService.remove(reg.tmpl.serviceID);
4063       } else {
4064         Object[] array = (EventReg[]) val;
4065         array = arrayDel(array, indexOf(array, reg));
4066         if (array.length == 1)
4067           val = array[0];
4068         else
4069           val = array;
4070         subEventByService.put(reg.tmpl.serviceID, val);
4071       }
4072     } else {
4073       subEventByID.remove(id);
4074     }
4075     computeMaxLeases();
4076   }
4077 
4078   /**
4079    * Put the service in the serviceByAttr map under all attribute values
4080    * in the given entry, or in the serviceByEmptyAttr map if the entry
4081    * has no attributes, add a new instance of the EntryClass, and update
4082    * entryClasses as necessary.
4083    */
4084   private void addAttrs(SvcReg reg, EntryRep entry)
4085   {
4086     EntryClass eclass = entry.eclass;
4087     addInstance(eclass);
4088     Object[] fields = entry.fields;
4089     if (fields.length > 0) {
4090       /* walk backwards to make getDefiningClass more efficient */
4091       for (int i = fields.length; --i >= 0;) {
4092         eclass = getDefiningClass(eclass, i);
4093         addAttr(reg, eclass, i, fields[i]);
4094       }
4095       return;
4096     }
4097     ArrayList regs = (ArrayList) serviceByEmptyAttr.get(eclass);
4098     if (regs == null) {
4099       regs = new ArrayList(2);
4100       regs.add(reg);
4101       serviceByEmptyAttr.put(eclass, regs);
4102     } else if (!regs.contains(reg)) {
4103       regs.add(reg);
4104     }
4105   }
4106 
4107   /**
4108    * If checkDups is false, delete the service (if present) from
4109    * serviceByAttr under all attribute values of the given entry or
4110    * from serviceByEmptyAttr if the entry has no attributes.
4111    * If checkDups is true, only delete for a given attribute value if the
4112    * service has no other entries of similar type that still have the
4113    * same value.  Either way, delete an instance of the EntryClass,
4114    * and update entryClasses as necessary.
4115    */
4116   private void deleteAttrs(SvcReg reg, EntryRep entry, boolean checkDups)
4117   {
4118     EntryClass eclass = entry.eclass;
4119     deleteInstance(eclass);
4120     Object[] fields = entry.fields;
4121     if (fields.length == 0) {
4122       ArrayList regs = (ArrayList) serviceByEmptyAttr.get(eclass);
4123       if (regs == null || (checkDups && hasEmptyAttr(reg, eclass)))
4124         return;
4125       int idx = regs.indexOf(reg);
4126       if (idx >= 0) {
4127         regs.remove(idx);
4128         if (regs.isEmpty())
4129           serviceByEmptyAttr.remove(eclass);
4130       }
4131       return;
4132     }
4133     /* walk backwards to make getDefiningClass more efficient */
4134     for (int fldidx = fields.length; --fldidx >= 0;) {
4135       eclass = getDefiningClass(eclass, fldidx);
4136       HashMap[] attrMaps = (HashMap[]) serviceByAttr.get(eclass);
4137       if (attrMaps == null || attrMaps[fldidx] == null
4138           || (checkDups && hasAttr(reg, eclass, fldidx, fields[fldidx])))
4139         continue;
4140       HashMap map = attrMaps[fldidx];
4141       Object value = fields[fldidx];
4142       ArrayList regs = (ArrayList) map.get(value);
4143       if (regs == null)
4144         continue;
4145       int idx = regs.indexOf(reg);
4146       if (idx < 0)
4147         continue;
4148       regs.remove(idx);
4149       if (!regs.isEmpty())
4150         continue;
4151       map.remove(value);
4152       if (!map.isEmpty())
4153         continue;
4154       attrMaps[fldidx] = null;
4155       if (allNull(attrMaps))
4156         serviceByAttr.remove(eclass);
4157     }
4158   }
4159 
4160   /**
4161    * Store all non-null elements of values into the given entry,
4162    * and update serviceByAttr to match.
4163    */
4164   private void updateAttrs(SvcReg reg, EntryRep entry, Object[] values)
4165   {
4166     EntryClass eclass = entry.eclass;
4167     /* walk backwards to make getDefiningClass more efficient */
4168     for (int fldidx = values.length; --fldidx >= 0;) {
4169       Object oval = entry.fields[fldidx];
4170       Object nval = values[fldidx];
4171       if (nval != null && !nval.equals(oval)) {
4172         eclass = getDefiningClass(eclass, fldidx);
4173         HashMap map = addAttr(reg, eclass, fldidx, nval);
4174         entry.fields[fldidx] = nval;
4175         if (hasAttr(reg, eclass, fldidx, oval))
4176           continue;
4177         ArrayList regs = (ArrayList) map.get(oval);
4178         regs.remove(regs.indexOf(reg));
4179         if (regs.isEmpty())
4180           map.remove(oval); /* map cannot become empty */
4181       }
4182     }
4183   }
4184 
4185   /**
4186    * Put the service under the given attribute value for the given
4187    * defining class and field, if it isn't already there.  Return
4188    * the HashMap for the given class and field.
4189    */
4190   private HashMap addAttr(SvcReg reg, EntryClass eclass, int fldidx, Object value)
4191   {
4192     HashMap[] attrMaps = (HashMap[]) serviceByAttr.get(eclass);
4193     if (attrMaps == null) {
4194       attrMaps = new HashMap[eclass.getNumFields()];
4195       serviceByAttr.put(eclass, attrMaps);
4196     }
4197     HashMap map = attrMaps[fldidx];
4198     if (map == null) {
4199       map = new HashMap(11);
4200       attrMaps[fldidx] = map;
4201     }
4202     ArrayList regs = (ArrayList) map.get(value);
4203     if (regs == null) {
4204       regs = new ArrayList(3);
4205       map.put(value, regs);
4206     } else if (regs.contains(reg))
4207       return map;
4208     regs.add(reg);
4209     return map;
4210   }
4211 
4212   /**
4213    * Add an instance of the EntryClass, and add the class to entryClasses
4214    * if this is the first such instance.
4215    */
4216   private void addInstance(EntryClass eclass)
4217   {
4218     int idx = entryClasses.indexOf(eclass);
4219     if (idx < 0) {
4220       entryClasses.add(eclass);
4221       idx = entryClasses.size() - 1;
4222     }
4223     eclass = (EntryClass) entryClasses.get(idx);
4224     eclass.setNumInstances(eclass.getNumInstances() + 1);
4225   }
4226 
4227   /**
4228    * Delete an instance of the EntryClass, and remove the class from
4229    * entryClasses if this is the last such instance.
4230    */
4231   private void deleteInstance(EntryClass eclass)
4232   {
4233     int idx = entryClasses.indexOf(eclass);
4234     eclass = (EntryClass) entryClasses.get(idx);
4235     int num = eclass.getNumInstances() - 1;
4236     if (num == 0)
4237       entryClasses.remove(idx);
4238     eclass.setNumInstances(num);
4239   }
4240 
4241   /** Return an appropriate iterator for Items matching the Template. */
4242   private ItemIter matchingItems(Template tmpl)
4243   {
4244     if (tmpl.serviceID != null)
4245       return new IDItemIter(tmpl);
4246     if (!isEmpty(tmpl.serviceTypes))
4247       return new SvcIterator(tmpl);
4248     EntryRep[] sets = tmpl.attributeSetTemplates;
4249     if (isEmpty(sets))
4250       return new AllItemIter();
4251     for (int i = sets.length; --i >= 0;) {
4252       Object[] fields = sets[i].fields;
4253       if (fields.length == 0) {
4254         EntryClass eclass = getEmptyEntryClass(sets[i].eclass);
4255         if (eclass != null)
4256           return new EmptyAttrItemIter(tmpl, eclass);
4257       } else {
4258         /* try subclass fields before superclass fields */
4259         for (int j = fields.length; --j >= 0;) {
4260           if (fields[j] != null)
4261             return new AttrItemIter(tmpl, i, j);
4262         }
4263       }
4264     }
4265     return new ClassItemIter(tmpl);
4266   }
4267 
4268   /**
4269    * Return member of entryClasses that is equal to or a subclass of
4270    * the specified class, provided there is exactly one such member
4271    * and it has no fields.
4272    */
4273   private EntryClass getEmptyEntryClass(EntryClass eclass)
4274   {
4275     EntryClass match = null;
4276     for (int i = entryClasses.size(); --i >= 0;) {
4277       EntryClass cand = (EntryClass) entryClasses.get(i);
4278       if (eclass.isAssignableFrom(cand)) {
4279         if (cand.getNumFields() != 0 || match != null)
4280           return null;
4281         match = cand;
4282       }
4283     }
4284     return match;
4285   }
4286 
4287   /** Returns a list of services that match all types passed in */
4288   private ArrayList matchingServices(ServiceType[] types)
4289   {
4290     ArrayList matches = new ArrayList();
4291     if (isEmpty(types)) {
4292       Map map = (Map) serviceByTypeName.get(objectServiceType.getName());
4293       matches.addAll(map.values());
4294     } else {
4295       Map map = (Map) serviceByTypeName.get(types[0].getName());
4296       if (map != null)
4297         matches.addAll(map.values());
4298       if (types.length > 1) {
4299         for (Iterator it = matches.iterator(); it.hasNext();) {
4300           SvcReg reg = (SvcReg) it.next();
4301           if (!matchType(types, reg.item.serviceType))
4302             it.remove();
4303         }
4304       }
4305     }
4306     return matches;
4307   }
4308 
4309   /** Return any valid codebase for an entry class that has instances. */
4310   private String pickCodebase(EntryClass eclass, long now) throws ClassNotFoundException
4311   {
4312     if (eclass.getNumFields() == 0)
4313       return pickCodebase(eclass, (ArrayList) serviceByEmptyAttr.get(eclass), now);
4314     int fldidx = eclass.getNumFields() - 1;
4315     HashMap[] attrMaps = (HashMap[]) serviceByAttr.get(getDefiningClass(eclass, fldidx));
4316     for (Iterator iter = attrMaps[fldidx].values().iterator(); iter.hasNext();) {
4317       try {
4318         return pickCodebase(eclass, (ArrayList) iter.next(), now);
4319       } catch (ClassNotFoundException e) {
4320       }
4321     }
4322     throw new ClassNotFoundException();
4323   }
4324 
4325   /** Return any valid codebase for an entry of the exact given class. */
4326   private String pickCodebase(EntryClass eclass, ArrayList svcs, long now)
4327       throws ClassNotFoundException
4328   {
4329     for (int i = svcs.size(); --i >= 0;) {
4330       SvcReg reg = (SvcReg) svcs.get(i);
4331       if (reg.leaseExpiration <= now)
4332         continue;
4333       EntryRep[] sets = reg.item.attributeSets;
4334       for (int j = sets.length; --j >= 0;) {
4335         if (eclass.equals(sets[j].eclass))
4336           return sets[j].codebase;
4337       }
4338     }
4339     throw new ClassNotFoundException();
4340   }
4341 
4342   /**
4343    * Compute new maxServiceLease and maxEventLease values.  This needs to
4344    * be called whenever the number of services (#S) or number of events
4345    * (#E) changes, or whenever any of the configuration parameters change.
4346    * The two base equations driving the computation are:
4347    *     #S/maxServiceLease + #E/maxEventLease <= 1/minRenewalInterval
4348    *     maxServiceLease/maxEventLease = minMaxServiceLease/minMaxEventLease
4349    */
4350   private void computeMaxLeases()
4351   {
4352     if (inRecovery)
4353       return;
4354     maxServiceLease = Math.max(minMaxServiceLease, minRenewalInterval
4355         * (serviceByID.size() + ((eventByID.size() * minMaxServiceLease) / minMaxEventLease)));
4356     maxEventLease = Math.max(minMaxEventLease,
4357         ((maxServiceLease * minMaxEventLease) / minMaxServiceLease));
4358   }
4359 
4360   /** Process a unicast discovery request, and respond. */
4361   private void respond(Socket socket) throws Exception
4362   {
4363     try {
4364       try {
4365         socket.setTcpNoDelay(true);
4366         socket.setKeepAlive(true);
4367       } catch (SocketException e) {
4368         if (logger.isLoggable(Levels.HANDLED))
4369           logger.log(Levels.HANDLED, "problem setting socket options", e);
4370       }
4371       socket.setSoTimeout(unicastDiscoveryConstraints
4372           .getUnicastSocketTimeout(DEFAULT_SOCKET_TIMEOUT));
4373       int pv = new DataInputStream(socket.getInputStream()).readInt();
4374       unicastDiscoveryConstraints.checkProtocolVersion(pv);
4375       getDiscovery(pv).handleUnicastDiscovery(
4376           new UnicastResponse(myLocator.getHost(), myLocator.getPort(), memberGroups, proxy),
4377           socket, unicastDiscoveryConstraints.getUnfulfilledConstraints(),
4378           unicastDiscoverySubjectChecker, Collections.EMPTY_LIST);
4379     } finally {
4380       try {
4381         socket.close();
4382       } catch (IOException e) {
4383         logger.log(Levels.HANDLED, "exception closing socket", e);
4384       }
4385     }
4386   }
4387 
4388   /** Returns Discovery instance implementing the given protocol version */
4389   private Discovery getDiscovery(int version) throws DiscoveryProtocolException
4390   {
4391     switch (version) {
4392       case Discovery.PROTOCOL_VERSION_1:
4393         return Discovery.getProtocol1();
4394       case Discovery.PROTOCOL_VERSION_2:
4395         return protocol2;
4396       default:
4397         throw new DiscoveryProtocolException("unsupported protocol version: " + version);
4398     }
4399   }
4400 
4401   /** Close any sockets that were sitting in the task queue. */
4402   private void closeRequestSockets(ArrayList tasks)
4403   {
4404     for (int i = tasks.size(); --i >= 0;) {
4405       Object obj = tasks.get(i);
4406       if (obj instanceof SocketTask) {
4407         try {
4408           ((SocketTask) obj).socket.close();
4409         } catch (IOException e) {
4410         }
4411       }
4412     }
4413   }
4414 
4415   /** Post-login (if login configured) initialization. */
4416   private void init(Configuration config, ActivationID activationID, boolean persistent,
4417       LifeCycle lifeCycle) throws IOException, ConfigurationException, ActivationException
4418   {
4419     this.lifeCycle = lifeCycle;
4420 
4421     /* persistence-specific initialization */
4422     if (persistent) {
4423       persistenceSnapshotThreshold = Config.getIntEntry(config, COMPONENT,
4424           "persistenceSnapshotThreshold", persistenceSnapshotThreshold, 0, Integer.MAX_VALUE);
4425       String persistenceDirectory = (String) config.getEntry(COMPONENT,
4426           "persistenceDirectory", String.class);
4427       recoveredListenerPreparer = (ProxyPreparer) Config.getNonNullEntry(config, COMPONENT,
4428           "recoveredListenerPreparer", ProxyPreparer.class, recoveredListenerPreparer);
4429       recoveredLocatorPreparer = (ProxyPreparer) Config.getNonNullEntry(config, COMPONENT,
4430           "recoveredLocatorPreparer", ProxyPreparer.class, recoveredLocatorPreparer);
4431       persistenceSnapshotWeight = Config.getFloatEntry(config, COMPONENT,
4432           "persistenceSnapshotWeight", persistenceSnapshotWeight, 0F, Float.MAX_VALUE);
4433 
4434       log = new ReliableLog(persistenceDirectory, new LocalLogHandler());
4435       if (logger.isLoggable(Level.CONFIG)) {
4436         logger.log(Level.CONFIG, "using persistence directory {0}",
4437             new Object[] { persistenceDirectory });
4438       }
4439       inRecovery = true;
4440       log.recover();
4441       inRecovery = false;
4442     } else {
4443       log = null;
4444     }
4445 
4446     /* activation-specific initialization */
4447     if (activationID != null) {
4448       ProxyPreparer activationIdPreparer = (ProxyPreparer) Config.getNonNullEntry(config,
4449           COMPONENT, "activationIdPreparer", ProxyPreparer.class, new BasicProxyPreparer());
4450       ProxyPreparer activationSystemPreparer = (ProxyPreparer) Config
4451           .getNonNullEntry(config, COMPONENT, "activationSystemPreparer", ProxyPreparer.class,
4452               new BasicProxyPreparer());
4453 
4454       this.activationID = (ActivationID) activationIdPreparer.prepareProxy(activationID);
4455       activationSystem = (ActivationSystem) activationSystemPreparer
4456           .prepareProxy(ActivationGroup.getSystem());
4457 
4458       serverExporter = (Exporter) Config.getNonNullEntry(config, COMPONENT, "serverExporter",
4459           Exporter.class, new ActivationExporter(this.activationID, new BasicJeriExporter(
4460               TcpServerEndpoint.getInstance(0), new BasicILFactory())), this.activationID);
4461     } else {
4462       this.activationID = null;
4463       activationSystem = null;
4464 
4465       serverExporter = (Exporter) Config.getNonNullEntry(config, COMPONENT, "serverExporter",
4466           Exporter.class, new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
4467               new BasicILFactory()));
4468     }
4469 
4470     /* fetch "initial*" config entries, if first time starting up */
4471     if (!recoveredSnapshot) {
4472       Entry[] initialLookupAttributes = (Entry[]) config.getEntry(COMPONENT,
4473           "initialLookupAttributes", Entry[].class, new Entry[0]);
4474       lookupGroups = (String[]) config.getEntry(COMPONENT, "initialLookupGroups",
4475           String[].class, lookupGroups);
4476       lookupLocators = (LookupLocator[]) config.getEntry(COMPONENT, "initialLookupLocators",
4477           LookupLocator[].class, lookupLocators);
4478       memberGroups = (String[]) config.getEntry(COMPONENT, "initialMemberGroups",
4479           String[].class, memberGroups);
4480       if (memberGroups == null) {
4481         throw new ConfigurationException("member groups cannot be ALL_GROUPS (null)");
4482       }
4483       memberGroups = (String[]) removeDups(memberGroups);
4484       unicastPort = Config.getIntEntry(config, COMPONENT, "initialUnicastDiscoveryPort",
4485           unicastPort, 0, 0xFFFF);
4486 
4487       if (initialLookupAttributes != null && initialLookupAttributes.length > 0) {
4488         List l = new ArrayList(Arrays.asList(baseAttrs));
4489         l.addAll(Arrays.asList(initialLookupAttributes));
4490         lookupAttrs = (Entry[]) l.toArray(new Entry[l.size()]);
4491       } else {
4492         lookupAttrs = baseAttrs;
4493       }
4494     }
4495 
4496     /* fetch remaining config entries */
4497     MethodConstraints discoveryConstraints = (MethodConstraints) config.getEntry(COMPONENT,
4498         "discoveryConstraints", MethodConstraints.class, null);
4499     if (discoveryConstraints == null) {
4500       discoveryConstraints = new BasicMethodConstraints(InvocationConstraints.EMPTY);
4501     }
4502     try {
4503       discoer = (DiscoveryManagement) config.getEntry(COMPONENT, "discoveryManager",
4504           DiscoveryManagement.class);
4505     } catch (NoSuchEntryException e) {
4506       discoer = new LookupDiscoveryManager(DiscoveryGroupManagement.NO_GROUPS, null, null,
4507           config);
4508     }
4509     listenerPreparer = (ProxyPreparer) Config.getNonNullEntry(config, COMPONENT,
4510         "listenerPreparer", ProxyPreparer.class, listenerPreparer);
4511     locatorPreparer = (ProxyPreparer) Config.getNonNullEntry(config, COMPONENT,
4512         "locatorPreparer", ProxyPreparer.class, locatorPreparer);
4513     minMaxEventLease = Config.getLongEntry(config, COMPONENT, "minMaxEventLease",
4514         minMaxEventLease, 1, MAX_LEASE);
4515     minMaxServiceLease = Config.getLongEntry(config, COMPONENT, "minMaxServiceLease",
4516         minMaxServiceLease, 1, MAX_LEASE);
4517     minRenewalInterval = Config.getLongEntry(config, COMPONENT, "minRenewalInterval",
4518         minRenewalInterval, 0, MAX_RENEW);
4519     multicastAnnouncementInterval = Config.getLongEntry(config, COMPONENT,
4520         "multicastAnnouncementInterval", multicastAnnouncementInterval, 1, Long.MAX_VALUE);
4521 
4522     multicastInterfaceRetryInterval = Config.getIntEntry(config, COMPONENT,
4523         "multicastInterfaceRetryInterval", multicastInterfaceRetryInterval, 1,
4524         Integer.MAX_VALUE);
4525     try {
4526       multicastInterfaces = (NetworkInterface[]) config.getEntry(COMPONENT,
4527           "multicastInterfaces", NetworkInterface[].class);
4528       multicastInterfacesSpecified = true;
4529     } catch (NoSuchEntryException e) {
4530       Enumeration en = NetworkInterface.getNetworkInterfaces();
4531       List l = (en != null) ? Collections.list(en) : Collections.EMPTY_LIST;
4532       multicastInterfaces = (NetworkInterface[]) l.toArray(new NetworkInterface[l.size()]);
4533       multicastInterfacesSpecified = false;
4534     }
4535     if (multicastInterfaces == null) {
4536       logger.config("using system default interface for multicast");
4537     } else if (multicastInterfaces.length == 0) {
4538       if (multicastInterfacesSpecified) {
4539         logger.config("multicast disabled");
4540       } else {
4541         logger.severe("no network interfaces detected");
4542       }
4543     } else if (logger.isLoggable(Level.CONFIG)) {
4544       logger.log(Level.CONFIG, "multicasting on interfaces {0}", new Object[] { Arrays
4545           .asList(multicastInterfaces) });
4546     }
4547 
4548     try {
4549       multicastRequestSubjectChecker = (ClientSubjectChecker) Config.getNonNullEntry(config,
4550           COMPONENT, "multicastRequestSubjectChecker", ClientSubjectChecker.class);
4551     } catch (NoSuchEntryException e) {
4552       // leave null
4553     }
4554     resourceIdGenerator = (UuidGenerator) Config.getNonNullEntry(config, COMPONENT,
4555         "resourceIdGenerator", UuidGenerator.class, resourceIdGenerator);
4556     serviceIdGenerator = (UuidGenerator) Config.getNonNullEntry(config, COMPONENT,
4557         "serviceIdGenerator", UuidGenerator.class, serviceIdGenerator);
4558     tasker = (TaskManager) Config.getNonNullEntry(config, COMPONENT, "taskManager",
4559         TaskManager.class, new TaskManager(50, 1000 * 15, 1.0F));
4560     unexportTimeout = Config.getLongEntry(config, COMPONENT, "unexportTimeout",
4561         unexportTimeout, 0, Long.MAX_VALUE);
4562     unexportWait = Config.getLongEntry(config, COMPONENT, "unexportWait", unexportWait, 0,
4563         Long.MAX_VALUE);
4564     String unicastDiscoveryHost;
4565     try {
4566       unicastDiscoveryHost = (String) Config.getNonNullEntry(config, COMPONENT,
4567           "unicastDiscoveryHost", String.class);
4568     } catch (NoSuchEntryException e) {
4569       // fix for 4906732: only invoke getCanonicalHostName if needed
4570       unicastDiscoveryHost = InetAddress.getLocalHost().getCanonicalHostName();
4571     }
4572     try {
4573       unicastDiscoverySubjectChecker = (ClientSubjectChecker) Config.getNonNullEntry(config,
4574           COMPONENT, "unicastDiscoverySubjectChecker", ClientSubjectChecker.class);
4575     } catch (NoSuchEntryException e) {
4576       // leave null
4577     }
4578 
4579     /* initialize state based on recovered/configured values */
4580     objectServiceType = new ServiceType(Object.class, null, null);
4581     computeMaxLeases();
4582     protocol2 = Discovery.getProtocol2(null);
4583     /* cache unprocessed unicastDiscovery constraints to handle
4584      reprocessing of time constraints associated with that method */
4585     rawUnicastDiscoveryConstraints = discoveryConstraints
4586         .getConstraints(DiscoveryConstraints.unicastDiscoveryMethod);
4587     multicastRequestConstraints = DiscoveryConstraints.process(discoveryConstraints
4588         .getConstraints(DiscoveryConstraints.multicastRequestMethod));
4589     multicastAnnouncementConstraints = DiscoveryConstraints.process(discoveryConstraints
4590         .getConstraints(DiscoveryConstraints.multicastAnnouncementMethod));
4591     unicastDiscoveryConstraints = DiscoveryConstraints.process(rawUnicastDiscoveryConstraints);
4592     serviceExpirer = new ServiceExpireThread();
4593     eventExpirer = new EventExpireThread();
4594     unicaster = new UnicastThread(unicastPort);
4595     multicaster = new MulticastThread();
4596     announcer = new AnnounceThread();
4597     snapshotter = new SnapshotThread();
4598     if (myServiceID == null) {
4599       myServiceID = newServiceID();
4600     }
4601     myRef = (Registrar) serverExporter.export(this);
4602     proxy = RegistrarProxy.getInstance(myRef, myServiceID);
4603     myLocator = (proxy instanceof RemoteMethodControl) ? new ConstrainableLookupLocator(
4604         unicastDiscoveryHost, unicaster.port, null) : new LookupLocator(unicastDiscoveryHost,
4605         unicaster.port);
4606     /* register myself */
4607     Item item = new Item(new ServiceItem(myServiceID, proxy, lookupAttrs));
4608     SvcReg reg = new SvcReg(item, myLeaseID, Long.MAX_VALUE);
4609     addService(reg);
4610     if (log != null) {
4611       log.snapshot();
4612     }
4613 
4614     try {
4615       DiscoveryGroupManagement dgm = (DiscoveryGroupManagement) discoer;
4616       String[] groups = dgm.getGroups();
4617       if (groups == null || groups.length > 0) {
4618         throw new ConfigurationException("discoveryManager must be initially configured with "
4619             + "no groups");
4620       }
4621       DiscoveryLocatorManagement dlm = (DiscoveryLocatorManagement) discoer;
4622       if (dlm.getLocators().length > 0) {
4623         throw new ConfigurationException("discoveryManager must be initially configured with "
4624             + "no locators");
4625       }
4626       dgm.setGroups(lookupGroups);
4627       dlm.setLocators(lookupLocators);
4628     } catch (ClassCastException e) {
4629       throw new ConfigurationException(null, e);
4630     }
4631     joiner = new JoinManager(proxy, lookupAttrs, myServiceID, discoer, null, config);
4632 
4633     /* start up all the daemon threads */
4634     serviceExpirer.start();
4635     eventExpirer.start();
4636     unicaster.start();
4637     multicaster.start();
4638     announcer.start();
4639     snapshotter.start();
4640     if (logger.isLoggable(Level.INFO)) {
4641       logger.log(Level.INFO, "started Reggie: {0}, {1}, {2}", new Object[] { myServiceID,
4642           Arrays.asList(memberGroups), myLocator });
4643     }
4644     ready.ready();
4645   }
4646 
4647   /** The code that does the real work of register. */
4648   private ServiceRegistration registerDo(Item nitem, long leaseDuration)
4649   {
4650     if (nitem.service == null)
4651       throw new NullPointerException("null service");
4652     if (myServiceID.equals(nitem.serviceID))
4653       throw new IllegalArgumentException("reserved service id");
4654     if (nitem.attributeSets == null)
4655       nitem.attributeSets = emptyAttrs;
4656     else
4657       nitem.attributeSets = (EntryRep[]) removeDups(nitem.attributeSets);
4658     leaseDuration = limitDuration(leaseDuration, maxServiceLease);
4659     long now = System.currentTimeMillis();
4660     if (nitem.serviceID == null) {
4661       /* new service, match on service object */
4662       Map svcs = (Map) serviceByTypeName.get(nitem.serviceType.getName());
4663       if (svcs != null) {
4664         for (Iterator it = svcs.values().iterator(); it.hasNext();) {
4665           SvcReg reg = (SvcReg) it.next();
4666           if (nitem.service.equals(reg.item.service)) {
4667             nitem.serviceID = reg.item.serviceID;
4668             deleteService(reg, now);
4669             break;
4670           }
4671         }
4672       }
4673       if (nitem.serviceID == null)
4674         nitem.serviceID = newServiceID();
4675     } else {
4676       /* existing service, match on service id */
4677       SvcReg reg = (SvcReg) serviceByID.get(nitem.serviceID);
4678       if (reg != null)
4679         deleteService(reg, now);
4680     }
4681     Util.checkRegistrantServiceID(nitem.serviceID, logger, Level.FINE);
4682     SvcReg reg = new SvcReg(nitem, newLeaseID(), now + leaseDuration);
4683     addService(reg);
4684     generateEvents(null, nitem, now);
4685     addLogRecord(new SvcRegisteredLogObj(reg));
4686     queueEvents();
4687     /* see if the expire thread needs to wake up earlier */
4688     if (reg.leaseExpiration < minSvcExpiration) {
4689       minSvcExpiration = reg.leaseExpiration;
4690       concurrentObj.waiterNotify(serviceNotifier);
4691     }
4692     return Registration.getInstance(myRef, ServiceLease.getInstance(myRef, myServiceID,
4693         nitem.serviceID, reg.leaseID, reg.leaseExpiration));
4694   }
4695 
4696   /**
4697    * The code that does the real work of lookup.  As a special case,
4698    * if the template specifies at least one service type to match,
4699    * and there are multiple items that match the template, then we
4700    * make a random pick among them, in an attempt to load balance
4701    * use of "equivalent" services and avoid starving any of them.
4702    * 
4703    * ** GREG ** Changed to return an array of service objects, if necessary.
4704    */
4705   private MarshalledWrapper[] lookupDo(Template tmpl)
4706   {
4707     // Return a group if necessary ** GREG **
4708     if (tmpl.gid != null) {
4709        ArrayList memberList = (ArrayList) groupMap.get(tmpl.gid);
4710        if (memberList != null && memberList.size() > 0) {
4711           MarshalledWrapper[] members = new MarshalledWrapper[memberList.size()+1];
4712           for (int i=0; i < members.length-1; i++)
4713              members[i] = ((Item) memberList.get(i)).service;
4714           return members;
4715        }
4716     }
4717 
4718     if (isEmpty(tmpl.serviceTypes) || tmpl.serviceID != null) {
4719       ItemIter iter = matchingItems(tmpl);
4720       if (iter.hasNext())
4721         return new MarshalledWrapper[] {iter.next().service};
4722       return null;
4723 
4724     }
4725     List services = matchingServices(tmpl.serviceTypes);
4726     long now = System.currentTimeMillis();
4727     int slen = services.size();
4728     if (slen == 0)
4729       return null;
4730     int srand = Math.abs(random.nextInt()) % slen;
4731     for (int i = 0; i < slen; i++) {
4732       SvcReg reg = (SvcReg) services.get((i + srand) % slen);
4733       if (reg.leaseExpiration > now && matchAttributes(tmpl, reg.item))
4734         return new MarshalledWrapper[] {reg.item.service};
4735     }
4736     return null;
4737   }
4738 
4739   /**
4740    * The code that does the real work of lookup.  We do a deep copy of the
4741    * items being returned, both to avoid having them modified while being
4742    * marshalled (by a concurrent update method), and to substitute
4743    * replacements for embedded EntryClass and ServiceType instances, to
4744    * minimize data sent back to the client.  If duplicates are possible
4745    * from the iterator, we save all matches, weeding out duplicates as we
4746    * go, then trim to maxMatches and deep copy.
4747    */
4748   private Matches lookupDo(Template tmpl, int maxMatches)
4749   {
4750     if (maxMatches < 0)
4751       throw new IllegalArgumentException("negative maxMatches");
4752     int totalMatches = 0;
4753     ArrayList matches = null;
4754     ItemIter iter = matchingItems(tmpl);
4755     if (maxMatches > 0 || iter.dupsPossible)
4756       matches = new ArrayList();
4757     if (iter.dupsPossible) {
4758       while (iter.hasNext()) {
4759         Item item = iter.next();
4760         if (!matches.contains(item))
4761           matches.add(item);
4762       }
4763       totalMatches = matches.size();
4764       if (maxMatches > 0) {
4765         for (int i = matches.size(); --i >= maxMatches;)
4766           matches.remove(i);
4767         for (int i = matches.size(); --i >= 0;) {
4768           matches.set(i, copyItem((Item) matches.get(i)));
4769         }
4770       } else {
4771         matches = null;
4772       }
4773     } else {
4774       while (iter.hasNext()) {
4775         Item item = iter.next();
4776         totalMatches++;
4777         if (--maxMatches >= 0)
4778           matches.add(copyItem(item));
4779       }
4780     }
4781     return new Matches(matches, totalMatches);
4782   }
4783 
4784   /**
4785    * The code that does the real work of notify.
4786    * Every registration is given a unique event id.  The event id
4787    * can thus also serve as a lease id.
4788    *
4789    */
4790   private EventRegistration notifyDo(Template tmpl, int transitions,
4791       RemoteEventListener listener, MarshalledObject handback, long leaseDuration)
4792       throws RemoteException
4793   {
4794     if (transitions == 0
4795         || transitions != (transitions & (ServiceRegistrar.TRANSITION_MATCH_NOMATCH
4796             | ServiceRegistrar.TRANSITION_NOMATCH_MATCH | ServiceRegistrar.TRANSITION_MATCH_MATCH)))
4797       throw new IllegalArgumentException("invalid transitions");
4798     if (listener == null)
4799       throw new NullPointerException("listener");
4800     listener = (RemoteEventListener) listenerPreparer.prepareProxy(listener);
4801     leaseDuration = limitDuration(leaseDuration, maxEventLease);
4802     long now = System.currentTimeMillis();
4803     EventReg reg = new EventReg(eventID, newLeaseID(), tmpl, transitions, listener, handback,
4804         now + leaseDuration);
4805     eventID++;
4806     addEvent(reg);
4807     addLogRecord(new EventRegisteredLogObj(reg));
4808     /* see if the expire thread needs to wake up earlier */
4809     if (reg.leaseExpiration < minEventExpiration) {
4810       minEventExpiration = reg.leaseExpiration;
4811       concurrentObj.waiterNotify(eventNotifier);
4812     }
4813     return new EventRegistration(reg.eventID, proxy, EventLease.getInstance(myRef,
4814         myServiceID, reg.eventID, reg.leaseID, reg.leaseExpiration), reg.seqNo);
4815   }
4816 
4817   /**
4818    * The code that does the real work of getEntryClasses. If the
4819    * template is empty, then we can just use entryClasses, without
4820    * having to iterate over items, but we have to work harder to
4821    * get codebases.
4822    */
4823   private EntryClassBase[] getEntryClassesDo(Template tmpl)
4824   {
4825     ArrayList classes = new ArrayList();
4826     ArrayList codebases = new ArrayList();
4827     if (tmpl.serviceID == null && isEmpty(tmpl.serviceTypes)
4828         && isEmpty(tmpl.attributeSetTemplates)) {
4829       long now = System.currentTimeMillis();
4830       for (int i = entryClasses.size(); --i >= 0;) {
4831         EntryClass eclass = (EntryClass) entryClasses.get(i);
4832         try {
4833           codebases.add(pickCodebase(eclass, now));
4834           classes.add(eclass);
4835         } catch (ClassNotFoundException e) {
4836         }
4837       }
4838     } else {
4839       for (ItemIter iter = matchingItems(tmpl); iter.hasNext();) {
4840         Item item = iter.next();
4841         for (int i = item.attributeSets.length; --i >= 0;) {
4842           EntryRep attrSet = item.attributeSets[i];
4843           if (attrMatch(tmpl.attributeSetTemplates, attrSet)
4844               && !classes.contains(attrSet.eclass)) {
4845             classes.add(attrSet.eclass);
4846             codebases.add(attrSet.codebase);
4847           }
4848         }
4849       }
4850     }
4851     if (classes.isEmpty())
4852       return null; /* spec says null */
4853     EntryClassBase[] vals = new EntryClassBase[classes.size()];
4854     for (int i = vals.length; --i >= 0;) {
4855       vals[i] = new EntryClassBase(((EntryClass) classes.get(i)).getReplacement(),
4856           (String) codebases.get(i));
4857     }
4858     return vals;
4859   }
4860 
4861   /**
4862    * The code that does the real work of getFieldValues.  If the
4863    * template is just a singleton entry with all null fields, then
4864    * we can do a faster computation by iterating over keys in the
4865    * given attribute's serviceByAttr map, rather than iterating
4866    * over items.
4867    */
4868   private Object[] getFieldValuesDo(Template tmpl, int setidx, int fldidx)
4869   {
4870     ArrayList values = new ArrayList();
4871     EntryRep etmpl = tmpl.attributeSetTemplates[setidx];
4872     if (tmpl.serviceID == null && isEmpty(tmpl.serviceTypes)
4873         && tmpl.attributeSetTemplates.length == 1 && allNull(etmpl.fields)) {
4874       long now = System.currentTimeMillis();
4875       EntryClass eclass = getDefiningClass(etmpl.eclass, fldidx);
4876       boolean checkAttr = !eclass.equals(etmpl.eclass);
4877       HashMap[] attrMaps = (HashMap[]) serviceByAttr.get(eclass);
4878       if (attrMaps != null && attrMaps[fldidx] != null) {
4879         for (Iterator iter = attrMaps[fldidx].entrySet().iterator(); iter.hasNext();) {
4880           Map.Entry ent = (Map.Entry) iter.next();
4881           ArrayList regs = (ArrayList) ent.getValue();
4882           Object value = ent.getKey();
4883           for (int i = regs.size(); --i >= 0;) {
4884             SvcReg reg = (SvcReg) regs.get(i);
4885             if (reg.leaseExpiration > now
4886                 && (!checkAttr || hasAttr(reg, etmpl.eclass, fldidx, value))) {
4887               values.add(value);
4888               break;
4889             }
4890           }
4891         }
4892       }
4893     } else {
4894       for (ItemIter iter = matchingItems(tmpl); iter.hasNext();) {
4895         Item item = iter.next();
4896         for (int j = item.attributeSets.length; --j >= 0;) {
4897           if (matchEntry(etmpl, item.attributeSets[j])) {
4898             Object value = item.attributeSets[j].fields[fldidx];
4899             if (!values.contains(value))
4900               values.add(value);
4901           }
4902         }
4903       }
4904     }
4905     if (values.isEmpty())
4906       return null;
4907     return values.toArray();
4908   }
4909 
4910   /**
4911    * The code that does the real work of getServiceTypes.  If the
4912    * template has at most service types, then we can do a fast
4913    * computation based solely on concrete classes, without having
4914    * to iterate over items, but we have to work a bit harder to
4915    * get codebases.
4916    */
4917   private ServiceTypeBase[] getServiceTypesDo(Template tmpl, String prefix)
4918   {
4919     ArrayList classes = new ArrayList();
4920     ArrayList codebases = new ArrayList();
4921     if (tmpl.serviceID == null && isEmpty(tmpl.attributeSetTemplates)) {
4922       List services = matchingServices(tmpl.serviceTypes);
4923       for (Iterator it = services.iterator(); it.hasNext();) {
4924         Item item = ((SvcReg) it.next()).item;
4925         addTypes(classes, codebases, tmpl.serviceTypes, prefix, item.serviceType,
4926             item.codebase);
4927       }
4928     } else {
4929       for (ItemIter iter = matchingItems(tmpl); iter.hasNext();) {
4930         Item item = iter.next();
4931         addTypes(classes, codebases, tmpl.serviceTypes, prefix, item.serviceType,
4932             item.codebase);
4933       }
4934     }
4935     if (classes.isEmpty())
4936       return null; /* spec says null */
4937     ServiceTypeBase[] vals = new ServiceTypeBase[classes.size()];
4938     for (int i = vals.length; --i >= 0;) {
4939       vals[i] = new ServiceTypeBase(((ServiceType) classes.get(i)).getReplacement(),
4940           (String) codebases.get(i));
4941     }
4942     return vals;
4943   }
4944 
4945   /**
4946    * The code that does the real work of addAttributes.
4947    * Add every element of attrSets to item, updating serviceByAttr as
4948    * necessary, incrementing the number of EntryClass instances, and
4949    * updating entryClasses as necessary.
4950    */
4951   private void addAttributesDo(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSets)
4952       throws UnknownLeaseException
4953   {
4954     long now = System.currentTimeMillis();
4955     SvcReg reg = (SvcReg) serviceByID.get(serviceID);
4956     if (reg == null || !reg.leaseID.equals(leaseID) || reg.leaseExpiration <= now)
4957       throw new UnknownLeaseException();
4958     Item pre = (Item) reg.item.clone();
4959     EntryRep[] sets = reg.item.attributeSets;
4960     int i = 0;
4961     /* don't add if it's a duplicate */
4962     for (int j = 0; j < attrSets.length; j++) {
4963       EntryRep set = attrSets[j];
4964       if (indexOf(sets, set) < 0 && indexOf(attrSets, j, set) < 0) {
4965         attrSets[i++] = set;
4966         addAttrs(reg, set);
4967       }
4968     }
4969     if (i > 0) {
4970       int len = sets.length;
4971       EntryRep[] nsets = new EntryRep[len + i];
4972       System.arraycopy(sets, 0, nsets, 0, len);
4973       System.arraycopy(attrSets, 0, nsets, len, i);
4974       reg.item.attributeSets = nsets;
4975     }
4976     generateEvents(pre, reg.item, now);
4977   }
4978 
4979   /**
4980    * The code that does the real work of modifyAttributes.
4981    * Modify the attribute sets that match attrSetTmpls, updating
4982    * or deleting based on attrSets, updating serviceByAttr as necessary,
4983    * decrementing the number of EntryClass instances, and updating
4984    * entryClasses as necessary.
4985    */
4986   private void modifyAttributesDo(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSetTmpls,
4987       EntryRep[] attrSets) throws UnknownLeaseException
4988   {
4989     if (attrSetTmpls.length != attrSets.length)
4990       throw new IllegalArgumentException("attribute set length mismatch");
4991     for (int i = attrSets.length; --i >= 0;) {
4992       if (attrSets[i] != null && !attrSets[i].eclass.isAssignableFrom(attrSetTmpls[i].eclass))
4993         throw new IllegalArgumentException("attribute set type mismatch");
4994     }
4995     long now = System.currentTimeMillis();
4996     SvcReg reg = (SvcReg) serviceByID.get(serviceID);
4997     if (reg == null || !reg.leaseID.equals(leaseID) || reg.leaseExpiration <= now)
4998       throw new UnknownLeaseException();
4999     Item pre = (Item) reg.item.clone();
5000     EntryRep[] preSets = pre.attributeSets;
5001     EntryRep[] sets = reg.item.attributeSets;
5002     for (int i = preSets.length; --i >= 0;) {
5003       EntryRep preSet = preSets[i];
5004       EntryRep set = sets[i];
5005       for (int j = attrSetTmpls.length; --j >= 0;) {
5006         if (matchEntry(attrSetTmpls[j], preSet)) {
5007           EntryRep attrs = attrSets[j];
5008           if (attrs == null) {
5009             sets = deleteSet(reg.item, i);
5010             deleteAttrs(reg, set, true);
5011             break;
5012           } else {
5013             updateAttrs(reg, set, attrs.fields);
5014           }
5015         }
5016       }
5017     }
5018     for (int i = sets.length; --i >= 0;) {
5019       EntryRep set = sets[i];
5020       if (indexOf(sets, i, set) >= 0) {
5021         sets = deleteSet(reg.item, i);
5022         deleteInstance(set.eclass);
5023       }
5024     }
5025     reg.item.attributeSets = sets;
5026     generateEvents(pre, reg.item, now);
5027   }
5028 
5029   /**
5030    * The code that does the real work of setAttributes.
5031    * Replace all attributes of item with attrSets, updating serviceByAttr
5032    * as necessary, incrementing the number of EntryClass instances, and
5033    * updating entryClasses as necessary.
5034    */
5035   private void setAttributesDo(ServiceID serviceID, Uuid leaseID, EntryRep[] attrSets)
5036       throws UnknownLeaseException
5037   {
5038     if (attrSets == null)
5039       attrSets = emptyAttrs;
5040     else
5041       attrSets = (EntryRep[]) removeDups(attrSets);
5042     long now = System.currentTimeMillis();
5043     SvcReg reg = (SvcReg) serviceByID.get(serviceID);
5044     if (reg == null || !reg.leaseID.equals(leaseID) || reg.leaseExpiration <= now)
5045       throw new UnknownLeaseException();
5046     Item pre = (Item) reg.item.clone();
5047     EntryRep[] entries = reg.item.attributeSets;
5048     for (int i = entries.length; --i >= 0;) {
5049       deleteAttrs(reg, entries[i], false);
5050     }
5051     reg.item.attributeSets = attrSets;
5052     for (int i = attrSets.length; --i >= 0;) {
5053       addAttrs(reg, attrSets[i]);
5054     }
5055     generateEvents(pre, reg.item, now);
5056   }
5057 
5058   /** The code that does the real work of cancelServiceLease. */
5059   private void cancelServiceLeaseDo(ServiceID serviceID, Uuid leaseID)
5060       throws UnknownLeaseException
5061   {
5062     if (serviceID.equals(myServiceID))
5063       throw new SecurityException("privileged service id");
5064     long now = System.currentTimeMillis();
5065     SvcReg reg = (SvcReg) serviceByID.get(serviceID);
5066     if (reg == null || !reg.leaseID.equals(leaseID) || reg.leaseExpiration <= now)
5067       throw new UnknownLeaseException();
5068     deleteService(reg, now);
5069     /* wake up thread if this might be the (only) earliest time */
5070     if (reg.leaseExpiration == minSvcExpiration)
5071       concurrentObj.waiterNotify(serviceNotifier);
5072   }
5073 
5074   /** The code that does the real work of renewServiceLease. */
5075   private long renewServiceLeaseDo(ServiceID serviceID, Uuid leaseID, long renewDuration)
5076       throws UnknownLeaseException
5077   {
5078     long now = System.currentTimeMillis();
5079     long renewExpiration = renewServiceLeaseInt(serviceID, leaseID, renewDuration, now);
5080     addLogRecord(new ServiceLeaseRenewedLogObj(serviceID, leaseID, renewExpiration));
5081     return renewExpiration - now;
5082   }
5083 
5084   /** Renew a service lease for a relative duration from now. */
5085   private long renewServiceLeaseInt(ServiceID serviceID, Uuid leaseID, long renewDuration,
5086       long now) throws UnknownLeaseException
5087   {
5088     if (serviceID.equals(myServiceID))
5089       throw new SecurityException("privileged service id");
5090     if (renewDuration == Lease.ANY)
5091       renewDuration = maxServiceLease;
5092     else if (renewDuration < 0)
5093       throw new IllegalArgumentException("negative lease duration");
5094     SvcReg reg = (SvcReg) serviceByID.get(serviceID);
5095     if (reg == null || !reg.leaseID.equals(leaseID) || reg.leaseExpiration <= now)
5096       throw new UnknownLeaseException();
5097     if (renewDuration > maxServiceLease && renewDuration > reg.leaseExpiration - now)
5098       renewDuration = Math.max(reg.leaseExpiration - now, maxServiceLease);
5099     long renewExpiration = now + renewDuration;
5100     /* force a re-sort: must remove before changing, then reinsert */
5101     serviceByTime.remove(reg);
5102     reg.leaseExpiration = renewExpiration;
5103     serviceByTime.put(reg, reg);
5104     /* see if the expire thread needs to wake up earlier */
5105     if (renewExpiration < minSvcExpiration) {
5106       minSvcExpiration = renewExpiration;
5107       concurrentObj.waiterNotify(serviceNotifier);
5108     }
5109     return renewExpiration;
5110   }
5111 
5112   /** Renew the service lease for an absolute expiration time. */
5113   private void renewServiceLeaseAbs(ServiceID serviceID, Uuid leaseID, long renewExpiration)
5114   {
5115     SvcReg reg = (SvcReg) serviceByID.get(serviceID);
5116     if (reg == null || !reg.leaseID.equals(leaseID))
5117       return;
5118     /* force a re-sort: must remove before changing, then reinsert */
5119     serviceByTime.remove(reg);
5120     reg.leaseExpiration = renewExpiration;
5121     serviceByTime.put(reg, reg);
5122   }
5123 
5124   /** The code that does the real work of cancelEventLease. */
5125   private void cancelEventLeaseDo(long eventID, Uuid leaseID) throws UnknownLeaseException
5126   {
5127     long now = System.currentTimeMillis();
5128     EventReg reg = (EventReg) eventByID.get(new Long(eventID));
5129     if (reg == null || reg.leaseExpiration <= now)
5130       throw new UnknownLeaseException();
5131     deleteEvent(reg);
5132     /* wake up thread if this might be the (only) earliest time */
5133     if (reg.leaseExpiration == minEventExpiration)
5134       concurrentObj.waiterNotify(eventNotifier);
5135   }
5136 
5137   /** The code that does the real work of renewEventLease. */
5138   private long renewEventLeaseDo(long eventID, Uuid leaseID, long renewDuration)
5139       throws UnknownLeaseException
5140   {
5141     long now = System.currentTimeMillis();
5142     long renewExpiration = renewEventLeaseInt(eventID, leaseID, renewDuration, now);
5143     addLogRecord(new EventLeaseRenewedLogObj(eventID, leaseID, renewExpiration));
5144     return renewExpiration - now;
5145   }
5146 
5147   private long renewEventLeaseInt(long eventID, Uuid leaseID, long renewDuration, long now)
5148       throws UnknownLeaseException
5149   {
5150     if (renewDuration == Lease.ANY)
5151       renewDuration = maxEventLease;
5152     else if (renewDuration < 0)
5153       throw new IllegalArgumentException("negative lease duration");
5154     EventReg reg = (EventReg) eventByID.get(new Long(eventID));
5155     if (reg == null || !reg.leaseID.equals(leaseID) || reg.leaseExpiration <= now)
5156       throw new UnknownLeaseException();
5157     if (renewDuration > maxEventLease && renewDuration > reg.leaseExpiration - now)
5158       renewDuration = Math.max(reg.leaseExpiration - now, maxEventLease);
5159     long renewExpiration = now + renewDuration;
5160     /* force a re-sort: must remove before changing, then reinsert */
5161     eventByTime.remove(reg);
5162     reg.leaseExpiration = renewExpiration;
5163     eventByTime.put(reg, reg);
5164     /* see if the expire thread needs to wake up earlier */
5165     if (renewExpiration < minEventExpiration) {
5166       minEventExpiration = renewExpiration;
5167       concurrentObj.waiterNotify(eventNotifier);
5168     }
5169     return renewExpiration;
5170   }
5171 
5172   /** Renew the event lease for an absolute expiration time. */
5173   private void renewEventLeaseAbs(long eventID, Uuid leaseID, long renewExpiration)
5174   {
5175     EventReg reg = (EventReg) eventByID.get(new Long(eventID));
5176     if (reg == null || !reg.leaseID.equals(leaseID))
5177       return;
5178     /* force a re-sort: must remove before changing, then reinsert */
5179     eventByTime.remove(reg);
5180     reg.leaseExpiration = renewExpiration;
5181     eventByTime.put(reg, reg);
5182   }
5183 
5184   /**
5185    * The code that does the real work of renewLeases.  Each element of
5186    * regIDs must either be a ServiceID (for a service lease) or a Long
5187    * (for an event lease).  Renewals contains durations.  All three
5188    * arrays must be the same length.
5189    */
5190   private RenewResults renewLeasesDo(Object[] regIDs, Uuid[] leaseIDs, long[] renewals)
5191   {
5192     long now = System.currentTimeMillis();
5193     Exception[] exceptions = null;
5194     for (int i = 0; i < regIDs.length; i++) {
5195       Object id = regIDs[i];
5196       try {
5197         if (id instanceof ServiceID)
5198           renewals[i] = renewServiceLeaseInt((ServiceID) id, leaseIDs[i], renewals[i], now);
5199         else
5200           renewals[i] = renewEventLeaseInt(((Long) id).longValue(), leaseIDs[i], renewals[i],
5201               now);
5202       } catch (Exception e) {
5203         renewals[i] = -1;
5204         if (exceptions == null)
5205           exceptions = new Exception[] { e };
5206         else
5207           exceptions = (Exception[]) arrayAdd(exceptions, e);
5208       }
5209     }
5210     /* don't bother to weed out problem leases */
5211     addLogRecord(new LeasesRenewedLogObj(regIDs, leaseIDs, renewals));
5212     for (int i = regIDs.length; --i >= 0;) {
5213       if (renewals[i] >= 0)
5214         renewals[i] -= now;
5215     }
5216     return new RenewResults(renewals, exceptions);
5217   }
5218 
5219   /**
5220    * Renew the leases for absolute expiration times.  Skip any leases
5221    * with negative expiration times.
5222    */
5223   private void renewLeasesAbs(Object[] regIDs, Uuid[] leaseIDs, long[] renewExpirations)
5224   {
5225     for (int i = regIDs.length; --i >= 0;) {
5226       long expiration = renewExpirations[i];
5227       if (expiration < 0)
5228         continue;
5229       Object id = regIDs[i];
5230       if (id instanceof ServiceID)
5231         renewServiceLeaseAbs((ServiceID) id, leaseIDs[i], expiration);
5232       else
5233         renewEventLeaseAbs(((Long) id).longValue(), leaseIDs[i], expiration);
5234     }
5235   }
5236 
5237   /**
5238    * The code that does the real work of cancelLeases.  Each element of
5239    * regIDs must either be a ServiceID (for a service lease) or a Long
5240    * (for an event lease).  The two arrays must be the same length.
5241    * If there are no exceptions, the return value is null.  Otherwise,
5242    * the return value has the same length as regIDs, and has nulls for
5243    * leases that successfully renewed.
5244    */
5245   private Exception[] cancelLeasesDo(Object[] regIDs, Uuid[] leaseIDs)
5246   {
5247     Exception[] exceptions = null;
5248     for (int i = regIDs.length; --i >= 0;) {
5249       Object id = regIDs[i];
5250       try {
5251         if (id instanceof ServiceID)
5252           cancelServiceLeaseDo((ServiceID) id, leaseIDs[i]);
5253         else
5254           cancelEventLeaseDo(((Long) id).longValue(), leaseIDs[i]);
5255       } catch (Exception e) {
5256         if (exceptions == null)
5257           exceptions = new Exception[regIDs.length];
5258         exceptions[i] = e;
5259       }
5260     }
5261     return exceptions;
5262   }
5263 
5264   /**
5265    * Generate events for all matching event registrations.  A null pre
5266    * represents creation of a new item, a null post represents deletion
5267    * of an item.
5268    */
5269   private void generateEvents(Item pre, Item post, long now)
5270   {
5271     if (inRecovery)
5272       return;
5273     ServiceID sid = (pre != null) ? pre.serviceID : post.serviceID;
5274     Object val = subEventByService.get(sid);
5275     if (val instanceof EventReg) {
5276       generateEvent((EventReg) val, pre, post, sid, now);
5277     } else if (val != null) {
5278       EventReg[] regs = (EventReg[]) val;
5279       for (int i = regs.length; --i >= 0;) {
5280         generateEvent(regs[i], pre, post, sid, now);
5281       }
5282     }
5283     for (Iterator iter = subEventByID.values().iterator(); iter.hasNext();) {
5284       generateEvent((EventReg) iter.next(), pre, post, sid, now);
5285     }
5286   }
5287 
5288   /**
5289    * Generate an event if the event registration matches.  A null pre
5290    * represents creation of a new item, a null post represents deletion
5291    * of an item.  sid is the serviceID of the item.
5292    */
5293   private void generateEvent(EventReg reg, Item pre, Item post, ServiceID sid, long now)
5294   {
5295     if (reg.leaseExpiration <= now)
5296       return;
5297     if ((reg.transitions & ServiceRegistrar.TRANSITION_NOMATCH_MATCH) != 0
5298         && (pre == null || !matchItem(reg.tmpl, pre))
5299         && (post != null && matchItem(reg.tmpl, post)))
5300       pendingEvent(reg, sid, post, ServiceRegistrar.TRANSITION_NOMATCH_MATCH);
5301     else if ((reg.transitions & ServiceRegistrar.TRANSITION_MATCH_NOMATCH) != 0
5302         && (pre != null && matchItem(reg.tmpl, pre))
5303         && (post == null || !matchItem(reg.tmpl, post)))
5304       pendingEvent(reg, sid, post, ServiceRegistrar.TRANSITION_MATCH_NOMATCH);
5305     else if ((reg.transitions & ServiceRegistrar.TRANSITION_MATCH_MATCH) != 0
5306         && (pre != null && matchItem(reg.tmpl, pre))
5307         && (post != null && matchItem(reg.tmpl, post)))
5308       pendingEvent(reg, sid, post, ServiceRegistrar.TRANSITION_MATCH_MATCH);
5309   }
5310 
5311   /** Add a pending EventTask for this event registration. */
5312   private void pendingEvent(EventReg reg, ServiceID sid, Item item, int transition)
5313   {
5314     if (item != null)
5315       item = copyItem(item);
5316     newNotifies.add(new EventTask(reg, sid, item, transition));
5317   }
5318 
5319   /** Queue all pending EventTasks for processing by the task manager. */
5320   private void queueEvents()
5321   {
5322     if (!newNotifies.isEmpty()) {
5323       tasker.addAll(newNotifies);
5324       newNotifies.clear();
5325     }
5326   }
5327 
5328   /** Generate a new service ID */
5329   private ServiceID newServiceID()
5330   {
5331     Uuid uuid = serviceIdGenerator.generate();
5332     return new ServiceID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
5333   }
5334 
5335   /** Generate a new lease ID */
5336   private Uuid newLeaseID()
5337   {
5338     return resourceIdGenerator.generate();
5339   }
5340 
5341   /**
5342    * Write the current state of the Registrar to persistent storage.
5343    * <p>
5344    * A 'snapshot' of the Registrar's current state is represented by
5345    * the data contained in certain fields of the Registrar. That data
5346    * represents many changes -- over time -- to the Registrar's state.
5347    * This method will record that data to a file referred to as the
5348    * snapshot file.
5349    * <p>
5350    * The data written by this method to the snapshot file -- as well as
5351    * the format of the file -- is shown below:
5352    * <ul>
5353    * <li> our class name
5354    * <li> log format version number
5355    * <li> our service ID
5356    * <li> current event ID
5357    * <li> current values of administrable parameters and current multicast
5358    * announcement sequence number
5359    * <li> contents of the container holding the current registered services
5360    * <li> null (termination 'marker' for the set of registered services)
5361    * <li> contents of the container holding the current registered events
5362    * <li> null (termination 'marker' for the set of registered events)
5363    * </ul>
5364    * Each data item is written to the snapshot file in serialized form.
5365    * 
5366    * @see RegistrarImpl.LocalLogHandler
5367    */
5368   private void takeSnapshot(OutputStream out) throws IOException
5369   {
5370     ObjectOutputStream stream = new ObjectOutputStream(out);
5371 
5372     stream.writeUTF(getClass().getName());
5373     stream.writeInt(LOG_VERSION);
5374     stream.writeObject(myServiceID);
5375     stream.writeLong(eventID);
5376     stream.writeInt(unicastPort);
5377     stream.writeObject(memberGroups);
5378     stream.writeObject(lookupGroups);
5379     stream.writeLong(announcementSeqNo);
5380     marshalAttributes(lookupAttrs, stream);
5381     marshalLocators(lookupLocators, stream);
5382     for (Iterator iter = serviceByID.entrySet().iterator(); iter.hasNext();) {
5383       Map.Entry entry = (Map.Entry) iter.next();
5384       if (myServiceID != entry.getKey())
5385         stream.writeObject(entry.getValue());
5386     }
5387     stream.writeObject(null);
5388     for (Iterator iter = eventByID.values().iterator(); iter.hasNext();) {
5389       stream.writeObject(iter.next());
5390     }
5391     stream.writeObject(null);
5392     stream.flush();
5393     logger.finer("wrote state snapshot");
5394   }
5395 
5396   /**
5397    * Retrieve the contents of the snapshot file and reconstitute the 'base'
5398    * state of the Registrar from the retrieved data.
5399    * <p>
5400    * The data retrieved by this method from the snapshot file is shown
5401    * below:
5402    * <ul>
5403    * <li> our class name
5404    * <li> log format version number
5405    * <li> our service ID
5406    * <li> current event ID
5407    * <li> current values of administrable parameters and multicast
5408    * announcement sequence number at time of last snapshot
5409    * <li> contents of the container holding the current registered services
5410    * <li> contents of the container holding the current registered events
5411    * </ul>
5412    * During recovery, the state of the Registrar at the time of a crash
5413    * or outage is re-constructed by first reconstituting the 'base state'
5414    * from the snapshot file; and then modifying that base state according
5415    * to the records retrieved from the log file. This method is invoked to
5416    * perform the first step in that reconstruction. As each registered
5417    * service or event is retrieved, it is resolved and then inserted into
5418    * its appropriate container object.
5419    * <p>
5420    * Because events can be generated before the next snapshot is taken,
5421    * the event sequence numbers must be incremented. This is because the
5422    * event specification requires that set of event sequence numbers
5423    * be monotonically increasing. Since the number of events that might
5424    * have been sent is arbitrary, each sequence number will be incremented
5425    * by a 'large' number so as to guarantee adherence to the specification.
5426    * 
5427    * @see RegistrarImpl.LocalLogHandler
5428    */
5429   private void recoverSnapshot(InputStream in) throws IOException, ClassNotFoundException
5430   {
5431     ObjectInputStream stream = new ObjectInputStream(in);
5432     if (!getClass().getName().equals(stream.readUTF()))
5433       throw new IOException("log from wrong implementation");
5434     int logVersion = stream.readInt();
5435     if (logVersion != LOG_VERSION)
5436       throw new IOException("wrong log format version");
5437     myServiceID = (ServiceID) stream.readObject();
5438     eventID = stream.readLong();
5439     unicastPort = stream.readInt();
5440     memberGroups = (String[]) stream.readObject();
5441     lookupGroups = (String[]) stream.readObject();
5442     announcementSeqNo = stream.readLong() + Integer.MAX_VALUE;
5443     lookupAttrs = unmarshalAttributes(stream);
5444     lookupLocators = prepareLocators(unmarshalLocators(stream), recoveredLocatorPreparer, true);
5445     recoverServiceRegistrations(stream, logVersion);
5446     recoverEventRegistrations(stream);
5447     recoveredSnapshot = true;
5448     logger.finer("recovered state from snapshot");
5449   }
5450 
5451   /** Recovers service registrations and reggie's lookup attributes */
5452   private void recoverServiceRegistrations(ObjectInputStream stream, int logVersion)
5453       throws IOException, ClassNotFoundException
5454   {
5455     SvcReg sReg;
5456     while ((sReg = (SvcReg) stream.readObject()) != null) {
5457       addService(sReg);
5458     }
5459   }
5460 
5461   /** Recovers event registrations */
5462   private void recoverEventRegistrations(ObjectInputStream stream) throws IOException,
5463       ClassNotFoundException
5464   {
5465     EventReg eReg;
5466     while ((eReg = (EventReg) stream.readObject()) != null) {
5467       eReg.prepareListener(recoveredListenerPreparer);
5468       eReg.seqNo += Integer.MAX_VALUE;
5469       addEvent(eReg);
5470     }
5471   }
5472 
5473   /**
5474    * Add a state-change record to persistent storage.
5475    * <p>
5476    * Whenever a significant change occurs to the Registrar's state, this
5477    * method is invoked to record that change in a file called a log file.
5478    * Each record written to the log file is an object reflecting both
5479    * the data used and the ACTIONS taken to make one change to the
5480    * Registrar's state at a particular point in time. If the number of
5481    * records contained in the log file exceeds the pre-defined threshold,
5482    * a snapshot of the current state of the Registrar will be recorded.
5483    * <p>
5484    * Whenever one of the following state changes occurs, this method
5485    * will be invoked with the appropriate implementation of the
5486    * LogRecord interface as the input argument.
5487    * <ul>
5488    * <li> a new service was registered
5489    * <li> a new event was registered
5490    * <li> new attributes were added to an existing service
5491    * <li> existing attributes of a service were modified
5492    * <li> new attributes were set in an existing service
5493    * <li> a single service lease was renewed
5494    * <li> a single service lease was cancelled
5495    * <li> a single event lease was renewed
5496    * <li> a single event lease was cancelled
5497    * <li> a set of service leases were renewed
5498    * <li> a set of service leases were cancelled
5499    * <li> a set of event leases were renewed
5500    * <li> a set of event leases were cancelled
5501    * <li> the unicast port number was set
5502    * <li> the set of Lookup Groups were changed
5503    * <li> the set of Lookup Locators were changed
5504    * <li> the set of Member Groups were changed
5505    * </ul>
5506    * 
5507    * @see RegistrarImpl.LocalLogHandler
5508    */
5509   private void addLogRecord(LogRecord rec)
5510   {
5511     if (log == null) {
5512       return;
5513     }
5514     try {
5515       log.update(rec, true);
5516       if (logger.isLoggable(Level.FINER)) {
5517         logger.log(Level.FINER, "wrote log record {0}", new Object[] { rec });
5518       }
5519       if (++logFileSize >= persistenceSnapshotThreshold) {
5520         int snapshotSize = serviceByID.size() + eventByID.size();
5521         if (logFileSize >= persistenceSnapshotWeight * snapshotSize) {
5522           concurrentObj.waiterNotify(snapshotNotifier);
5523         }
5524       }
5525     } catch (Exception e) {
5526       if (!Thread.currentThread().isInterrupted()) {
5527         logger.log(Level.WARNING, "log update failed", e);
5528       }
5529     }
5530   }
5531 }