View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.fiddler;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InvalidObjectException;
23  import java.io.ObjectInput;
24  import java.io.ObjectInputStream;
25  import java.io.ObjectOutputStream;
26  import java.io.OutputStream;
27  import java.io.Serializable;
28  import java.lang.reflect.Array;
29  import java.net.InetAddress;
30  import java.rmi.MarshalledObject;
31  import java.rmi.NoSuchObjectException;
32  import java.rmi.RemoteException;
33  import java.rmi.activation.ActivationException;
34  import java.rmi.activation.ActivationID;
35  import java.rmi.activation.ActivationSystem;
36  import java.security.AccessControlContext;
37  import java.security.AccessController;
38  import java.security.PrivilegedAction;
39  import java.security.PrivilegedActionException;
40  import java.security.PrivilegedExceptionAction;
41  import java.util.ArrayList;
42  import java.util.Collection;
43  import java.util.Collections;
44  import java.util.HashMap;
45  import java.util.HashSet;
46  import java.util.Iterator;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.Set;
50  import java.util.TreeMap;
51  import java.util.TreeSet;
52  import java.util.concurrent.ExecutorService;
53  import java.util.concurrent.TimeUnit;
54  import java.util.concurrent.locks.Condition;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  import javax.security.auth.Subject;
58  import javax.security.auth.login.LoginContext;
59  import javax.security.auth.login.LoginException;
60  import net.jini.activation.ActivationGroup;
61  import net.jini.config.Configuration;
62  import net.jini.config.ConfigurationException;
63  import net.jini.config.ConfigurationProvider;
64  import net.jini.core.discovery.LookupLocator;
65  import net.jini.core.entry.CloneableEntry;
66  import net.jini.core.entry.Entry;
67  import net.jini.core.event.EventRegistration;
68  import net.jini.core.event.RemoteEventListener;
69  import net.jini.core.lease.Lease;
70  import net.jini.core.lease.UnknownLeaseException;
71  import net.jini.core.lookup.ServiceID;
72  import net.jini.core.lookup.ServiceRegistrar;
73  import net.jini.discovery.DiscoveryChangeListener;
74  import net.jini.discovery.DiscoveryEvent;
75  import net.jini.discovery.DiscoveryGroupManagement;
76  import net.jini.discovery.DiscoveryLocatorManagement;
77  import net.jini.discovery.DiscoveryManagement;
78  import net.jini.discovery.LookupDiscoveryManager;
79  import net.jini.discovery.LookupDiscoveryRegistration;
80  import net.jini.discovery.RemoteDiscoveryEvent;
81  import net.jini.export.CodebaseAccessor;
82  import net.jini.export.Exporter;
83  import net.jini.export.ProxyAccessor;
84  import net.jini.lookup.ServiceAttributesAccessor;
85  import net.jini.lookup.ServiceIDAccessor;
86  import net.jini.lookup.ServiceProxyAccessor;
87  import net.jini.id.Uuid;
88  import net.jini.id.UuidFactory;
89  import net.jini.io.MarshalledInstance;
90  import net.jini.lookup.JoinManager;
91  import net.jini.lookup.entry.Comment;
92  import net.jini.lookup.entry.ServiceInfo;
93  import net.jini.lookup.entry.Status;
94  import net.jini.lookup.entry.StatusType;
95  import net.jini.security.ProxyPreparer;
96  import net.jini.security.Security;
97  import net.jini.security.TrustVerifier;
98  import net.jini.security.proxytrust.ServerProxyTrust;
99  import net.jini.security.proxytrust.TrustEquivalence;
100 import org.apache.river.api.io.AtomicSerial;
101 import org.apache.river.api.io.AtomicSerial.GetArg;
102 import org.apache.river.api.io.AtomicSerial.ReadInput;
103 import org.apache.river.api.io.AtomicSerial.ReadObject;
104 import org.apache.river.api.util.Startable;
105 import org.apache.river.config.Config;
106 import org.apache.river.constants.ThrowableConstants;
107 import org.apache.river.constants.TimeConstants;
108 import org.apache.river.constants.VersionConstants;
109 import org.apache.river.logging.Levels;
110 import org.apache.river.lookup.entry.BasicServiceType;
111 import org.apache.river.lookup.entry.LookupAttributes;
112 import org.apache.river.proxy.ThrowThis;
113 import org.apache.river.reliableLog.LogHandler;
114 import org.apache.river.reliableLog.ReliableLog;
115 import org.apache.river.start.lifecycle.LifeCycle;
116 import org.apache.river.thread.InterruptedStatusThread;
117 import org.apache.river.thread.ReadersWriter;
118 import org.apache.river.thread.ReadersWriter.ConcurrentLockException;
119 import org.apache.river.thread.ReadyState;
120 import org.apache.river.fiddler.proxy.*;
121 import org.apache.river.proxy.CodebaseProvider;
122 
123 /**
124  * This class is the server side of an implementation of the lookup
125  * discovery service. Multiple client-side proxy classes are used to
126  * interact and communicate with this backend server. Those proxy
127  * classes are: <code>FiddlerProxy</code> (the proxy for the 
128  * <code>LookupDiscoveryService</code> interface which defines how clients
129  * register with the lookup discovery service), <code>FiddlerAdminProxy</code>
130  * (the proxy for the <code>FiddlerAdmin</code> interface which specifies 
131  * the methods through which administration duties such as joining, persistent
132  * state logging policy, and shutting down the lookup discovery service can
133  * be performed), and <code>FiddlerRegistration</code> (the proxy for the 
134  * <code>LookupDiscoveryRegistration</code> interface which defines the
135  * methods through which clients can perform duties such as group and
136  * locator management, state retrieval, and discarding discovered but
137  * unavailable lookup services so they are eligible for re-discovery).
138  * <p>
139  * It is through the proxies that communicate with this class that clients
140  * interact with the lookup discovery service. When a client makes a method
141  * invocation on one of the proxies, the proxy makes a corresponding call
142  * on the methods specified in the <code>Fiddler</code> interface which
143  * are implemented in this class, ultimately executing on the backend server.
144  * <p>
145  *
146  * This implementation of the lookup discovery service employs a number of
147  * {@link java.util.logging.Logger}s. The details of each such
148  * {@link java.util.logging.Logger} are described
149  * <a href="./package-summary.html#fiddlerLoggers">here</a>.
150  * <p>
151  * The configuration entries supported by this implementation are described
152  * <a href="./package-summary.html#fiddlerConfigEntries">here</a>.
153  *
154  * @author Sun Microsystems, Inc.
155  */
156 public class FiddlerImpl implements ServerProxyTrust, ProxyAccessor, Fiddler,
157 	Startable, CodebaseAccessor,
158 	ServiceProxyAccessor, ServiceAttributesAccessor, ServiceIDAccessor {
159 
160     /* Name of this component; used in config entry retrieval and the logger.*/
161     static final String COMPONENT_NAME = "org.apache.river.fiddler";
162     /* Loggers used by this implementation of the service. */
163     static final Logger problemLogger      = Logger.getLogger(COMPONENT_NAME+
164                                                               ".problem");
165     static final Logger startupLogger      = Logger.getLogger(COMPONENT_NAME+
166                                                               ".startup");
167     static final Logger tasksLogger        = Logger.getLogger(COMPONENT_NAME+
168                                                               ".tasks");
169     static final Logger eventsLogger       = Logger.getLogger(COMPONENT_NAME+
170                                                               ".events");
171     static final Logger groupsLogger       = Logger.getLogger(COMPONENT_NAME+
172                                                               ".groups");
173     static final Logger locatorsLogger     = Logger.getLogger(COMPONENT_NAME+
174                                                               ".locators");
175     static final Logger discardLogger      = Logger.getLogger(COMPONENT_NAME+
176                                                               ".discard");
177     static final Logger leaseLogger        = Logger.getLogger(COMPONENT_NAME+
178                                                               ".lease");
179     static final Logger registrationLogger = Logger.getLogger(COMPONENT_NAME+
180                                                               ".registration");
181     static final Logger persistLogger      = Logger.getLogger(COMPONENT_NAME+
182                                                               ".persist");
183 
184     @Override
185     public Entry[] getServiceAttributes() throws IOException {
186 	readyState.check();
187         concurrentObj.readLock();
188         try {
189 	    return joinMgr.getAttributes();
190         } finally {
191             concurrentObj.readUnlock();
192         }
193     }
194 
195     @Override
196     public ServiceID serviceID() throws IOException {
197 	readyState.check();
198         concurrentObj.readLock();
199         try {
200 	    return serviceID;
201 	} finally {
202             concurrentObj.readUnlock();
203         }
204     }
205 
206     @Override
207     public String getClassAnnotation() throws IOException {
208 	return "".equals(codebase) ? 
209 		CodebaseProvider.getClassAnnotation(Fiddler.class) 
210 		: codebase;
211     }
212 
213     @Override
214     public String getCertFactoryType() throws IOException {
215 	return certFactoryType;
216     }
217 
218     @Override
219     public String getCertPathEncoding() throws IOException {
220 	return certPathEncoding;
221     }
222 
223     @Override
224     public byte[] getEncodedCerts() throws IOException {
225 	return encodedCerts.clone();
226     }
227 
228     /** Data structure - associated with a <code>ServiceRegistrar</code> -
229      *  containing the <code>LookupLocator</code> and the member groups of
230      *  the registrar
231      */
232     static final class LocatorGroupsStruct {
233         final LookupLocator locator;
234         final String[]      groups;
235         LocatorGroupsStruct(LookupLocator locator, String[] groups) {
236             this.locator = locator;
237             this.groups  = groups;
238         }
239     }//end class LocatorGroupsStruct
240 
241     /* ServiceInfo values */
242     private static final String PRODUCT      = "Lookup Discovery Service";
243     private static final String MANUFACTURER = "Sun Microsystems, Inc.";
244     private static final String VENDOR       = MANUFACTURER;
245     private static final String VERSION      = VersionConstants.SERVER_VERSION;
246 
247     /** When re-setting the bound on lease durations, that bound cannot be
248      *  set to a value larger than this value */
249     static final long MAX_LEASE = 1000L * 60 * 60 * 24 * 365 * 1000;
250     /** Log format version */
251     private static final int LOG_VERSION = 2;
252 
253     /** The outer (smart) proxy to this server */
254     private FiddlerProxy outerProxy;
255     /** The inner proxy (stub or dynamic proxy) to this server */
256     private Fiddler innerProxy;
257     /** The admin proxy to this server */
258     private FiddlerAdminProxy adminProxy;
259     /** The service ID associated with this service when it is registered
260      *  with any lookup service.
261      */
262     private ServiceID serviceID = null;
263     /** The activation id of the current instance of the lookup discovery
264      *  service, if it happens to be and activatable instance
265      */
266     private final ActivationID activationID;
267     /* Holds the prepared proxy to the ActivationSystem */
268     private final ActivationSystem activationSystem;
269     /** The unique identifier generated (or recovered) when an instance of
270      *  this service is constructed. This ID is typically used to determine
271      *  equality between the proxies of any two instances of this service.
272      */
273     private Uuid proxyID = null;
274     /** Map from the set of all currently discovered registrars to their 
275      *  corresponding [locator,member groups] pairs (locatorGroupsStuct).
276      */
277     private final HashMap allDiscoveredRegs = new HashMap(11);
278     /** Map from registrationID to registrationInfo.  Every registration is in
279      *  this map under its registrationID.
280      */
281     private final HashMap registrationByID = new HashMap(11);
282     /** Map from registrationInfo to registrationInfo (that is, to itself),
283      *  where the elements of the map are ordered by lease expiration time.
284      */
285     private final TreeMap registrationByTime = new TreeMap();
286     /** Performs all group and locator discovery on behalf of clients */
287     private final LookupDiscoveryManager discoveryMgr; 
288     /** The listener registered for both group discovery events and locator
289      *  discovery events.
290      */
291     private final LookupDiscoveryListener discoveryListener 
292                                                = new LookupDiscoveryListener();
293     /** For each registration created by the lookup discovery service, an
294      *  event identifier that uniquely maps the registration to the
295      *  registration's listener and managed sets will be generated and
296      *  associated with the registration through the EventRegistration
297      *  field of the registrationInfo. This event ID is unique across
298      *  all registrations with the current instance of the lookup discovery
299      *  service.
300      */
301     private long curEventID = 0;
302     /** Earliest expiration time over all active registrations */
303     private long minExpiration = Long.MAX_VALUE;
304     /** The lookup discovery manager this service's join manager will use */
305     private final DiscoveryManagement joinMgrLDM;
306     /** Manager for discovering and registering with lookup services */
307     private JoinManager joinMgr;
308     /** Executor for sending remote discovery events */
309     private final ExecutorService executorService;
310     /** Registration lease expiration thread */
311     private final LeaseExpireThread leaseExpireThread;
312     /** Snapshot-taking thread */
313     private final SnapshotThread snapshotThread;
314 
315     /** Concurrent object to control read and write access */
316     private final ReadersWriter concurrentObj = new ReadersWriter();
317     /** Object for synchronizing with the registration expire thread */
318     private final Condition leaseExpireThreadSyncObj;
319     /** Object on which the snapshot-taking thread will synchronize */
320     private final Condition snapshotThreadSyncObj;
321 
322     /** Reliable log object to hold persistent state of the service.
323      *  This object is also used as a flag: non-null ==> persistent service
324      *                                      null ==> non-persistent service
325      */
326     private final ReliableLog log;
327     /** Flag indicating whether system is in a state of recovery */
328     private boolean inRecovery;
329     /** Current number of records in the Log File since the last snapshot */
330     private int logFileSize = 0;
331     /** The name of the directory to which the persistent modes of this service
332      *  will log the service's state (using ReliableLog).
333      */
334     private final String persistDir;
335     /** least upper bound applied to all granted lease durations */
336     private long leaseBound = 1000 * 60 * 30;
337     /** Weight factor applied to snapshotSize when deciding to take snapshot */
338     private float snapshotWt = 10;
339     /** Log File must contain this many records before snapshot allowed */
340     private int snapshotThresh = 200;
341     /** Groups whose members are lookup services this service should join.
342      *  Unless configured otherwise, this service will initially join the
343      *  un-named public group. The desired join groups for this service can
344      *  be set administratively after start up.
345      */
346     private String[] thisServicesGroups = new String[] {""};
347     /** Locators of specific lookup services this service should join.
348      *  This service will initially join no lookups found through locator
349      *  discovery. The locators of the specific lookup services that are
350      *  desired for this service to join should be set administratively
351      *  after start up.
352      */
353     private LookupLocator[] thisServicesLocators = {};
354     /** The attributes to use when joining lookup service(s) */
355     private Entry[] thisServicesAttrs
356             = new Entry[]
357                  { new ServiceInfo(PRODUCT,MANUFACTURER,VENDOR,VERSION,"",""),
358                    new BasicServiceType("Lookup Discovery Service")
359                  };
360     /* Object used to obtain the configuration items for this service. */
361     private Configuration config;
362     /* The JAAS login context to use when performing a JAAS login. */
363     private final LoginContext loginContext;
364     /* The exporter used to export this service. */
365     private final Exporter serverExporter;
366     /* Maximum value of upper bound on lease durations.*/
367     private final long leaseMax;
368     /* Flag indicating this service is being started for the very 1st time */
369     private boolean initialStartup = true;
370     /** Object that, if non-<code>null</code>, will cause the object's
371      *  <code>unregister</code> method to be invoked during service shutdown
372      *  to notify the service starter framework that the reference to this
373      *  service's implementation can be 'released' for garbage collection;
374      *  the framework is notified that it does not have to hold on to the
375      *  service reference any longer.  Note hat this object is used only 
376      *  in the non-activatable case.
377      */
378     private final LifeCycle lifeCycle;
379     
380     /* ProxyPreparer fields were originally static and set every time a 
381      * constructor was called, this was done to enable the static class
382      * RegistrationInfo to prepare recovered proxy's after deserialization.
383      * 
384      * These fields are now final instance fields.  This would for instance
385      * enable multiple FiddlerImpl to co exist in one jvm if necessary using
386      * different configurations.
387      */
388     /* Preparer for proxies to remote listeners newly registered with this
389      * service.
390      */
391     private final ProxyPreparer listenerPreparer;
392     /* Preparer for proxies to remote listeners, previously prepared, and
393      * recovered from this service's persisted state.
394      */
395     private final ProxyPreparer recoveredListenerPreparer;
396     /* Preparer for initial and new lookup locators this service should
397      * discover and join.
398      */
399     private final ProxyPreparer locatorToJoinPreparer;
400     /* Preparer for lookup locators this service should discover and join
401      * that were previously prepared and which were recovered from this
402      * service's persisted state.
403      */
404     private final ProxyPreparer recoveredLocatorToJoinPreparer;
405     /* Preparer for initial and new lookup locators this service should
406      * discover on behalf of the clients that register with it.
407      */
408     private final ProxyPreparer locatorToDiscoverPreparer;
409     /* Preparer for lookup locators this service should discover on behalf
410      * of its registered clients that were previously prepared and which
411      * were recovered from this service's persisted state.
412      */
413     private final ProxyPreparer recoveredLocatorToDiscoverPreparer;
414     /** Object used to prevent access to this service during the service's
415      *  initialization or shutdown processing.
416      */
417     private final ReadyState readyState = new ReadyState();
418     
419     private boolean persistent;
420     private LocalLogHandler logHandler;
421     private final AccessControlContext context;
422     
423     private boolean started;
424     
425     private String codebase;
426     private String certFactoryType;
427     private String certPathEncoding;
428     private byte[] encodedCerts;
429 
430     /* ************************* BEGIN Constructors ************************ */
431     /**
432      * Constructs a new instance of FiddlerImpl. This version of the
433      * constructor is used to create an activatable instance of the lookup
434      * discovery service that logs its state information to persistent storage.
435      * <p>
436      * A constructor having this signature is required for the class to be
437      * activatable. This constructor is automatically called by the 
438      * activation group when the lookup discovery service is activated.
439      * 
440      * @param activationID the activation ID generated by the activation
441      *                     system and assigned to the instance of the server
442      *                     being activated
443      * @param data         state data (represented as a 
444      *                     <code>MarshalledObject</code>) which is needed to
445      *                     re-activate this server
446      *
447      * @throws IOException            this exception can occur when there is
448      *                                a problem recovering data from disk,
449      *                                exporting the server that's being
450      *                                activated, or when unmarshalling the
451      *                                given <code>data</code> parameter.
452      * @throws ConfigurationException this exception can occur when a
453      *                                problem occurs while retrieving an item
454      *                                from the <code>Configuration</code>
455      *                                generated from the contents of the
456      *                                given <code>data</code> parameter
457      * @throws ActivationException    this exception can occur when a problem
458      *                                occurs while activating the service
459      * @throws LoginException         this exception occurs when authentication
460      *                                fails while performing a JAAS login for
461      *                                this service
462      * @throws ClassNotFoundException this exception can occur while 
463      *                                unmarshalling the given <code>data</code>
464      *                                parameter; when a class needed in the
465      *                                unmarshalling process cannot be found.
466      * @throws ClassCastException     this exception can occur while
467      *                                unmarshalling the given <code>data</code>
468      *                                parameter; when the contents of that
469      *                                parameter is not a <code>String</code>
470      *                                array.
471      */
472     FiddlerImpl(ActivationID activationID,
473                 MarshalledObject data) throws IOException,
474                                               ActivationException,
475                                               ConfigurationException,
476                                               LoginException,
477                                               ClassNotFoundException
478     {
479             this(init( (String[]) new MarshalledInstance(data).get(false), true /* persistent */, activationID ), null);
480     }//end activatable constructor
481 
482     /**
483      * Constructs a new instance of FiddlerImpl. This version of the
484      * constructor is used to create a NON-activatable instance of the
485      * lookup discovery service.
486      *
487      * @param configArgs <code>String</code> array whose elements are
488      *                   the arguments to use when creating this version of
489      *                   the server
490      * @param lifeCycle  instance of <code>LifeCycle</code> that, if 
491      *                   non-<code>null</code>, will cause this object's
492      *                   <code>unregister</code> method to be invoked during
493      *                   shutdown to notify the service starter framework that
494      *                   the reference to this service's implementation can be
495      *                   'released' for garbage collection. A value of 
496      *                   <code>null</code> for this argument is allowed.
497      * @param persistent if <code>true</code>, then the service should persist
498      *                   its state.
499      *
500      * @throws IOException            this exception can occur when there is
501      *                                a problem recovering data from disk, or
502      *                                while exporting the server that's being
503      *                                created.
504      * @throws ConfigurationException this exception can occur when an
505      *                                problem occurs while retrieving an item
506      *                                from the <code>Configuration</code>
507      *                                generated from the contents of the
508      *                                given <code>configArgs</code> parameter
509      * @throws LoginException         this exception occurs when authentication
510      *                                fails while performing a JAAS login for
511      *                                this service
512      */
513     FiddlerImpl(String[] configArgs, LifeCycle lifeCycle, boolean persistent)
514                                              throws IOException,
515                                                     ConfigurationException,
516                                                     LoginException
517     {
518         this(init(configArgs, persistent), lifeCycle);
519     }//end non-activatable constructor
520     
521     FiddlerImpl(FiddlerInit i, LifeCycle lifeCycle){
522         this.snapshotThreadSyncObj = concurrentObj.newCondition();
523         this.leaseExpireThreadSyncObj = concurrentObj.newCondition();
524         this.lifeCycle = lifeCycle;
525 	this.codebase = i.codebase;
526 	this.certFactoryType = i.certFactoryType;
527 	this.certPathEncoding = i.certPathEncoding;
528 	this.encodedCerts = i.encodedCerts.clone();
529         discoveryMgr = i.discoveryMgr;
530         listenerPreparer = i.listenerPreparer;
531         locatorToJoinPreparer = i.locatorToJoinPreparer;
532         locatorToDiscoverPreparer = i.locatorToDiscoverPreparer;
533         recoveredListenerPreparer = i.recoveredListenerPreparer;
534         recoveredLocatorToJoinPreparer = i.recoveredLocatorToJoinPreparer;
535         recoveredLocatorToDiscoverPreparer = i.recoveredLocatorToDiscoverPreparer;
536         persistDir = i.persistDir;
537         log = i.log;
538         joinMgrLDM = i.joinMgrLDM;
539         leaseMax = i.leaseMax;
540         executorService = i.executorService;
541         activationSystem = i.activationSystem;
542         serverExporter = i.serverExporter;
543         logHandler = i.logHandler;
544         activationID = i.activationID;
545         // These three fields are used by the Starter.start() implementation.
546         persistent = i.persistent;
547         config = i.config;
548         context = i.context;
549         loginContext = i.loginContext;
550         leaseExpireThread = AccessController.doPrivileged(
551             new PrivilegedAction<LeaseExpireThread>(){
552                 @Override
553                 public LeaseExpireThread run() {
554                     return new LeaseExpireThread(FiddlerImpl.this);
555                 }
556                     
557             }, context);
558         if (log != null){
559             snapshotThread = AccessController.doPrivileged(
560                 new PrivilegedAction<SnapshotThread>(){
561                     @Override
562                     public SnapshotThread run() {
563                         return new SnapshotThread(FiddlerImpl.this);
564                     }
565 
566                 }, context);
567         } else {
568             snapshotThread = null;
569         }
570     }
571     /* ************************** END Constructors ************************* */
572 
573     /* ******************* BEGIN Inner Class Definitions ******************* */
574     /** Class which is used to communicate the status of this service to
575      *  interested entities. In particular, when certain errors occur during
576      *  operation, an instance of this class will be registered as an 
577      *  attribute in all of the lookup services with which this service
578      *  is registered. By registering for notification (from the lookup
579      *  services) of the existence of this attribute, interested entities
580      *  such as administrative clients and clients wishing to use this
581      *  service will be informed when this service can not proceed with its
582      *  processing, and can take appropriate action.
583      */
584     private static class FiddlerStatus extends Status {
585         private static final long serialVersionUID = -8511826097053446749L;
586         public FiddlerStatus(StatusType severity) {
587             super(severity);
588         }
589     }//end class FiddlerStatus
590 
591     /** Class whose discovered() method is invoked by threads in the 
592      *  LookupDiscovery class whenever a new lookup service is discovered
593      *  on behalf of a client registration
594      */
595     private class LookupDiscoveryListener implements DiscoveryChangeListener {
596         public LookupDiscoveryListener() {
597             super();
598         }
599         public void discovered(DiscoveryEvent event) {
600             executorService.execute(new DiscoveredEventTask(event));
601         }
602         public void discarded(DiscoveryEvent event) {
603             executorService.execute(new DiscardedEventTask(event));
604         }
605         public void changed(DiscoveryEvent event) {
606             executorService.execute(new ChangedEventTask(event));
607         }
608     }//end class LookupDiscoveryListener
609 
610     /** This class acts as a record of one registration with the lookup
611      *  discovery service; containing all of the information about that
612      *  registration.
613      */
614     @AtomicSerial
615     private final static class RegistrationInfo
616                                           implements Comparable, Serializable
617     {
618         private static final long serialVersionUID = 2L;
619 
620         /** The unique identifier assigned to the registration to which the
621          *  data in the current implementation of this class corresponds.
622          *  This identifier is unique across all other active registrations
623          *  generated with the current instance of the lookup discovery
624          *  service.
625          *  @serial
626          */
627         public final Uuid registrationID;
628         /** Map from the set of instances of the <code>ServiceRegistrar</code>
629          *  interface, to the set of marshalled instances of the
630          *  <code>ServiceRegistrar</code> interface, where each key and
631          *  each value (which is the marshalled form of its corresponding
632          *  key) is a proxy to one of the lookup service(s) that have been
633          *  discovered for the current registration. The contents of
634          *  this set represents the 'remote state' of the registration's
635          *  currently discovered lookup service(s).
636          *  @serial
637          */
638         public final Map<ServiceRegistrar, MarshalledObject> discoveredRegsMap;
639         /** The managed set containing the names of the groups whose
640          *  members are the lookup services the lookup discovery service
641          *  should attempt to discover for the current registration.
642          *  (HashSet is used to prevent duplicates.)
643          *  @serial
644          */
645         public Set<String> groups;
646         /** The managed set containing the locators of the specific lookup
647          *  services the lookup discovery service should attempt to discover
648          *  for the current registration. (HashSet is used to prevent
649          *  duplicates.)
650          *  @serial
651          */
652         public Set<LookupLocator> locators;
653         /** The ID of the lease placed on the current registration.
654          *  @serial
655          */
656         public final Uuid leaseID;
657         /** The absolute expiration time of the current lease.
658          *  @serial
659          */
660         public long leaseExpiration;
661         /** The identifier that maps the current registration to the remote
662          *  event listener and the managed set of groups and locators.
663          */
664         public long eventID;
665         /** The current sequence number of the set of remote discovery events
666          *  sent to the current registration's listener. When a registration
667          *  is granted, this class is instantiated to contain the information
668          *  related to that particular registration. The event sequence
669          *  number is initialized to 0 upon instantiation because the
670          *  remote discovery events are sent to the listeners of each 
671          *  separate registration. Thus, each registration has its own
672          *  sequence of events.
673          *  @serial
674          */
675         public long seqNum;
676         /** The handback object returned with every remote discovery event
677          *  sent to the current registration's listener.
678          *  @serial
679          */
680         public final MarshalledObject handback;
681         /** When the lookup discovery service discards a registrar as a 
682          *  result of some internal condition (such as multicast announcements
683          *  ceasing) and not as a result of a request from a registration,
684          *  every registration configured for group discovery of that discarded
685          *  registrar will be sent a remote discarded event. On the other
686          *  hand, for the case where a registrar is discarded as a result
687          *  of a request from a registration, only those registrations that
688          *  actually request that the registrar be discarded will be sent
689          *  a remote discarded event. This flag is used to determine whether
690          *  to send a remote discarded event to one or multiple listeners.
691          */
692         public boolean discardFlag;
693         /** The remote event listener registered by the client. This field
694          *  is transient because it is marshalled separately from the rest
695          *  of this class when being serialized. (See the description for
696          *  <code>writeObject</code> below.)
697          */
698          public transient RemoteEventListener listener;
699          
700 	 private static RemoteEventListener check(GetArg arg) throws IOException {
701 	    Object registrationID = arg.get("registrationID", null);
702 	    if (!(registrationID instanceof Uuid)) 
703 		throw new InvalidObjectException(
704 		    "registrationID must be instanceof Uuid and non null");
705 	    Map<ServiceRegistrar, MarshalledObject> discoveredRegsMap 
706 		= (Map<ServiceRegistrar, MarshalledObject>)
707 		    arg.get("discoveredRegsMap", null);
708 	    Map<ServiceRegistrar, MarshalledObject> checkedRegsMap =
709 		Collections.checkedMap(
710 		    new HashMap(discoveredRegsMap.size()),
711 			ServiceRegistrar.class, MarshalledObject.class);
712 	    checkedRegsMap.putAll(discoveredRegsMap);
713 	    Set<String> groups = (Set<String>) arg.get("groups", null);
714 	    Set<String> checkedGroups = 
715 		    Collections.checkedSet(new TreeSet<String>(), String.class);
716 	    checkedGroups.addAll(groups);
717 	    Set<LookupLocator> locators = (Set<LookupLocator>) arg.get("locators", null);
718 	    Set<LookupLocator> checkedLocators = 
719 		    Collections.checkedSet(
720 			    new HashSet(locators.size()), LookupLocator.class);
721 	    checkedLocators.addAll(locators);
722 	    Object leaseID = arg.get("leaseID", null);
723 	    if (!(leaseID instanceof Uuid)) 
724 		throw new InvalidObjectException(
725 		    "leaseID must be instanceof Uuid and non null");
726 	    arg.get("leaseExpiration", 0L); // Checks existance
727 	    arg.get("eventID", 0L); // Checks existance
728 	    arg.get("seqNum", 0L); // Checks existance
729 	    Object handback = arg.get("handback", null);
730 	    if (handback != null && !(handback instanceof MarshalledObject))
731 		throw new InvalidObjectException(
732 		    "handback, if non null, must be an instance of MarshalledObject");
733 	    arg.get("discardFlag", false); // Checks existance
734 	    return ((RO)arg.getReader()).listener;
735 	 }
736 	 
737 	 RegistrationInfo(GetArg arg) throws IOException {
738 	     this(arg, check(arg));
739 	 }
740 	 
741 	private RegistrationInfo(GetArg arg, RemoteEventListener listener) throws IOException {
742 	    this.listener = listener;
743 	    registrationID = (Uuid) arg.get("registrationID", null);
744 	    discoveredRegsMap = new HashMap<ServiceRegistrar, MarshalledObject>(
745 		    (Map<ServiceRegistrar, MarshalledObject>)
746 		arg.get("discoveredRegsMap", null));
747 	    groups = new HashSet<String>((Set<String>) arg.get("groups", null));
748 	    locators = new HashSet<LookupLocator>(
749 		    (Set<LookupLocator>) arg.get("locators", null));
750 	    leaseID = (Uuid) arg.get("leaseID", null);
751 	    leaseExpiration = arg.get("leaseExpiration", 0L);
752 	    eventID = arg.get("eventID", 0L);
753 	    seqNum = arg.get("seqNum", 0L);
754 	    handback = (MarshalledObject) arg.get("handback", null);
755 	    discardFlag = arg.get("discardFlag", false);
756 	}
757          
758         /** Constructs an instance of this class and stores the information
759          *  related to the current registration: IDs, managed sets, lease
760          *  information, and event registration information.
761          */
762         public RegistrationInfo(Uuid registrationID,
763                 String[] groups, 
764                 LookupLocator[] locators, 
765                 Uuid leaseID, 
766                 long leaseExpiration, 
767                 long eventID, 
768                 MarshalledObject handback, 
769                                 RemoteEventListener listener)
770         {
771             this.registrationID = registrationID;
772             /* Initialize the groups field, removing nulls and duplicates */
773             if(groups != null) {
774                 this.groups = new HashSet<String>(groups.length);
775                 for(int i=0;i<groups.length;i++) {
776                     if(groups[i] == null) continue;
777                     this.groups.add(groups[i]);
778                 }
779             }
780             /* Initialize the locators field, removing nulls and duplicates */
781             this.locators = new HashSet(locators.length);
782             if( (locators != null) && (locators.length > 0) ) {
783                 for(int i=0;i<locators.length;i++) {
784                     if(locators[i] == null) continue;
785                     this.locators.add(locators[i]);
786                 }
787             }
788             this.discoveredRegsMap = new HashMap<ServiceRegistrar, MarshalledObject>(11);
789             this.leaseID = leaseID;
790             this.leaseExpiration = leaseExpiration;
791 
792             this.eventID = eventID;
793             this.seqNum = 0; // initialize to 0
794             this.handback = handback;
795             this.discardFlag = false;//set true only on first discard request
796             this.listener = listener;
797         }//end constructor
798 
799         /** Attempts to marshal each element of the input set of instances of
800          *  the <code>ServiceRegistrar</code> interface and then map the
801          *  registrar to its marshalled form, and store the mapping in this
802          *  registration's <code>discoveredRegsMap</code> field.
803          *  <p>
804          *  This method is typically invoked to handle discovered (as opposed
805          *  to discarded) registrars. Note that if a particular registrar
806          *  cannot be serialized (marshalled), it is not included in the
807          *  mapping; nor is it included in the return set.
808          * 
809          * @param regMapIn mapping in which the key values are the registrars
810          *                 to serialize and store, and the map values are data
811          *                 structures of type <code>LocatorGroupsStruct</code>
812          *                 that contain the locator and member groups of the
813          *                 corresponding registrar key
814          * 
815          *  @return a <code>HashMap</code> whose keys are the registrars
816          *          whose marshalled form and un-marshalled form were inserted
817          *          as key/value pairs into the <code>discoveredRegsMap</code>
818          *          field of this regInfo; and whose values are the member
819          *          groups of each corresponding registrar key.
820          */
821         public Map addToDiscoveredRegs(Map regMapIn) {
822             HashMap regMapOut = new HashMap(regMapIn.size());
823             Iterator itr = (regMapIn.entrySet()).iterator();
824             nextReg:
825             for(int i=0;itr.hasNext();i++) {
826                 Map.Entry pair = (Map.Entry)itr.next();
827                 ServiceRegistrar reg = (ServiceRegistrar)pair.getKey();
828                 /* If reg is already in map, go to next registrar */
829                 if( discoveredRegsMap.containsKey(reg) ) continue nextReg;
830                 /* It doesn't contain it, try to marshal it */
831                 MarshalledObject mReg = null;
832                 try {
833                     mReg = new MarshalledInstance(reg).convertToMarshalledObject();
834                 } catch(IOException e) { continue nextReg; } //failed, next reg
835                 /* Succeeded, map registrar to its marshalled form */
836                 discoveredRegsMap.put(reg,mReg);
837                 /* Map the registrar to its member groups for the return map */
838                 regMapOut.put(reg,
839                               ((LocatorGroupsStruct)pair.getValue()).groups);
840             }//end loop
841             return regMapOut;
842         }//end addToDiscoveredRegs
843 
844         /** Performs a primary sort by leaseExpiration, and a secondary sort
845          *  by registrationID. The secondary sort is immaterial, except to
846          *  ensure a total order (required by <code>TreeMap</code>).
847          */
848         public int compareTo(Object obj) {
849             RegistrationInfo regInfo = (RegistrationInfo)obj;
850             if (this == regInfo)  return 0;
851             if (    (leaseExpiration < regInfo.leaseExpiration)
852                  || (    (leaseExpiration == regInfo.leaseExpiration)
853 		      && (eventID < regInfo.eventID) )  )
854             {
855                 return -1;
856             }//endif
857             return 1;
858         }//end compareTo
859         
860         /** When a registration is granted to a client, the client registers
861          *  a remote listener with the lookup discovery service so that the
862          *  lookup discovery service may send remote discovery events to the
863          *  client. The client typically annotates the listener with an RMI
864          *  codebase from which the backend server can download the remote
865          *  listener's proxy (stub). When the current registration is logged
866          *  to persistent storage (for example, a snapshot is taken), the
867          *  listener is written to the output snapshot or log file through
868          *  an <code>ObjectOutputStream</code> which only serializes the
869          *  listener; it does not marshal the listener. Thus, when the
870          *  listener field of this class is logged, unless special action
871          *  is taken, the codebase from which to retrieve the listener will
872          *  not be included in the output. 
873          *
874          *  In order to include the codebase with the listener when saving
875          *  state, the following custom <code>writeObject</code> method
876          *  is provided which first serializes the current instance of
877          *  this class (excluding the transient <code>listener</code> field),
878          *  and then explicitly marshals the listener to preserve the
879          *  codebase upon writing to the file. In this way, the listener --
880          *  along with its codebase -- is persisted through a mechanism that
881          *  is separate from the normal mechanism applied to the remaining
882          *  fields of this class.
883          */
884         private void writeObject(ObjectOutputStream stream) throws IOException{
885             stream.defaultWriteObject();
886             stream.writeObject(new MarshalledInstance(listener).convertToMarshalledObject());
887         }//end writeObject
888 
889         /** When this class is deserialized, this method is invoked. This
890          *  method first deserializes the non-transient elements of this
891          *  class, and then unmarshals the remote event listener. (See the
892          *  description for <code>writeObject</code> above.)
893          */
894         private void readObject(ObjectInputStream stream)
895                                     throws IOException, ClassNotFoundException
896         {
897             stream.defaultReadObject();
898             MarshalledObject mo = (MarshalledObject)stream.readObject();
899             try {
900                 listener = (RemoteEventListener) new MarshalledInstance(mo).get(false);
901             } catch (Throwable e) {
902                 problemLogger.log(Level.INFO, "problem recovering listener "
903                                   +"for recovered registration", e);
904                 if((e instanceof Error) && (ThrowableConstants.retryable(e)
905                                              == ThrowableConstants.BAD_OBJECT))
906                 {
907                    throw (Error)e;
908                 }//endif
909             }
910         }//end readObject
911         
912 	@ReadInput
913 	private static ReadObject getRO(){
914 	    return new RO();
915 	}
916 	
917 	private static class RO implements ReadObject {
918 	    
919 	    RemoteEventListener listener;
920 
921 	    @Override
922 	    public void read(ObjectInput stream) throws IOException, ClassNotFoundException { 
923 		MarshalledObject mo = (MarshalledObject)stream.readObject();
924 		try {
925 		    listener = (RemoteEventListener) new MarshalledInstance(mo).get(false);
926 		} catch (Throwable e) {
927 		    problemLogger.log(Level.INFO, "problem recovering listener "
928 				      +"for recovered registration", e);
929 		    if((e instanceof Error) && (ThrowableConstants.retryable(e)
930 						 == ThrowableConstants.BAD_OBJECT))
931 		    {
932 		       throw (Error)e;
933 		    }//endif
934 		}
935 	    }
936 	    
937 	}
938         
939         /**
940          * Must be called immediately after de-serialization to prepare
941          * proxies.
942          * 
943          * @param recoveredListenerPreparer
944          * @param recoveredLocatorToDiscoverPreparer 
945          */
946         public void prepare(ProxyPreparer recoveredListenerPreparer,
947                      ProxyPreparer recoveredLocatorToDiscoverPreparer )
948         {
949             try {
950                 /* Re-prepare the recovered listener */
951                 listener = (RemoteEventListener)
952                         recoveredListenerPreparer.prepareProxy(listener);
953             } catch (Throwable e) {
954                 problemLogger.log(Level.INFO, "problem recovering listener "
955                                   +"for recovered registration", e);
956                 if((e instanceof Error) && (ThrowableConstants.retryable(e)
957                                              == ThrowableConstants.BAD_OBJECT))
958                 {
959                    throw (Error)e;
960                 }//endif
961             }
962             
963             /* Prepare the locators recovered from the stream */
964             int nUnprepared = (locators).size();
965             locators = (HashSet)prepareOldLocators
966                                          ( recoveredLocatorToDiscoverPreparer,
967                                            locators );
968             if( nUnprepared != (locators).size() ) {
969                 /* Failure occurred when preparing one of the locs. Because
970                  * this breaks the contract with the client, this registration
971                  * will not be recovered so that the client will eventually
972                  * be notified. To facilitate this, the listener is set to
973                  * null so that the registration will not be added to the
974                  * managed set, and so that when the client eventually attempts
975                  * to renew the lease on that registration, an exception will
976                  * occur; causing the client to be "notified" that there was
977                  * a problem with that registration. The client can then
978                  * retry the registration; and if problems still exist, the
979                  * exception the client receives may give the client more
980                  * useful information from which the client can determine
981                  * how to proceed.
982                  */
983                 listener = null;
984                 if( problemLogger.isLoggable(Level.WARNING) ) {
985                     problemLogger.log(Level.WARNING, "failure preparing "
986                                       +"locator while recovering registration"
987                                       +"... discarding recovered"
988                                       +"registration");
989                 }//endif
990             }
991         }
992         
993     }//end class RegistrationInfo
994 
995     /** This class represents a <code>Task</code> object that is placed
996      *  in the <code>TaskManager</code> queue for processing in the thread
997      *  pool. Instances of this class are placed on the task queue when
998      *  registrations are granted. 
999      *  <p>
1000      *  The <code>run</code> method of this class will determine if any of
1001      *  the new registration's desired lookup service(s) have already been
1002      *  discovered, and will send the appropriate remote discovery event to
1003      *  the registration's listener.
1004      */
1005     private final class NewRegistrationTask implements Runnable {
1006         /** The data structure record corresponding to the new registration */
1007         public final RegistrationInfo regInfo;
1008         /** Constructs an instance of this class and stores the registration
1009          *  information.
1010          */
1011         public NewRegistrationTask(RegistrationInfo regInfo) {
1012             this.regInfo = regInfo;
1013         }//end constructor
1014         /** This method processes the information associated with the new
1015          *  registration and determines, based on the current state of the
1016          *  set of 'already-discovered' lookup service(s), whether to send
1017          *  a <code>RemoteDiscoveryEvent</code> to the new registration's
1018          *  listener.
1019          */
1020         public void run() {
1021             concurrentObj.writeLock();
1022             try {
1023                 logInfoTasks("NewRegistrationTask.run(): "
1024                              +"new Registration added");
1025                 maybeSendDiscoveredEvent(regInfo,allDiscoveredRegs);
1026             } finally {
1027                 concurrentObj.writeUnlock();
1028             }
1029         }//end run
1030 
1031     }//end class NewRegistrationTask
1032 
1033     /** This class represents a <code>Task</code> object that is placed
1034      *  in the <code>TaskManager</code> queue for processing in the thread
1035      *  pool. An instance of this class is placed on the task queue when a
1036      *  <code>DiscoveryEvent</code> instance indicating a discovered event
1037      *  is received from the local discovery process. 
1038      *  <p>
1039      *  The <code>run</code> method of this class will process discovery
1040      *  event information and determine to which active registrations the
1041      *  appropriate <code>RemoteDiscoveryEvent</code> should be sent; and
1042      *  then sends that event.
1043      */
1044     private final class DiscoveredEventTask implements Runnable {
1045         /** The local event sent by the discovery manager. */
1046         public final DiscoveryEvent event;
1047         /** Constructs an instance of this class and stores the event*/
1048         public DiscoveredEventTask(DiscoveryEvent event) {
1049             this.event = event;
1050         }//end constructor
1051         /** This method processes the local discovery event information and
1052          *  determines, based on the current state of each active 
1053          *  registration, to which such registration the appropriate
1054          *  <code>RemoteDiscoveryEvent</code> should be sent. After making
1055          *  the determination, the remote event appropriate for each 
1056          *  registration is constructed and sent.
1057          */
1058         public void run() {
1059             /* Get locators before sync block (no remote calls in sync block)*/
1060             Map groupsMap = event.getGroups();
1061             ServiceRegistrar[] regs = event.getRegistrars();
1062             HashMap regMap = new HashMap(regs.length);
1063             for(int i=0;i<regs.length;i++) {
1064                 try {
1065                     LookupLocator regLoc = regs[i].getLocator();
1066                     String[] regGroups = (String[])groupsMap.get(regs[i]);
1067                     LocatorGroupsStruct regLocGroups
1068                                   = new LocatorGroupsStruct(regLoc,regGroups);
1069                     regMap.put(regs[i],regLocGroups);
1070                 } catch(Exception e) {
1071                     problemLogger.log(Levels.FAILED,
1072                                        "problem retrieving locator "
1073                                       +"from discovered lookup service ... "
1074                                       +"discarded the lookup service", e);
1075                     discoveryMgr.discard(regs[i]);
1076                 }
1077             }//end loop
1078             /* Synchronization block -- no remote calls here */
1079             concurrentObj.writeLock();
1080             logInfoTasks("DiscoveredEventTask.run(): processing DISCOVERED "
1081                          +"event from discovery manager");
1082             try {
1083                 /* Update the global allDiscoveredRegs map with new pairs */
1084                 Set eSet = regMap.entrySet();
1085                 for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
1086                     Map.Entry pair = (Map.Entry)itr.next();
1087                     allDiscoveredRegs.put(pair.getKey(),pair.getValue());
1088                 }//end loop
1089                 /* Loop thru regInfo's, adding only those not already known */
1090                 for( Iterator itr=registrationByID.values().iterator();
1091                                                               itr.hasNext(); )
1092                 {
1093                     RegistrationInfo regInfo = (RegistrationInfo)itr.next();
1094                     /* Build and send the "discovered event" if appropriate */
1095                     maybeSendDiscoveredEvent(regInfo,regMap);
1096                 }//end loop
1097             } finally {
1098                 concurrentObj.writeUnlock();
1099             }
1100         }//end run
1101 
1102     }//end class DiscoveredEventTask
1103 
1104     /** This class represents a <code>Task</code> object that is placed
1105      *  in the <code>TaskManager</code> queue for processing in the thread
1106      *  pool.  An instance of this class is placed on the task queue when a
1107      *  <code>DiscoveryEvent</code> instance indicating a discarded event
1108      *  is received from the local discovery process. 
1109      *  <p>
1110      *  The <code>run</code> method of this class will process event
1111      *  information resulting from the "discarding" of one or more
1112      *  lookup services (registrars), and will determine to which active 
1113      *  registrations the appropriate <code>RemoteDiscoveryEvent</code>
1114      *  should be sent; and then sends that event.
1115      */
1116     private final class DiscardedEventTask implements Runnable {
1117         /** The local event sent by the discovery manager. */
1118         public final DiscoveryEvent event;
1119         /** Constructs an instance of this class and stores the event*/
1120         public DiscardedEventTask(DiscoveryEvent event) {
1121             this.event = event;
1122         }//end constructor
1123         /** This method processes the local discovery event information and
1124          *  determines, based on the current state of each active 
1125          *  registration, to which such registration the appropriate
1126          *  <code>RemoteDiscoveryEvent</code> should be sent. After making
1127          *  the determination, the remote event appropriate for each 
1128          *  registration is constructed and sent.
1129          */
1130         public void run() {
1131             concurrentObj.writeLock();
1132             logInfoTasks("DiscardedEventTask.run(): processing DISCARDED "
1133                          +"event from discovery manager");
1134             try {
1135                 /* Get the registrars that were just discarded */
1136                 Map groupsMap = event.getGroups();
1137                 HashSet allDiscardedRegs = new HashSet(groupsMap.size());
1138                 /* Determine if we're here because of an external request for
1139                  * discard from one of the regInfo's (an active communication
1140                  * discard), or because the discovery manager has determined
1141                  * one or more of the discovered registrars has become 
1142                  * unreachable (a passive communication discard)
1143                  */
1144                 RegistrationInfo regInfo = externalDiscardRequest();
1145                 /* If an external request, send the discarded event to only
1146                  * the regInfo that requested the discard; otherwise, send
1147                  * it to all regInfo's that might be interested.
1148                  */
1149                 if(regInfo != null) {
1150                     /* Send discard event to only this one registration */
1151                     HashSet discardedRegs = maybeSendDiscardedEvent
1152                                                     (regInfo,groupsMap,true);
1153                     /* Transfer the just-discarded regs to the summary set */
1154                     for(Iterator jtr=discardedRegs.iterator();jtr.hasNext(); ){
1155                         allDiscardedRegs.add(jtr.next());
1156                     }
1157                 } else {
1158                     /* Send discard event to each "eligible" registration */
1159                     for( Iterator itr=registrationByID.values().iterator();
1160                                                               itr.hasNext(); )
1161                     {
1162                         regInfo = (RegistrationInfo)itr.next();
1163                         HashSet discardedRegs = maybeSendDiscardedEvent
1164                                                      (regInfo,groupsMap,false);
1165                         /* Transfer the just-discarded regs to summary set */
1166                         for(Iterator jtr=discardedRegs.iterator();
1167                                                              jtr.hasNext(); ) {
1168                             allDiscardedRegs.add(jtr.next());
1169                         }
1170                     }//end loop
1171                 }//endif
1172                 maybeRemoveDiscardedRegsFromGlobalSet(allDiscardedRegs);
1173             } finally {
1174                 concurrentObj.writeUnlock();
1175             }
1176         }//end run
1177 
1178         /** This method determines, based on the current state of the 
1179          * <code>regInfo</code> parameter, whether or not to send a
1180          * remote discarded event to the regInfo's listener, and then builds
1181          * and sends the event if appropriate. This method is called in
1182          * response to one of the following situations:
1183          * <p>
1184          * 1 after invocation of the public <code>discard</code> method
1185          * 2 after receipt of a "passive" discarded event from the discovery
1186          *   manager.
1187          * <p>
1188          * For case 1, such an event typically indicates what is referred to
1189          * as an "active, communication" discarded event. This term is used
1190          * in this situation because the regInfo takes the specific action
1191          * of requesting that a registrar the client has determined is
1192          * unreachable be discarded.
1193          * <p>
1194          * For case 2, such an event typically indicates what is referred to
1195          * as a "passive, communication" discarded event. This term is used
1196          * here because the discovery manager - not the client - has determined
1197          * that one or more of the previously discovered registrars are now
1198          * unreachable. In this case, the client remains "passive", and it
1199          * is the discovery manager that discards the unreachable registrars
1200          * and notifies the client(s).
1201          * 
1202          * @param regInfo    the data structure record corresponding to the 
1203          *                   registration whose listener will receive the event
1204          * @param groupsMap  mapping from the registrars referenced in the 
1205          *                   just-received event to their corresponding set of
1206          *                   member groups
1207          * @param active     flag indicating whether the event is an "active"
1208          *                   or a "passive" discarded event
1209          *
1210          * @return set of registrars that were discarded for the given regInfo
1211          */
1212         private HashSet maybeSendDiscardedEvent(RegistrationInfo regInfo,
1213                                                 Map<ServiceRegistrar,String[]> groupsMap,
1214                                                 boolean active)
1215         {
1216             HashSet discardedRegs = new HashSet(groupsMap.size()); //return val
1217             /* If no interest in groups or locators, go to next regInfo*/
1218             if(     (regInfo.groups != null) && ((regInfo.groups).size() == 0)
1219                  && ((regInfo.locators).size() == 0) )
1220             {
1221                 return discardedRegs;
1222             }
1223             HashMap<ServiceRegistrar,String[]> discardMap = new HashMap<ServiceRegistrar,String[]>(groupsMap.size());
1224             /* loop thru the (registrar,groups) pairs, find regs to discard */
1225             Set eSet = groupsMap.entrySet();
1226             for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
1227                 Map.Entry pair = (Map.Entry)itr.next();
1228                 ServiceRegistrar reg = (ServiceRegistrar)pair.getKey();
1229                 /* Include the current reg in the discard map only if that
1230                  * reg is in the regInfo's discovered set.
1231                  */
1232                 if( (regInfo.discoveredRegsMap).containsKey(reg) ) {
1233                     /* The groups corresponding to the discarded registrar that
1234                      * arrived in the event may be more up-to-date than the
1235                      * groups associated with the registrar in the global map.
1236                      * Thus, if the event is a passive communication discarded
1237                      * event, when determining whether the regInfo is still
1238                      * interested in the discarded registrar, use the old group
1239                      * info rather than the group info sent in the event.
1240                      */
1241                     String[] regGroups = (active ? (String[])pair.getValue() :
1242                     ((LocatorGroupsStruct)allDiscoveredRegs.get(reg)).groups );
1243 
1244                     if( active || interested(regGroups,regInfo.groups) ) {
1245                         discardMap.put(reg,regGroups);
1246                         discardedRegs.add(reg);
1247                         (regInfo.discoveredRegsMap).remove(reg);
1248                     }//end if
1249                 }//end if
1250             }//end loop
1251             /* Build and send the "discarded event" */
1252             RemoteDiscoveryEvent event = buildEvent(regInfo,discardMap,true);
1253             if(event != null) {
1254                 queueEvent(regInfo,event);
1255                 logInfoEvents("DiscardedEventTask.run(): "
1256                               +"DISCARDED Event SENT to regInfo\n");
1257             }
1258             return discardedRegs;
1259         }//end maybeSendDiscardedEvent
1260 
1261     }//end class DiscardedEventTask
1262 
1263     /** This class represents a <code>Task</code> object that is placed
1264      *  in the <code>TaskManager</code> queue for processing in the thread
1265      *  pool. Instances of this class are placed on the task queue when
1266      *  registrations request that a given registrar be discarded. 
1267      *  <p>
1268      *  The <code>run</code> method of this class will remove the indicated
1269      *  registrar from the registration's set of discovered registrars and
1270      *  if successfully removed, will build and send a remote discarded event
1271      *  to the registration's listener.
1272      */
1273     private final class DiscardRegistrarTask implements Runnable {
1274         /** Data structure record corresponding to the registration that has
1275          *  requested to have one of its discovered registrars discarded
1276          */
1277         public final RegistrationInfo regInfo;
1278         /** The registrar to discard */
1279         public final ServiceRegistrar registrar;
1280         /** Constructs an instance of this class and stores the registration
1281          *  information.
1282          */
1283         public DiscardRegistrarTask(RegistrationInfo regInfo,
1284                                     ServiceRegistrar registrar)
1285         {
1286             this.regInfo   = regInfo;
1287             this.registrar = registrar;
1288         }//end constructor
1289         /** This method attempts to remove the indicated registrar from
1290          *  the registration's set of discovered registrars. If successful,
1291          *  this method builds and sends a remote discarded event to the
1292          *  registration's listener.
1293          */
1294         public void run() {
1295             concurrentObj.writeLock();
1296             try {
1297                 logInfoTasks("DiscardRegistrarTask.run(): "
1298                              +"registrar requested to be discarded");
1299                 /* Remove registrar from regInfo's set and send event */
1300                 if( (regInfo.discoveredRegsMap).remove(registrar) != null) {
1301                     HashMap groupsMap = mapRegToGroups(registrar,
1302                ((LocatorGroupsStruct)allDiscoveredRegs.get(registrar)).groups);
1303 
1304                     RemoteDiscoveryEvent event = buildEvent
1305                                                       (regInfo,groupsMap,true);
1306                     if(event != null) {
1307                         queueEvent(regInfo,event);
1308                         logInfoEvents("DiscardRegistrarTask.run(): "
1309                                       +"DISCARDED Event was SENT\n");
1310                     }//endif
1311                     maybeRemoveDiscardedRegFromGlobalSet(registrar);
1312                 }
1313             } finally {
1314                 concurrentObj.writeUnlock();
1315             }
1316         }//end run
1317     }//end class DiscardRegistrarTask
1318 
1319     /** This class represents a <code>Task</code> object that is placed
1320      *  in the <code>TaskManager</code> queue for processing in the thread
1321      *  pool.  An instance of this class is placed on the task queue when a
1322      *  <code>DiscoveryEvent</code> instance indicating a changed event
1323      *  is received from the local discovery process. 
1324      *  <p>
1325      *  The <code>run</code> method of this class will process event
1326      *  information resulting from a change in the state of the member
1327      *  groups of one or more lookup services (registrars). This task
1328      *  analyzes the group information in the event and, based on that
1329      *  information, determines which active registrations are no longer
1330      *  interested in the registrars referenced in the event. A
1331      *  <code>RemoteDiscoveryEvent</code> indicating a discarded event
1332      *  will be sent to each active registration that has lost interest
1333      *  in any of the registrars of the event.
1334      */
1335     private final class ChangedEventTask implements Runnable {
1336         /** The local event sent by the discovery manager. */
1337         public final DiscoveryEvent event;
1338         /** Constructs an instance of this class and stores the event*/
1339         public ChangedEventTask(DiscoveryEvent event) {
1340             this.event = event;
1341         }//end constructor
1342         /** This method processes the local discovery event information and
1343          *  determines, based on the current state of each active 
1344          *  registration, to which such registration the appropriate
1345          *  <code>RemoteDiscoveryEvent</code> should be sent. After making
1346          *  the determination, the remote event appropriate for each 
1347          *  registration is constructed and sent.
1348          */
1349         public void run() {
1350             concurrentObj.writeLock();
1351             logInfoTasks("ChangedEventTask.run(): processing CHANGED "
1352                          +"event from discovery manager");
1353             try {
1354                 Map groupsMap = event.getGroups();
1355                 HashSet allDiscardedRegs = new HashSet(groupsMap.size());
1356                 HashMap locatorMap = new HashMap(groupsMap.size());
1357                 /* Retrieve the locators of each registrar in the event */
1358                 for(Iterator itr = (groupsMap.keySet()).iterator();
1359                                                             itr.hasNext(); )
1360                 {
1361                     ServiceRegistrar reg = (ServiceRegistrar)itr.next();
1362                     locatorMap.put(reg,
1363                     ((LocatorGroupsStruct)allDiscoveredRegs.get(reg)).locator);
1364                 }//end loop
1365 
1366                 for( Iterator itr=registrationByID.values().iterator();
1367                                                               itr.hasNext(); )
1368                 {
1369                     RegistrationInfo regInfo = (RegistrationInfo)itr.next();
1370                     HashSet discardedRegs = maybeSendDiscardedEvent
1371                                                 (regInfo,groupsMap,locatorMap);
1372                     /* Transfer the just-discarded regs to the summary set */
1373                     for(Iterator jtr=discardedRegs.iterator();jtr.hasNext(); ){
1374                         allDiscardedRegs.add(jtr.next());
1375                     }//end loop
1376                 }//end loop
1377                 maybeRemoveDiscardedRegsFromGlobalSet(allDiscardedRegs);
1378                 updateGroupsInGlobalSet(groupsMap); //replace with new groups
1379             } finally {
1380                 concurrentObj.writeUnlock();
1381             }
1382         }//end run
1383 
1384         /** This method determines, based on the current state of the 
1385          * <code>regInfo</code> parameter, whether or not to send a
1386          * remote discarded event to the regInfo's listener, and then builds
1387          * and sends the event if appropriate. This method is called in
1388          * response to the receipt of a changed event from the discovery
1389          * manager. 
1390          * <p>
1391          * Such an event may indicate what is referred to as a
1392          * "passive, no interest" discard; passive because the event 
1393          * resulted from action taken by the discovery manager rather than
1394          * the client, and no interest because the discovery manager sends
1395          * such an event when it determines that one or more of the 
1396          * previously discovered registrars - although still reachable -
1397          * have changed their member groups in such a way that they may
1398          * now be of no interest to one or more of the client registrations.
1399          * <p>
1400          * Note that changed events can be sent for registrars having a
1401          * locator and member groups that the current regInfo never asked
1402          * to be discovered. This can happen because some other regInfo
1403          * asked that the registrar's locator or groups be discovered. 
1404          * <p>
1405          * If a particular registrar is contained in the discovered set 
1406          * of the given regInfo, then we know that that regInfo must
1407          * have requested discovery of the registrar (through either
1408          * locator or group discovery). If the registrar is not contained
1409          * in that set, then there's no need to proceed with the processing
1410          * of the registrar since we don't want to send a discarded event
1411          * to a regInfo that was never interested in that registrar in the
1412          * first place.
1413          * <p>
1414          * If the locator of the registrar is contained in the regInfo's
1415          * set of locators to discover, then that regInfo is considered
1416          * "still interested" in the registrar; and so no discarded event
1417          * is sent to the regInfo.
1418          * <p>
1419          * Thus, a discarded event is sent to the given regInfo only
1420          * if the regInfo is not interested in discovering the registrar
1421          * through locator discovery, and the registrar's member groups have
1422          * changed in such a way that it now belongs to groups that
1423          * the regInfo is not interested in discovering and joining.
1424          *
1425          * @param regInfo    the data structure record corresponding to the 
1426          *                   registration whose listener will receive the event
1427          * @param groupsMap  mapping from the registrars referenced in the 
1428          *                   just-received event to their corresponding set of
1429          *                   member groups
1430          * @param locatorMap mapping from the registrars referenced in the 
1431          *                   just-received event to their corresponding locator
1432          *
1433          * @return the registrars that were discarded for the given regInfo
1434          */
1435         private HashSet maybeSendDiscardedEvent(RegistrationInfo regInfo,
1436                                                 Map groupsMap,
1437                                                 Map locatorMap)
1438         {
1439             /* For each registrar discard candidate, send a discarded event if:
1440              *   The candidate is in the discovered set and
1441              *    a. regInfo is configured for at least group discovery
1442              *    b. candidate is NOT to be discovered by locator discovery
1443              *    c. regInfo is no longer interested in the candidate's groups
1444              */
1445             HashSet discardedRegs = new HashSet(groupsMap.size()); //return val
1446             /* If this regInfo isn't interested in groups, go to next regInfo*/
1447             if( (regInfo.groups != null) && ((regInfo.groups).size() == 0) )
1448             {
1449                 return discardedRegs;
1450             }
1451             HashMap discardMap = new HashMap(groupsMap.size());
1452             /* loop thru the (registrar,groups) pairs, find regs to discard */
1453             Set eSet = groupsMap.entrySet();
1454             for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
1455                 Map.Entry pair = (Map.Entry)itr.next();
1456                 ServiceRegistrar reg = (ServiceRegistrar)pair.getKey();
1457                 String[] regGroups = (String[])pair.getValue();
1458                 LookupLocator regLoc = (LookupLocator)locatorMap.get(reg);
1459                 /* Include the current reg in the discard map only if that
1460                  * reg is in the regInfo's discovered set, the regInfo
1461                  * is not interested in discovering the reg through locator
1462                  * discovery, and the reg's member groups have changed in
1463                  * such a way that it now belongs to groups that the regInfo
1464                  * is not interested in discovering and joining.
1465                  */
1466                 if(    ( (regInfo.discoveredRegsMap).containsKey(reg) )
1467                     && (!interested(regLoc,regGroups,
1468                                          regInfo.locators,regInfo.groups)) )
1469                 {
1470                     discardMap.put(reg,regGroups);
1471                     discardedRegs.add(reg);
1472                     (regInfo.discoveredRegsMap).remove(reg);
1473                 }
1474             }//end loop
1475             /* Build and send the "discarded event" */
1476             RemoteDiscoveryEvent event = buildEvent(regInfo,discardMap,true);
1477             if(event != null) {
1478                 queueEvent(regInfo,event);
1479                 logInfoEvents("ChangedEventTask.run(): "
1480                               +"DISCARDED Event was SENT\n");
1481             }//endif
1482             return discardedRegs;
1483         }//end maybeSendDiscardedEvent
1484     }//end class ChangedEventTask
1485 
1486     /** This class represents a <code>Task</code> object that is placed
1487      *  in the <code>TaskManager</code> queue for processing in the thread
1488      *  pool. Instances of this class are placed on the task queue when
1489      *  a registration has requested the augmentation of the set of groups
1490      *  that currently will be discovered for it.
1491      */
1492     private final class AddGroupsTask implements Runnable {
1493         /** Data structure record of the registration that made the request */
1494         public final RegistrationInfo regInfo;
1495         /** The group set with which to replace the registration's old set */
1496         public final String[] groups;
1497         /** Constructs an instance of this class and stores the input */
1498         public AddGroupsTask(RegistrationInfo regInfo, String[] groups) {
1499             this.regInfo = regInfo;
1500             this.groups  = groups;
1501         }//end constructor
1502         public void run() {
1503             /* For the regInfo associated with the current instance of this
1504              * task, do the following:
1505              * a. in the given regInfo data structure, add the new groups,
1506              *    with duplicates removed, to that regInfo's current set of
1507              *    desired groups
1508              * b. from the global mapping of all currently discovered 
1509              *    registrars to (locator,groups) pairs, retrieve the elements
1510              *    that contain registrars belonging to groups that regInfo
1511              *    should now be interested in as a result of the call to
1512              *    addGroups
1513              * c. for each registrar-to-(locator,groups) mapping retrieved in
1514              *    b. above, add that mapping to the given regInfo data
1515              *    structure's discovered state (these are the registrars that
1516              *    were previously discovered for OTHER regInfo's, not the
1517              *    current regInfo)
1518              * d. for each of the registrars previously discovered for other
1519              *    registrations that belong to any of the new groups regInfo
1520              *    is now interested in as a result of the call to addGroups,
1521              *    queue a remote discovery event to be sent to that regInfo's
1522              *    listener
1523              * e. if any of the new groups regInfo is now interested in as
1524              *    as a result of the call to addGroups were not previously
1525              *    in the local discovery manager's managed set of groups,
1526              *    (and the local discovery manager is currently not configured
1527              *    to discover ALL_GROUPS), add the new groups to the local
1528              *    discovery manager so that when that manager does discover
1529              *    one of those groups in the future, a remote discovered
1530              *    event will be sent to the given regInfo's listener
1531              */
1532             concurrentObj.writeLock();
1533             try {
1534                 HashSet newGroupSet = addRegInfoGroups(regInfo,groups);  // a.
1535                 if(newGroupSet.size() > 0) {
1536                     logInfoTasks("AddGroupsTask.run(): adding to the "
1537                                  +"registration's groups");
1538                     Map discoveredRegs = getDesiredRegsByGroup
1539                                                               (regInfo); // b.
1540                     Map regsAdded = regInfo.addToDiscoveredRegs
1541                                                        (discoveredRegs); // c.
1542                     RemoteDiscoveryEvent event = buildEvent
1543                                               (regInfo,regsAdded,false); // d.
1544                     if(event != null) {
1545                         queueEvent(regInfo,event);                       // d.
1546                         logInfoEvents("AddGroupsTask.run(): DISCOVERED "
1547                                       +"Event was SENT\n");
1548                     }//endif
1549                     updateDiscoveryMgrGroups();                          // e.
1550                 }//endif(newGroupSet.size() > 0)
1551             } finally {
1552                 concurrentObj.writeUnlock();
1553             }
1554         }//end run
1555 
1556         /** Augments the registration's managed set of groups with the new
1557          *  groups.
1558          *
1559          * @return the set of new groups added to regInfo's desired groups
1560          */
1561         private HashSet addRegInfoGroups(RegistrationInfo regInfo,
1562                                          String[] groups)
1563         {
1564             /* Build a HashSet (removes duplicates) from the input groups */
1565             HashSet newGroupSet = new HashSet(1);
1566             for(int i=0;i<groups.length;i++) {
1567                 newGroupSet.add(groups[i]);
1568             }//end loop
1569             /* If the input set was not empty, add the new groups to the 
1570              * registration's managed set of groups.
1571              */
1572             if( newGroupSet.size() > 0 ) {
1573                 (regInfo.groups).addAll(newGroupSet);
1574             }//endif
1575             return newGroupSet;
1576         }//end addRegInfoGroups
1577 
1578     }//end class AddGroupsTask
1579 
1580     /** This class represents a <code>Task</code> object that is placed
1581      *  in the <code>TaskManager</code> queue for processing in the thread
1582      *  pool. Instances of this class are placed on the task queue when
1583      *  a registration has requested the replacement of the set of groups
1584      *  that currently will be discovered for it.
1585      */
1586     private final class SetGroupsTask implements Runnable {
1587         /** Data structure record of the registration that made the request */
1588         public final RegistrationInfo regInfo;
1589         /** The group set with which to replace the registration's old set */
1590         public final String[] groups;
1591         /** Constructs an instance of this class and stores the input */
1592         public SetGroupsTask(RegistrationInfo regInfo, String[] groups) {
1593             this.regInfo = regInfo;
1594             this.groups  = groups;
1595         }//end constructor
1596         public void run() {
1597             /* For the regInfo associated with the current instance of this
1598              * task, do the following:
1599              * a. from the global mapping of all currently discovered 
1600              *    registrars to their (locator,groups) pair, retrieve the
1601              *    elements that contain registrars belonging to groups that
1602              *    regInfo was interested in PRIOR to the call to setGroups
1603              * b. in the given regInfo data structure, replace that regInfo's
1604              *    current set of desired groups with the new set of desired
1605              *    groups that resulted from the call to setGroups
1606              * c. again from the global mapping of all currently discovered 
1607              *    registrars to (locator,groups) pairs, retrieve the elements
1608              *    that contain registrars belonging to groups that regInfo
1609              *    should now be interested in as a result of the call to
1610              *    setGroups
1611              * d. for each registrar-to-(locator,groups) mapping retrieved in
1612              *    c. above, add that mapping to the given regInfo data
1613              *    structure's state (these are the registrars that were
1614              *    previously discovered for OTHER regInfo's, not the current
1615              *    regInfo)
1616              * e. for each of the registrars previously discovered for other
1617              *    registrations that belong to any of the new groups regInfo
1618              *    is now interested in as a result of the call to setGroups,
1619              *    queue a remote discovery event to be sent to that regInfo's
1620              *    listener
1621              * f. from the mapping of already-discovered registrars that 
1622              *    regInfo was interested in prior to the call to setGroups
1623              *    (the mapping retrieved in a. above), retrieve the elements
1624              *    that contain registrars belonging to groups that regInfo is
1625              *    no longer interested in due to the call to setGroups
1626              * g. for each registrar-to-(locator,groups) mapping retrieved in
1627              *    f. above, remove that mapping from the given regInfo data
1628              *    structure's state, and queue a remote discarded event to be
1629              *    sent to that regInfo's listener
1630              * h. if any of the new groups regInfo is now interested in as
1631              *    as a result of the call to setGroups were not previously
1632              *    in the local discovery manager's managed set of groups,
1633              *    add those groups to that discovery manager so that when
1634              *    that manager does discover one of those groups in the  
1635              *    future, a remote discovered event will be sent to the given
1636              *    regInfo's listener
1637              */
1638             concurrentObj.writeLock();
1639             try {
1640                 logInfoTasks("SetGroupsTask.run(): setting the "
1641                              +"registration's groups");
1642                 Map oldDesiredRegs = getDesiredRegsByGroup(regInfo);     // a.
1643                 setRegInfoGroups(regInfo,groups);                        // b.
1644                 Map newDesiredRegs = getDesiredRegsByGroup(regInfo); // c.
1645                 Map regsAdded = regInfo.addToDiscoveredRegs
1646                                                        (newDesiredRegs); // d.
1647                 RemoteDiscoveryEvent event = buildEvent
1648                                               (regInfo,regsAdded,false); // e.
1649                 if(event != null) {
1650                     queueEvent(regInfo,event);                           // e.
1651                     logInfoEvents("SetGroupsTask.run(): DISCOVERED "
1652                                   +"Event was SENT\n");
1653                 }//endif
1654                 Map discardRegs = getUndesiredRegsByGroup
1655                                                (oldDesiredRegs,regInfo); // f.
1656                 for(Iterator itr = (discardRegs.keySet()).iterator();
1657                                                              itr.hasNext(); )
1658                 {
1659                     (regInfo.discoveredRegsMap).remove(itr.next());     // g.
1660                 }//end loop
1661                 event = buildEvent(regInfo,discardRegs,true);           // g.
1662                 if(event != null) {
1663                     queueEvent(regInfo,event);                          // g.
1664                     logInfoEvents("SetGroupsTask.run(): "
1665                                   +"DISCARDED Event was SENT\n");
1666                 }//endif
1667                 updateDiscoveryMgrGroups();                             // h.
1668             } finally {
1669                 concurrentObj.writeUnlock();
1670             }
1671         }//end run
1672 
1673         /** Replaces the registration's managed set of groups with the new
1674          *  groups (even if the new set of groups is empty -- this just means
1675          *  group discovery will be "turned off" for this registration).
1676          */
1677         private void setRegInfoGroups(RegistrationInfo regInfo,
1678                                       String[] groups)
1679         {
1680             if(groups == DiscoveryGroupManagement.ALL_GROUPS) {
1681                 regInfo.groups = null;
1682             } else {
1683                 /* Build a HashSet from the input set */
1684                 HashSet newGroups = new HashSet();
1685                 for(int i=0;i<groups.length;i++) {
1686                     newGroups.add(groups[i]);
1687                 }//end loop
1688                 /* Prepare the registration's managed set for replacement */
1689                 if(regInfo.groups == null) {
1690                     regInfo.groups = new HashSet();
1691                 } else {
1692                     (regInfo.groups).clear();
1693                 }//endif
1694                 /* Replace the registration's managed set with the new set */
1695                 (regInfo.groups).addAll(newGroups);
1696             }//end if (groups == DiscoveryGroupManagement.ALL_GROUPS)
1697         }//end setRegInfoGroups
1698     }//end class SetGroupsTask
1699 
1700     /** This class represents a <code>Task</code> object that is placed
1701      *  in the <code>TaskManager</code> queue for processing in the thread
1702      *  pool. Instances of this class are placed on the task queue when
1703      *  a registration has requested the removal of a set of groups from
1704      *  the current set of groups to discover for it.
1705      */
1706     private final class RemoveGroupsTask implements Runnable {
1707         /** Data structure record of the registration that made the request */
1708         public final RegistrationInfo regInfo;
1709         /** The groups to remove from the registration's old set */
1710         public final String[] groups;
1711         /** Constructs an instance of this class and stores the input */
1712         public RemoveGroupsTask(RegistrationInfo regInfo, String[] groups) {
1713             this.regInfo = regInfo;
1714             this.groups  = groups;
1715         }//end constructor
1716         public void run() {
1717             concurrentObj.writeLock();
1718             try {
1719                 if(groups.length == 0) return; // nothing from which to remove
1720                logInfoTasks("RemoveGroupsTask.run(): removing groups from "
1721                             +"the registration's current group set");
1722                 /* regInfo's discovered regs (by group) previously desired */
1723                 Map oldDesiredRegs = getDesiredRegsByGroup(regInfo);
1724                 /* update regInfo's desired regs */
1725                 removeRegInfoGroups(regInfo,groups);
1726                 /* regInfo's discovered regs (by group) no longer desired */
1727                 Map discardRegs = getUndesiredRegsByGroup(oldDesiredRegs,
1728                                                           regInfo);
1729                 /* remove regInfo's undesired regs from its discovered map */
1730                 for(Iterator itr = (discardRegs.keySet()).iterator();
1731                                                               itr.hasNext(); )
1732                 {
1733                     (regInfo.discoveredRegsMap).remove(itr.next());
1734                 }//end loop
1735                 RemoteDiscoveryEvent event = buildEvent
1736                                                     (regInfo,discardRegs,true);
1737                 if(event != null) {
1738                     queueEvent(regInfo,event);
1739                     logInfoEvents("RemoveGroupsTask.run(): "
1740                                   +"DISCARDED Event was SENT\n");
1741                 }//endif
1742                 updateDiscoveryMgrGroups(); // may send more discards
1743             } finally {
1744                 concurrentObj.writeUnlock();
1745             }
1746         }//end run
1747 
1748         /** Removes the elements of the given set from the given registration's
1749          *  current set of groups to discover.
1750          */
1751         private void removeRegInfoGroups(RegistrationInfo regInfo,
1752                                          String[] groups)
1753         {
1754             HashSet<String> removeSet = new HashSet<String>();
1755             int l = groups.length;
1756             for(int i = 0; i < l; i++) {
1757                 removeSet.add(groups[i]);
1758             }//end loop
1759             (regInfo.groups).removeAll(removeSet);
1760         }//end setRegInfoGroups
1761 
1762     }//end class RemoveGroupsTask
1763 
1764     /** This class represents a <code>Task</code> object that is placed
1765      *  in the <code>TaskManager</code> queue for processing in the thread
1766      *  pool. Instances of this class are placed on the task queue when
1767      *  a registration has requested the augmentation of the set of locators
1768      *  that currently will be discovered for it.
1769      */
1770     private final class AddLocatorsTask implements Runnable {
1771         /** Data structure record of the registration that made the request */
1772         public final RegistrationInfo regInfo;
1773         /** The locator set with which to replace the registration's old set */
1774         public final LookupLocator[] locators;
1775         /** Constructs an instance of this class and stores the input */
1776         public AddLocatorsTask(RegistrationInfo regInfo,
1777                                LookupLocator[]  locators)
1778         {
1779             this.regInfo = regInfo;
1780             this.locators  = locators;
1781         }//end constructor
1782         public void run() {
1783             /* For the regInfo associated with the current instance of this
1784              * task, do the following:
1785              * a. in the given regInfo data structure, add the new locators,
1786              *    with duplicates removed, to that regInfo's current set of
1787              *    desired locators
1788              * b. from the global mapping of all currently discovered 
1789              *    registrars to (locator,groups) pairs, retrieve the elements
1790              *    that contain registrars having locators that regInfo
1791              *    should now be interested in as a result of the call to
1792              *    addLocators
1793              * c. for each registrar-to-(locator,groups) mapping retrieved in
1794              *    b. above, add that mapping to the given regInfo data
1795              *    structure's discovered state (these are the registrars that
1796              *    were previously discovered for OTHER regInfo's, not the
1797              *    current regInfo)
1798              * d. for each of the registrars previously discovered for other
1799              *    registrations that have locators equal to any of the new
1800              *    locators regInfo is now interested in as a result of the
1801              *    call to addLocators, queue a remote discovery event to be
1802              *    sent to that regInfo's listener
1803              * e. if any of the new locators regInfo is now interested in as
1804              *    as a result of the call to addLocators were not previously
1805              *    in the local discovery manager's managed set of locators,
1806              *    add the new locators to the local discovery manager so that
1807              *    when that manager does discover one of those locators in
1808              *    the future, a remote discovered event will be sent to the
1809              *    given regInfo's listener
1810              */
1811             concurrentObj.writeLock();
1812             try {
1813                 HashSet newLocSet = addRegInfoLocators(regInfo,locators);// a.
1814                 if(newLocSet.size() > 0) {
1815                     logInfoTasks("AddLocatorsTask.run(): adding to the "
1816                                  +"registration's locators");
1817                     Map discoveredRegs = getDesiredRegsByLocator
1818                                                               (regInfo); // b.
1819                     Map regsAdded = regInfo.addToDiscoveredRegs
1820                                                        (discoveredRegs); // c.
1821                     RemoteDiscoveryEvent event = buildEvent
1822                                               (regInfo,regsAdded,false); // d.
1823                     if(event != null) {
1824                         queueEvent(regInfo,event);                       // d.
1825                         logInfoEvents("AddLocatorsTask.run(): DISCOVERED "
1826                                       +"Event was SENT\n");
1827                     }//endif
1828                     updateDiscoveryMgrLocators();                        // e.
1829                 }//endif(newLocSet.size() > 0)
1830             } finally {
1831                 concurrentObj.writeUnlock();
1832             }
1833         }//end run
1834 
1835         /** Augments the registration's managed set of locators with the new
1836          *  locators.
1837          *
1838          * @return the set of new locators added to regInfo's desired locators
1839          */
1840         private HashSet addRegInfoLocators(RegistrationInfo regInfo,
1841                                            LookupLocator[]  locators)
1842         {
1843             /* Build a HashSet (removes duplicates) from the input locators */
1844             HashSet newLocSet = new HashSet(1);
1845             for(int i=0;i<locators.length;i++) {
1846                 newLocSet.add(locators[i]);
1847             }//end loop
1848             /* If the input set was not empty, add the new locators to the 
1849              * registration's managed set of locators.
1850              */
1851             if( newLocSet.size() > 0 ) {
1852                 (regInfo.locators).addAll(newLocSet);
1853             }//endif
1854             return newLocSet;
1855         }//end addRegInfoLocators
1856     }//end class AddLocatorsTask
1857 
1858     /** This class represents a <code>Task</code> object that is placed
1859      *  in the <code>TaskManager</code> queue for processing in the thread
1860      *  pool. Instances of this class are placed on the task queue when
1861      *  a registration has requested the replacement of the set of locators
1862      *  that currently will be discovered for it.
1863      */
1864     private final class SetLocatorsTask implements Runnable {
1865         /** Data structure record of the registration that made the request */
1866         public final RegistrationInfo regInfo;
1867         /** The locator set with which to replace the registration's old set */
1868         public final LookupLocator[] locators;
1869         /** Constructs an instance of this class and stores the input */
1870         public SetLocatorsTask(RegistrationInfo regInfo,
1871                                LookupLocator[] locators)
1872         {
1873             this.regInfo  = regInfo;
1874             this.locators = locators;
1875         }//end constructor
1876         public void run() {
1877             /* For the regInfo associated with the current instance of this
1878              * task, do the following:
1879              * a. from the global mapping of all currently discovered 
1880              *    registrars to their (locator,groups) pair, retrieve the
1881              *    elements that contain registrars having locators that
1882              *    regInfo was interested in PRIOR to the call to setGroups
1883              * b. in the given regInfo data structure, replace that regInfo's
1884              *    current set of desired locators with the new set of desired
1885              *    locators that resulted from the call to setLocators
1886              * c. again from the global mapping of all currently discovered 
1887              *    registrars to (locator,groups) pairs, retrieve the elements
1888              *    that contain registrars having locators that regInfo should
1889              *    now be interested in as a result of the call to setLocators
1890              * d. for each registrar-to-(locator,groups) mapping retrieved in
1891              *    c. above, add that mapping to the given regInfo data
1892              *    structure's state (these are the registrars that were
1893              *    previously discovered for OTHER regInfo's, not the current
1894              *    regInfo)
1895              * e. for each of the registrars previously discovered for other
1896              *    registrations that have locators equal to the new locators
1897              *    regInfo is now interested in as a result of the call to
1898              *    setLocators, queue a remote discovery event to be sent to
1899              *    that regInfo's listener
1900              * f. from the mapping of already-discovered registrars that 
1901              *    regInfo was interested in prior to the call to setLocators
1902              *    (the mapping retrieved in a. above), retrieve the elements
1903              *    that contain registrars having locators that regInfo is
1904              *    no longer interested in due to the call to setLocators
1905              * g. for each registrar-to-(locator,groups) mapping retrieved in
1906              *    f. above, remove that mapping from the given regInfo data
1907              *    structure's state, and queue a remote discarded event to be
1908              *    sent to that regInfo's listener
1909              * h. if any of the new locators regInfo is now interested in as
1910              *    as a result of the call to setLocators were not previously
1911              *    in the local discovery manager's managed set of locators,
1912              *    add those locators to that discovery manager so that when
1913              *    that manager does discover one of those locators in the  
1914              *    future, a remote discovery event will be sent to the given
1915              *    regInfo's listener
1916              */
1917             concurrentObj.writeLock();
1918             try {
1919                 logInfoTasks("SetLocatorsTask.run(): setting the "
1920                              +"registration's locators");
1921                 Map oldDesiredRegs = getDesiredRegsByLocator(regInfo);    // a.
1922                 setRegInfoLocators(regInfo,locators);                     // b.
1923                 Map newDesiredRegs = getDesiredRegsByLocator(regInfo);// c.
1924                 Map regsAdded = regInfo.addToDiscoveredRegs
1925                                                        (newDesiredRegs);  // d.
1926                 RemoteDiscoveryEvent event = buildEvent
1927                                               (regInfo,regsAdded,false);  // e.
1928                 if(event != null) {
1929                     queueEvent(regInfo,event);                            // e.
1930                     logInfoEvents("SetLocatorsTask.run(): DISCOVERED "
1931                                   +"Event was SENT\n");
1932                 }//endif
1933                 Map undesiredRegs = getUndesiredRegsByLocator
1934                                                (oldDesiredRegs,regInfo);  // f.
1935                 HashMap discardRegs = new HashMap(undesiredRegs.size());
1936                 for(Iterator itr = (undesiredRegs.keySet()).iterator();
1937                                                               itr.hasNext(); )
1938                 {
1939                     ServiceRegistrar reg = (ServiceRegistrar)itr.next();
1940                     (regInfo.discoveredRegsMap).remove(reg);              // g.
1941                     discardRegs.put(reg,
1942                     ((LocatorGroupsStruct)allDiscoveredRegs.get(reg)).groups);
1943 
1944                 }//end loop
1945                 event = buildEvent(regInfo,discardRegs,true);             // g.
1946                 if(event != null) {
1947                     queueEvent(regInfo,event);                            // g.
1948                     logInfoEvents("SetLocatorsTask.run(): "
1949                                   +"DISCARDED Event was SENT\n");
1950                 }//endif
1951                 updateDiscoveryMgrLocators();                             // h.
1952             } finally {
1953                 concurrentObj.writeUnlock();
1954             }
1955         }//end run
1956 
1957         /** Replaces the registration's managed set of locators with the new
1958          *  locators (even if the new set of locators is empty -- this just
1959          *  means locator discovery will be "turned off" for this registration)
1960          */
1961         private void setRegInfoLocators(RegistrationInfo regInfo,
1962                                         LookupLocator[]  locators)
1963         {
1964             /* Build a HashSet from the input set */
1965             HashSet newLocSet = new HashSet();
1966             for(int i=0;i<locators.length;i++) {
1967                 newLocSet.add(locators[i]);
1968             }//end loop
1969             /* Prepare the registration's managed set for replacement */
1970             if(regInfo.locators == null) {
1971                 regInfo.locators = new HashSet();
1972             } else {
1973                 (regInfo.locators).clear();
1974             }//endif
1975             /* Replace the registration's managed set with the new set */
1976             (regInfo.locators).addAll(newLocSet);
1977         }//end setRegInfoLocators
1978 
1979     }//end class SetLocatorsTask
1980 
1981     /** This class represents a <code>Task</code> object that is placed
1982      *  in the <code>TaskManager</code> queue for processing in the thread
1983      *  pool. Instances of this class are placed on the task queue when
1984      *  a registration has requested the removal of a set of locators
1985      *  from the current set of locators to discover for it.
1986      */
1987     private final class RemoveLocatorsTask implements Runnable {
1988         /** Data structure record of the registration that made the request */
1989         public final RegistrationInfo regInfo;
1990         /** The locators to remove from the registration's old set */
1991         public final LookupLocator[] locators;
1992         /** Constructs an instance of this class and stores the input */
1993         public RemoveLocatorsTask(RegistrationInfo regInfo,
1994                                   LookupLocator[] locators)
1995         {
1996             this.regInfo  = regInfo;
1997             this.locators = locators;
1998         }//end constructor
1999         public void run() {
2000             concurrentObj.writeLock();
2001             try {
2002                 if(locators.length == 0) return; //nothing from which to remove
2003 
2004                 logInfoTasks("RemoveLocatorsTask.run(): removing locators "
2005                              +"from the registration's current locator set");
2006                 /* regInfo's discovered regs (by locator) previously desired */
2007                 Map oldDesiredRegs = getDesiredRegsByLocator(regInfo);
2008                 /* update regInfo's desired regs */
2009                 removeRegInfoLocators(regInfo,locators);
2010                 /* regInfo's discovered regs (by locator) no longer desired */
2011                 Map undesiredRegs = getUndesiredRegsByLocator
2012                                                     (oldDesiredRegs,regInfo);
2013                 /* remove regInfo's undesired regs from its discovered map,
2014                  * and construct the registrars-to-groups map for the event
2015                  */
2016                 HashMap discardRegs = new HashMap(undesiredRegs.size());
2017                 for(Iterator itr = (undesiredRegs.keySet()).iterator();
2018                                                               itr.hasNext(); )
2019                 {
2020                     ServiceRegistrar reg = (ServiceRegistrar)itr.next();
2021                     (regInfo.discoveredRegsMap).remove(reg);
2022                     discardRegs.put(reg,
2023                     ((LocatorGroupsStruct)allDiscoveredRegs.get(reg)).groups);
2024                 }//end loop
2025                 /* Construct the registrars-to-groups map for the event */
2026                 RemoteDiscoveryEvent event = buildEvent
2027                                                     (regInfo,discardRegs,true);
2028                 if(event != null) {
2029                     queueEvent(regInfo,event);
2030                     logInfoEvents("SetLocatorsTask.run(): "
2031                                   +"DISCARDED Event was SENT\n");
2032                 }//endif
2033                 updateDiscoveryMgrLocators(); // may send more discards
2034             } finally {
2035                 concurrentObj.writeUnlock();
2036             }
2037         }//end run
2038 
2039         /** Removes the elements of the given set from the given registration's
2040          *  current set of locators to discover.
2041          */
2042         private void removeRegInfoLocators(RegistrationInfo regInfo,
2043                                            LookupLocator[]  locators)
2044         {
2045             /* Build a HashSet from the input set */
2046             HashSet removeSet = new HashSet();
2047             for(int i=0;i<locators.length;i++) {
2048                 removeSet.add(locators[i]);
2049             }//end loop
2050             (regInfo.locators).removeAll(removeSet);
2051         }//end removeRegInfoLocators
2052     }//end class RemoveLocatorsTask
2053 
2054     /** This class represents a <code>Task</code> object that is placed
2055      *  in the <code>TaskManager</code> queue for processing in the thread
2056      *  pool. Instances of this class are placed on the task queue when
2057      *  a remote event is to be sent to a given registration. 
2058      *  <p>
2059      *  Remote events are sent in a separate task such as this to avoid
2060      *  making the remote call to the registration's listener within a
2061      *  synchronization block.
2062      */
2063     private final class SendEventTask implements Runnable {
2064         /** Data structure record corresponding to registration to get event */
2065         public final RegistrationInfo     regInfo;
2066         /** The remote event to send to the given registration's listener */
2067         public final RemoteDiscoveryEvent event;
2068         /** Constructs an instance of this class and stores the registration
2069          *  information.
2070          */
2071         public SendEventTask(RegistrationInfo     regInfo,
2072                              RemoteDiscoveryEvent event)
2073         {
2074             this.regInfo = regInfo;
2075             this.event   = event;
2076         }//end constructor
2077         /** This method sends a <code>RemoteDiscoveryEvent</code> to the
2078          *  listener of the registration that corresponds to the
2079          *  <code>regInfo</code> field of this class. This method handles
2080          *  all exceptions and error conditions in the appropriate manner.
2081          */
2082         public void run() {
2083             try {
2084                 regInfo.listener.notify(event);
2085             } catch (Throwable e) {
2086                 problemLogger.log(Level.INFO, "Exception in SendEventTask", e);
2087                 switch (ThrowableConstants.retryable(e)) {
2088                     case ThrowableConstants.BAD_OBJECT:
2089                         if(e instanceof Error)  throw (Error)e;
2090                     case ThrowableConstants.BAD_INVOCATION:
2091                     case ThrowableConstants.UNCATEGORIZED:
2092                     /* If the listener throws UnknownEvent or some other
2093                      * definite exception, or the listener is gone, it's
2094                      * okay to cancel the lease.
2095                      */
2096                     concurrentObj.writeLock();
2097                     try {
2098                         try {
2099                             logInfoEvents
2100                                        ("  Cancelling lease on registration: "
2101                                         +" (registrationID,leaseID) = ("
2102                                         +regInfo.registrationID+", "
2103                                         +regInfo.leaseID+")");
2104                             cancelLeaseDo(regInfo,regInfo.leaseID);
2105                             addLogRecord(new LeaseCancelledLogObj
2106                                                       (regInfo.registrationID,
2107                                                        regInfo.leaseID));
2108                         } catch (UnknownLeaseException ee) {
2109                         } catch (IOException ee) { }
2110                     } finally {
2111                         concurrentObj.writeUnlock();
2112                     }
2113                 }//end switch
2114             }//end try
2115         }//end run
2116     }//end class SendEventTask
2117 
2118     /**
2119      * Handler class for the persistent storage facility.
2120      * <p>
2121      * At any point during processing in this service, there will exist
2122      * both a 'snapshot' of the service's state and a set of records
2123      * detailing each significant change that has occurred to the state
2124      * since the snapshot was taken. The snapshot information and the
2125      * incremental change information will be stored in separate files
2126      * called, respectively, the snapshot file and the log file. Together,
2127      * these files are used to recover the state of the service after a
2128      * crash or a network outage (or if the service or its ActivationGroup
2129      * is un-registered and then re-registered through the Activation Daemon).
2130      * <p>
2131      * This class contains the methods that are used to record and recover
2132      * the snapshot of the service's state; as well as the method used to
2133      * apply the state changes that were recorded in the log file.
2134      * <p>
2135      * When the ReliableLog class is instantiated, a new instance of this
2136      * class is passed to its constructor so that the methods of this
2137      * class may be invoked by the methods defined in the ReliableLog.
2138      * Because this class extends the LogHandler class associated with
2139      * the ReliableLog class, this class must provide implementations of
2140      * the abstract methods declared in the LogHandler. Also, some of the
2141      * methods defined in this class override the methods of the LogHandler
2142      * in order to customize the handling of snapshot creation and
2143      * retrieval.
2144      * <p>
2145      * Each significant change to the service's state is written to the
2146      * log file as an individual record (when addLogRecord() is invoked).
2147      * After the number of records logged exceeds a pre-defined threshold,
2148      * a snapshot of the state is recorded by invoking -- through the
2149      * ReliableLog and its LogHandler -- the snapshot() method defined in
2150      * this class. After the snapshot is taken, the log file is cleared
2151      * and the incremental log process starts over.
2152      * <p>
2153      * The contents of the snapshot file reflect the DATA contained in
2154      * the fields making up the current state of the service. That data
2155      * represents many changes -- over time -- to the service's state.
2156      * On the other hand, each record written to the log file is an object
2157      * that reflects both the data used and the ACTIONS taken to make one
2158      * change to the service's state at a particular point in time.
2159      * <p>
2160      * During recovery, the state of the service at the time of a crash
2161      * or outage is re-constructed by first retrieving the 'base' state from
2162      * the snapshot file; and then modifying that base state according to
2163      * the records retrieved from the log file. The reconstruction of the
2164      * base state is achieved by invoking the recover() method defined in
2165      * this class. The modifications recorded in the log file are then
2166      * applied to the base state by invoking the applyUpdate() method
2167      * defined in this class. Both recover() and applyUpdate() are invoked
2168      * through the ReliableLog and its associated LogHandler.
2169      * <p>
2170      * NOTE: The following lines must be added to the service's policy file
2171      * <pre>
2172      *     permission java.io.FilePermission "dirname",   "read,write,delete";
2173      *     permission java.io.FilePermission "dirname/-", "read,write,delete";
2174      * </pre>
2175      *     where 'dirname' is the name of the directory path (relative or
2176      *     absolute) where the snapshot and log file will be maintained.
2177      */
2178     static class LocalLogHandler extends LogHandler {
2179         private FiddlerImpl fiddler;
2180         /** No-arg public constructor */
2181         public LocalLogHandler() { }
2182         
2183         synchronized void setFiddler(FiddlerImpl fiddler){
2184             this.fiddler = fiddler;
2185         }
2186 
2187         /* Overrides snapshot() defined in ReliableLog's LogHandler class. */
2188         public synchronized void snapshot(OutputStream out) throws IOException {
2189             fiddler.takeSnapshot(out);
2190         }//end snapshot
2191 
2192         /* Overrides recover() defined in ReliableLog's LogHandler class. */
2193         public synchronized void recover(InputStream in)
2194                             throws IOException, ClassNotFoundException
2195         {
2196             fiddler.recoverSnapshot(in);
2197         }//end recover
2198 
2199         /**
2200          * Required method that implements the abstract applyUpdate()
2201          * defined in ReliableLog's associated LogHandler class.
2202          * <p>
2203          * During state recovery, the recover() method defined in the
2204          * ReliableLog class is invoked. That method invokes the method
2205          * recoverUpdates() which invokes the method readUpdates(). Both
2206          * of those methods are defined in ReliableLog. The method
2207          * readUpdates() retrieves a record from the log file and then
2208          * invokes this method.
2209          * <p>
2210          * This method invokes the version of the method apply() that
2211          * corresponds to the particular type of 'log record' object
2212          * that is input as the first argument. The log record object and its
2213          * corresponding apply() method are defined in one of the so-called
2214          * LogObj classes. Any instance of one the LogObj classes is an
2215          * implementation of the LogRecord interface. The particular
2216          * implementation that is input to this method is dependent on the
2217          * type of record that was originally logged. The apply() method
2218          * will then modify the state of the service in a way dictated
2219          * by the type of record that was retrieved.
2220          */
2221         public synchronized void applyUpdate(Object logRecObj) {
2222             ((LogRecord)logRecObj).apply(fiddler);
2223         }//end applyUpdate
2224     }//end class LocalLogHandler
2225     /* ******************* END Inner Class Definitions ********************* */
2226 
2227     /* ******************* BEGIN Thread Class Definitions ****************** */
2228     /** Thread which is used to monitor the current leases in effect and
2229      *  cancel (expire) those leases with expiration times that have exceeded
2230      *  the current time.
2231      */
2232     static class LeaseExpireThread extends InterruptedStatusThread {
2233 
2234         private final FiddlerImpl fiddler;
2235         
2236         public LeaseExpireThread(FiddlerImpl fiddler) {
2237             super("lease expire");
2238             setDaemon(false);
2239             this.fiddler = fiddler;
2240         }//end constructor
2241 
2242         public void run() {
2243             try {
2244                 fiddler.concurrentObj.writeLock();
2245             } catch (ConcurrentLockException e) {
2246                 return;
2247             }
2248             try {
2249                 while (!hasBeenInterrupted()) {
2250                     long curTime  = System.currentTimeMillis();
2251                     fiddler.minExpiration = Long.MAX_VALUE;
2252                     /* Loop through registrationByTime removing registrations
2253                      * with expiration times that are earlier than the current
2254                      * time. The logic of this loop relies on the fact that 
2255                      * registrationByTime is a TreeMap in which the elements
2256                      * are ordered (in ascending order) by the lease expiration
2257                      * times. Thus, when one registration is encountered with
2258                      * an expiration time that is later than the current time,
2259                      * it can be assumed that all remaining registrations have
2260                      * expiration times that are also later than the current
2261                      * time; and the loop can be exited. Until such a
2262                      * registration is encountered, each registration is
2263                      * removed from its various storage locations.
2264                      */
2265                     while (!fiddler.registrationByTime.isEmpty()) {
2266                         RegistrationInfo regInfo
2267                              = (RegistrationInfo)fiddler.registrationByTime.firstKey();
2268                         if (regInfo.leaseExpiration > curTime) {
2269                             fiddler.minExpiration = regInfo.leaseExpiration;
2270                             break;
2271                         }
2272 	                /* The removal of a registration typically involves the
2273                          * the modification of the managed sets in the
2274                          * discovery manager, which usually involves starting
2275                          * the discovery protocol. An IOException can occur
2276                          * when the discovery protocol fails to start. When
2277                          * such an exception does occur, register an ERROR
2278                          * status attribute (along with a Comment attribute
2279                          * describing the nature of the problem) to all lookup
2280                          * services with which this service is registered. 
2281                          *
2282                          * Administrative clients, as well as clients that use
2283                          * this service should have registered for notification
2284                          * of the existence of this attribute.
2285 	                 */
2286                         try {
2287                             fiddler.removeRegistration(regInfo);
2288                         } catch(IOException e) {
2289                             String eStr = "Failure while removing "
2290                                           +"registration (ID = "
2291                                           +regInfo.registrationID
2292                                           +") from service state";
2293                             if( problemLogger.isLoggable(Level.INFO) ) {
2294                                 problemLogger.log(Level.INFO, eStr, e);
2295                             }//endif
2296                             Entry[] errorAttrs
2297                                     = new Entry[]
2298                                         { new FiddlerStatus(StatusType.ERROR),
2299                                           new Comment(eStr)
2300                                         };
2301                             fiddler.joinMgr.addAttributes(errorAttrs,true);
2302                         }
2303                     }//end while
2304                     try {
2305                         fiddler.leaseExpireThreadSyncObj.await(
2306                                 fiddler.minExpiration - curTime,
2307                                 TimeUnit.MILLISECONDS);
2308                     } catch (InterruptedException ex) {
2309                         Thread.currentThread().interrupt();// restore
2310                         return;
2311                     }
2312                 }//end while            
2313             } finally {
2314                 fiddler.concurrentObj.writeUnlock();
2315             }
2316         }//end run
2317     }//end class LeaseExpireThread
2318 
2319     /**
2320      * Snapshot-taking thread. 
2321      * <p>
2322      * A snapshot is taken when -- after writing a new record to the 
2323      * log file -- it is determined that the size of the log file has 
2324      * exceeded a certain threshold. The code which adds the new record 
2325      * to the log file and which, in turn, decides that a snapshot
2326      * must be taken is "wrapped" in a writer mutex. That is, synchronization
2327      * of processing is achieved in this service through a "reader/writer"
2328      * mutex construct. This construct allows only one writer at any one
2329      * time; but allows an unlimited number of simultaneous readers as
2330      * long as no writer has locked the mutex. During steady-state, it is
2331      * anticipated that far more "read actions" will occur (e.g. discovery
2332      * events being sent) than "write actions" (e.g. modifying the managed
2333      * sets). Since the process of taking a snapshot can be time-consuming,
2334      * if the whole snapshot-taking process occupies that single writer
2335      * mutex, then a significant number of read actions will be un-necessarily
2336      * blocked; possibly resulting in an unacceptable degradation in
2337      * response time. 
2338      * <p>
2339      * It is for the above reason that the process of taking a snapshot is
2340      * performed in a separate thread. The thread waits on the monitor
2341      * belonging to the snapshotThreadSyncObj instance until it is notified
2342      * (or "signalled") that a snapshot must be taken. The notification
2343      * is sent by another thread, created by this service, which determines
2344      * when the conditions are right for a snapshot. The notification takes
2345      * the form of an interrupt indicating that the snapshot monitor is
2346      * available. Although the interrupt is sent while the writer mutex is
2347      * locked, the act of sending the notification is less time-consuming
2348      * than the act of taking the snapshot itself. When the thread receives
2349      * a notification, it awakens and requests a lock on the reader mutex
2350      * (this is all done in the readerWait() method). Because a reader -- not
2351      * a writer -- mutex is locked, read-only processes still have access
2352      * to the system state, so discovery events can be sent and the service's
2353      * state can be queried; but the reader mutex prevents changes to the
2354      * state while the snapshot is in progress.  
2355      * <p>
2356      * Note that the current snapshot is guaranteed to complete before the
2357      * next snapshot request is received. This is because even though
2358      * the act of taking a snapshot can be viewed as a writer process, 
2359      * the fact that the next snapshot notification will be wrapped in a
2360      * writer mutex, combined with the fact that a writer mutex can not
2361      * be locked while a reader mutex is locked, allows the snapshot to
2362      * be treated as a reader process.
2363      */
2364     static class SnapshotThread extends InterruptedStatusThread {
2365         private final FiddlerImpl fiddler;
2366         
2367         /** Not a daemon thread, to avoid termination by jvm during snapshot */
2368         public SnapshotThread(FiddlerImpl fiddler) {
2369             super("snapshot thread");
2370             setDaemon(false);
2371             this.fiddler = fiddler;
2372         }
2373 
2374         public void run() {
2375             try {
2376                 fiddler.concurrentObj.writeLock();
2377             } catch (ConcurrentLockException e) {
2378                 return;
2379             }
2380             try {
2381                 while (!hasBeenInterrupted()) {
2382                     try {
2383                         fiddler.snapshotThreadSyncObj.await();
2384                     } catch (InterruptedException ex) {
2385                         Thread.currentThread().interrupt();// restore
2386                         return;
2387                     }
2388                     try {
2389                         fiddler.log.snapshot();
2390                         fiddler.logFileSize = 0;
2391                     } catch (Exception e) {
2392                         if (hasBeenInterrupted())  return;
2393                         /* If taking the snapshot fails for any reason,
2394                          * then register an ERROR status attribute (along
2395                          * with a Comment attribute describing the nature
2396                          * of the problem) to all lookup services with
2397                          * which this service is registered. 
2398                          *
2399                          * Administrative clients, as well as clients that
2400                          * use this service should have registered for
2401                          * notification of the existence of this attribute.
2402                          */
2403                         String eStr = "Failure while taking a snapshot of "
2404                                       +"the service state";
2405                         problemLogger.log(Level.INFO, eStr, e);
2406                         Entry[] errorAttrs
2407                                 = new Entry[]
2408                                     { new FiddlerStatus(StatusType.ERROR),
2409                                       new Comment(eStr)
2410                                     };
2411                         fiddler.joinMgr.addAttributes(errorAttrs,true);
2412                     }
2413                 }//end while           
2414             } finally {
2415                 fiddler.concurrentObj.writeUnlock();
2416             }
2417         }//end run
2418     }//end class SnapshotThread
2419 
2420     /** Thread which is used to terminate the current executing instance
2421      *  of the Fiddler implementation of the lookup discovery service. 
2422      *  Termination processing is performed in a separate thread (that is,
2423      *  in an instance of this class) in order to avoid deadlock that 
2424      *  can occur because ActivationGroup.inactive will block until all 
2425      *  in-progress RMI calls have completed.
2426      */
2427     private class DestroyThread extends InterruptedStatusThread {
2428         /** Maximum delay for unexport attempts */
2429         private static final long MAX_UNEXPORT_DELAY = 2*TimeConstants.MINUTES;
2430 
2431 	/** Constructor that creates a non-daemon thread */
2432 	public DestroyThread() {
2433 	    super("destroy");
2434 	    /* override inheritance from RMI daemon thread */
2435 	    setDaemon(false);
2436 	}
2437 
2438 	public void run() {
2439 	    /* Must unregister before unexporting. Unregistering makes sure
2440              * that the object corresponding to the given activation ID can
2441              * no longer be activated through that ID.
2442              */
2443 	    if (activationID != null) {
2444 		try {
2445                     activationSystem.unregisterObject(activationID);
2446 		} catch (RemoteException e) {
2447                     problemLogger.log(Level.WARNING, "aborting shutdown - "
2448                                      +"could not unregister activation ID", e);
2449 		    return;//give up until we can at least unregister
2450 		} catch (ActivationException e) {
2451                     problemLogger.log(Levels.HANDLED, "shutdown problem - "
2452                                      +"could not unregister activation ID", e);
2453                 }
2454 	    }
2455             readyState.shutdown();
2456             /* Unexport the object. This removes the object from the RMI
2457              * runtime so that the object can no longer accept incoming RMI
2458              * calls. 
2459              * 
2460              * An attempt to 'gracefully' unexport the object is initially
2461              * made. That is, for a finite period of time, an attempt is
2462              * made to allow all calls to the object that are in progress
2463              * or pending to complete before the object is unexported. If,
2464              * after that finite period of time, the object has not been
2465              * successfully unexported, the object is 'forcibly' unexported;
2466              * that is, the object is unexported even if there are calls to
2467              * the object that are in progress or still pending.
2468              */
2469             final long endTime = System.currentTimeMillis()+MAX_UNEXPORT_DELAY;
2470             boolean unexported = false;
2471             boolean interrupted = false;
2472             /* Unexport only if there are no pending or in-progress calls*/
2473             while(!unexported && (System.currentTimeMillis() < endTime)) {
2474                 unexported = serverExporter.unexport(false);
2475                 if(!unexported) try {
2476                     Thread.sleep(500L);
2477                 } catch (InterruptedException ex) {
2478                     interrupted = true;
2479                     continue;
2480                 }
2481             }//end loop
2482             // Restore the interrupt.
2483             if (interrupted) Thread.currentThread().interrupt();
2484             if(!unexported) {//Not yet unexported. Forcibly unexport
2485                 serverExporter.unexport(true);
2486             }//endif
2487 	    /* all daemons must terminate before deleting persistent store */
2488 	    leaseExpireThread.interrupt();
2489 	    if(log != null) snapshotThread.interrupt();
2490 	    executorService.shutdown();
2491 	    joinMgr.terminate();
2492             joinMgrLDM.terminate();
2493             discoveryMgr.terminate();
2494 	    try {
2495 		leaseExpireThread.join();
2496 		if(log != null) snapshotThread.join();
2497 	    } catch (InterruptedException e) { 
2498                 // Should the interrupt really be swallowed?  Or should we reset
2499                 // the status for later handling?
2500                 Thread.currentThread().interrupt();
2501             }
2502 	    if(log != null) log.deletePersistentStore();
2503 	    if (activationID != null) {
2504                 /* Inform the activation system that the object corresponding
2505                  * to the given activation ID is no longer active.
2506                  */
2507 		try {
2508 		    ActivationGroup.inactive(activationID, serverExporter);
2509 		} catch (RemoteException e) {
2510 		} catch (ActivationException e) { }
2511             } else {//not activatable, tell starter it's ok to release for gc
2512                 if(lifeCycle != null)  lifeCycle.unregister(FiddlerImpl.this);
2513             }//endif(activationID != null)
2514             /* If applicable, logout of the JAAS login session */
2515             if(loginContext != null) {
2516                 try {
2517                     loginContext.logout();
2518                 } catch(Exception e) {
2519                     startupLogger.log(Level.INFO,"Problem logging out of "
2520                                                  +"JAAS login session",e);
2521                 }
2522             }//endif
2523             logInfoShutdown();
2524         }//end run
2525     }//end class DestroyThread
2526     /* ******************* END Thread Class Definitions ******************** */
2527 
2528     /* ************************ BEGIN Public Methods *********************** */
2529     /* -------------------------------------------------------------------- 
2530      * BEGIN net.jini.security.proxytrust.ServerProxyTrust
2531      */
2532     /** 
2533      * Returns a <code>TrustVerifier</code> specific to this service which
2534      * can be used to verify that a given proxy to this service can be
2535      * trusted.
2536      * <p>
2537      * The verifier returned by this method contains the method
2538      * {@link TrustVerifier#isTrustedObject isTrustedObject}. That method 
2539      * can be called with a candidate proxy as the first argument, and
2540      * {@link net.jini.security.TrustVerifier.Context}
2541      * as the second argument. When called in this way, the 
2542      * <code>isTrustedObject</code> determines whether or not the input
2543      * proxy is trusted. Thus, the verifier returned by this method should
2544      * be able to verify as trusted, all proxies to this service; including
2545      * proxies such as leases, event registrations, and administrative
2546      * proxies.
2547      * 
2548      * @return a <code>TrustVerifier</code> which can be used to verify that
2549      *         a given proxy to this service can be trusted.
2550      * 
2551      * @throws java.rmi.NoSuchObjectException if this method is called during
2552      *         service initialization or shutdown processing
2553      * 
2554      * @throws UnsupportedOperationException if the server proxy does not
2555      *	       implement both
2556      *         {@link net.jini.core.constraint.RemoteMethodControl}
2557      *         and {@link TrustEquivalence}
2558      *
2559      * @see net.jini.security.proxytrust.ServerProxyTrust#getProxyVerifier
2560      */
2561     public TrustVerifier getProxyVerifier() throws NoSuchObjectException {
2562 	readyState.check();
2563 	return new ProxyVerifier(innerProxy, proxyID);
2564     }//end getProxyVerifier
2565     /*  END net.jini.security.proxytrust.ServerProxyTrust                   */
2566     /* -------------------------------------------------------------------- */
2567 
2568     /* -------------------------------------------------------------------- 
2569      * BEGIN net.jini.export.ProxyAccessor
2570      */
2571     /**
2572      * Public method that facilitates the use of the mechanism provided by
2573      * {@link org.apache.river.start.ServiceStarter} to create an activatable
2574      * instance of this server.
2575      * 
2576      * @return the inner proxy (stub or dynamic proxy) for the server
2577      */
2578     public Object getProxy() {
2579         return innerProxy;
2580     }//end getProxy
2581     /*  END net.jini.export.ProxyAccessor                                   */
2582     /* -------------------------------------------------------------------- */
2583 
2584     /* -------------------------------------------------------------------- 
2585      * BEGIN org.apache.river.fiddler.proxy.Fiddler --> net.jini.admin.Administrable
2586      */
2587     /** 
2588      * Returns a proxy to the current instance of this class through which
2589      * a client may administer the lookup discovery service
2590      *
2591      * @return a proxy object through which the lookup discovery service
2592      *         may be administered.
2593      * 
2594      * @throws java.rmi.NoSuchObjectException if this method is called during
2595      *         service initialization or shutdown processing
2596      * 
2597      * @throws java.rmi.RemoteException typically, this exception occurs when
2598      *         there is a communication failure between the client and the
2599      *         server.
2600      *
2601      * see org.apache.river.fiddler.proxy.FiddlerAdminProxy#getAdmin (?)
2602      * @see net.jini.admin.Administrable#getAdmin
2603      */
2604     public Object getAdmin() throws NoSuchObjectException, RemoteException {
2605 	readyState.check();
2606         concurrentObj.readLock();
2607         try {
2608             return adminProxy;
2609         } finally {
2610             concurrentObj.readUnlock();
2611         }
2612     }
2613     /*  END org.apache.river.fiddler.proxy.Fiddler --> net.jini.admin.Administrable   */
2614     /* -------------------------------------------------------------------- */
2615 
2616     /* -------------------------------------------------------------------- 
2617      * BEGIN org.apache.river.fiddler.proxy.Fiddler
2618      *                                --> org.apache.river.fiddler.proxy.FiddlerAdmin
2619      *                                         --> net.jini.admin.JoinAdmin
2620      */
2621     /** 
2622      * Returns the current attribute sets for the lookup discovery service. 
2623      * 
2624      * @return array of net.jini.core.entry.Entry containing the current
2625      *         attribute sets for the lookup discovery service
2626      * 
2627      * @throws java.rmi.NoSuchObjectException if this method is called during
2628      *         service initialization or shutdown processing
2629      * 
2630      * @throws java.rmi.RemoteException typically, this exception occurs when
2631      *         there is a communication failure between the client and the
2632      *         lookup discovery service.
2633      *
2634      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#getLookupAttributes
2635      * @see net.jini.admin.JoinAdmin#getLookupAttributes
2636      */
2637     public Entry[] getLookupAttributes()
2638                                  throws NoSuchObjectException, RemoteException
2639     {
2640 	readyState.check();
2641         concurrentObj.readLock();
2642         try {
2643             return thisServicesAttrs;
2644         } finally {
2645             concurrentObj.readUnlock();
2646         }
2647     }//end getLookupAttributes
2648 
2649     /** 
2650      * Adds attribute sets to the current set of attributes associated
2651      * with the lookup discovery service. The resulting set will be used
2652      * for all future registrations with lookup services. The new attribute
2653      * sets are also added to the lookup discovery service's attributes
2654      * on each lookup service with which the lookup discovery service
2655      * is currently registered.
2656      *
2657      * @param  attrSets array of net.jini.core.entry.Entry containing the
2658      *         attribute sets to add
2659      * 
2660      * @throws java.rmi.NoSuchObjectException if this method is called during
2661      *         service initialization or shutdown processing
2662      * 
2663      * @throws java.rmi.RemoteException typically, this exception occurs when
2664      *         there is a communication failure between the client and the
2665      *         lookup discovery service. When this exception does occur, the
2666      *         attributes may or may not have been added successfully.
2667      *
2668      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#addLookupAttributes
2669      * @see net.jini.admin.JoinAdmin#addLookupAttributes
2670      */
2671     public void addLookupAttributes(Entry[] attrSets)
2672                                  throws NoSuchObjectException, RemoteException
2673     {
2674 	readyState.check();
2675         concurrentObj.writeLock();
2676         try {
2677             joinMgr.addAttributes(attrSets, true);
2678             thisServicesAttrs = joinMgr.getAttributes();
2679 	    addLogRecord(new LookupAttrsAddedLogObj(this,attrSets));
2680         } finally {
2681             concurrentObj.writeUnlock();
2682         }
2683     }//end addLookupAttributes
2684 
2685     /** 
2686      * Modifies the current set of attributes associated with the lookup
2687      * discovery service. The resulting set will be used for all future
2688      * registrations with lookup services. The same modifications are 
2689      * also made to the lookup discovery service's attributes on each
2690      * lookup service with which the lookup discovery service is currently
2691      * registered.
2692      *
2693      * @param  attrSetTemplates  array of net.jini.core.entry.Entry containing
2694      *         the templates to use for selecting the attributes (contained
2695      *         within the set of existing attributes) that are to be
2696      *         modified
2697      * @param  attrSets array of net.jini.core.entry.Entry containing the
2698      *         modifications to make to matching sets
2699      * 
2700      * @throws java.rmi.NoSuchObjectException if this method is called during
2701      *         service initialization or shutdown processing
2702      * 
2703      * @throws java.rmi.RemoteException typically, this exception occurs when
2704      *         there is a communication failure between the client and the
2705      *         lookup discovery service. When this exception does occur, the
2706      *         attributes may or may not have been modified successfully.
2707      *
2708      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#modifyLookupAttributes
2709      * @see net.jini.admin.JoinAdmin#modifyLookupAttributes
2710      */
2711     public void modifyLookupAttributes(Entry[] attrSetTemplates,
2712 				       Entry[] attrSets)
2713                                  throws NoSuchObjectException, RemoteException
2714     {
2715 	readyState.check();
2716         concurrentObj.writeLock();
2717         try {
2718             joinMgr.modifyAttributes(attrSetTemplates, attrSets, true);
2719             thisServicesAttrs = joinMgr.getAttributes();
2720             addLogRecord
2721                (new LookupAttrsModifiedLogObj(this,attrSetTemplates,attrSets));
2722         } finally {
2723             concurrentObj.writeUnlock();
2724         }
2725     }//end modifyLookupAttributes
2726 
2727     /** 
2728      * Get the names of the groups whose members are lookup services the
2729      * lookup discovery services wishes to register with (join).
2730      * 
2731      * @return String array containing the names of the groups whose members
2732      *         are lookup services the lookup discovery service wishes to
2733      *         join.
2734      * <p>
2735      *         If the array returned is empty, the lookup discovery service
2736      *         is configured to join no groups. If null is returned, the
2737      *         lookup discovery service is configured to join all groups.
2738      * 
2739      * @throws java.rmi.NoSuchObjectException if this method is called during
2740      *         service initialization or shutdown processing
2741      * 
2742      * @throws java.rmi.RemoteException typically, this exception occurs when
2743      *         there is a communication failure between the client and the
2744      *         lookup discovery service.
2745      *
2746      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#getLookupGroups
2747      * @see net.jini.admin.JoinAdmin#getLookupGroups
2748      */
2749     public String[] getLookupGroups()
2750                                  throws NoSuchObjectException, RemoteException
2751     {
2752 	readyState.check();
2753         concurrentObj.readLock();
2754         try {
2755             return thisServicesGroups;
2756         } finally {
2757             concurrentObj.readUnlock();
2758         }
2759     }//end getLookupGroups
2760 
2761     /** 
2762      * Add new names to the set consisting of the names of groups whose
2763      * members are lookup services the lookup discovery service wishes
2764      * to register with (join). Any lookup services belonging to the
2765      * new groups that the lookup discovery service has not yet registered
2766      * with, will be discovered and joined.
2767      *
2768      * @param  groups String array containing the names of the groups to add
2769      * 
2770      * @throws java.rmi.NoSuchObjectException if this method is called during
2771      *         service initialization or shutdown processing
2772      * 
2773      * @throws java.rmi.RemoteException typically, this exception occurs when
2774      *         there is a communication failure between the client and the
2775      *         lookup discovery service. When this exception does occur, the
2776      *         group names may or may not have been added successfully.
2777      *
2778      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#addLookupGroups
2779      * @see net.jini.admin.JoinAdmin#addLookupGroups
2780      */
2781     public void addLookupGroups(String[] groups)
2782                                  throws NoSuchObjectException, RemoteException
2783     {
2784 	readyState.check();
2785         concurrentObj.writeLock();
2786         try {
2787             try {
2788                 ((DiscoveryGroupManagement)joinMgrLDM).addGroups(groups);
2789             } catch (IOException e) {
2790                 throw new RuntimeException(e.toString());
2791             }
2792             thisServicesGroups
2793                          = ((DiscoveryGroupManagement)joinMgrLDM).getGroups();
2794             addLogRecord(new LookupGroupsChangedLogObj(thisServicesGroups));
2795         } finally {
2796             concurrentObj.writeUnlock();
2797         }
2798     }//end addLookupGroups
2799 
2800     /** 
2801      * Remove a set of group names from lookup discovery service's managed
2802      * set of groups (the set consisting of the names of groups whose
2803      * members are lookup services the lookup discovery service wishes
2804      * to join). Any leases granted to the lookup discovery service by
2805      * lookup services that are not members of the groups whose names 
2806      * remain in the managed set will be cancelled at those lookup services.
2807      *
2808      * @param  groups String array containing the names of the groups to remove
2809      * 
2810      * @throws java.rmi.NoSuchObjectException if this method is called during
2811      *         service initialization or shutdown processing
2812      * 
2813      * @throws java.rmi.RemoteException typically, this exception occurs when
2814      *         there is a communication failure between the client and the
2815      *         lookup discovery service. When this exception does occur, the
2816      *         group names may or may not have been removed successfully.
2817      *
2818      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#removeLookupGroups
2819      * @see net.jini.admin.JoinAdmin#removeLookupGroups
2820      */
2821     public void removeLookupGroups(String[] groups)
2822                                  throws NoSuchObjectException, RemoteException
2823     {
2824 	readyState.check();
2825         concurrentObj.writeLock();
2826         try {
2827             ((DiscoveryGroupManagement)joinMgrLDM).removeGroups(groups);
2828             thisServicesGroups
2829                          = ((DiscoveryGroupManagement)joinMgrLDM).getGroups();
2830             addLogRecord(new LookupGroupsChangedLogObj(thisServicesGroups));
2831         } finally {
2832             concurrentObj.writeUnlock();
2833         }
2834     }//end removeLookupGroups
2835 
2836     /** 
2837      * Replace the lookup discovery service's managed set of groups with a
2838      * new set of group names. Any leases granted to the lookup discovery
2839      * service by lookup services that are not members of the groups whose
2840      * names are in the new managed set will be cancelled at those lookup
2841      * services. Lookup services that are members of groups reflected in
2842      * the new managed set will be discovered and joined.
2843      *
2844      * @param  groups String array containing the names of the new groups
2845      * 
2846      * @throws java.rmi.NoSuchObjectException if this method is called during
2847      *         service initialization or shutdown processing
2848      * 
2849      * @throws java.rmi.RemoteException typically, this exception occurs when
2850      *         there is a communication failure between the client and the
2851      *         lookup discovery service. When this exception does occur, the
2852      *         group names may or may not have been replaced successfully.
2853      *
2854      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#setLookupGroups
2855      * @see net.jini.admin.JoinAdmin#setLookupGroups
2856      */
2857     public void setLookupGroups(String[] groups)
2858                                  throws NoSuchObjectException, RemoteException
2859     {
2860 	readyState.check();
2861         concurrentObj.writeLock();
2862         try {
2863             try {
2864                 ((DiscoveryGroupManagement)joinMgrLDM).setGroups(groups);
2865             } catch (IOException e) {
2866                 throw new RuntimeException(e.toString());
2867             }
2868             thisServicesGroups 
2869                          = ((DiscoveryGroupManagement)joinMgrLDM).getGroups();
2870             addLogRecord(new LookupGroupsChangedLogObj(thisServicesGroups));
2871         } finally {
2872             concurrentObj.writeUnlock();
2873         }
2874     }//end setLookupGroups
2875 
2876     /** 
2877      * Get the lookup discovery service's managed set of locators. The
2878      * managed set of locators is the set of LookupLocator objects
2879      * corresponding to the specific lookup services with which the lookup
2880      * discovery service wishes to register (join).
2881      * 
2882      * @return array of objects of type net.jini.core.discovery.LookupLocator,
2883      *         each of which corresponds to a specific lookup service the
2884      *         lookup discovery service wishes to join.
2885      * 
2886      * @throws java.rmi.NoSuchObjectException if this method is called during
2887      *         service initialization or shutdown processing
2888      * 
2889      * @throws java.rmi.RemoteException typically, this exception occurs when
2890      *         there is a communication failure between the client and the
2891      *         lookup discovery service.
2892      *
2893      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#getLookupLocators
2894      * @see net.jini.admin.JoinAdmin#getLookupLocators
2895      */
2896     public LookupLocator[] getLookupLocators()
2897                                  throws NoSuchObjectException, RemoteException
2898     {
2899 	readyState.check();
2900         concurrentObj.readLock();
2901         try {
2902             return thisServicesLocators;
2903         } finally {
2904             concurrentObj.readUnlock();
2905         }
2906     }//end getLookupLocators
2907 
2908     /** 
2909      * Add a set of LookupLocator objects to the lookup discovery service's
2910      * managed set of locators. The managed set of locators is the set of
2911      * LookupLocator objects corresponding to the specific lookup services
2912      * with which the lookup discovery service wishes to register (join).
2913      * <p>
2914      * Any lookup services corresponding to the new locators that the lookup
2915      * discovery service has not yet joined, will be discovered and joined.
2916      *
2917      * @param  locators array of net.jini.core.discovery.LookupLocator objects to add
2918      *         to the managed set of locators
2919      * 
2920      * @throws java.rmi.NoSuchObjectException if this method is called during
2921      *         service initialization or shutdown processing
2922      * 
2923      * @throws java.rmi.RemoteException typically, this exception occurs when
2924      *         there is a communication failure between the client and the
2925      *         lookup discovery service. When this exception does occur, the
2926      *         new locators may or may not have been added successfully.
2927      *
2928      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#addLookupLocators
2929      * @see net.jini.admin.JoinAdmin#addLookupLocators
2930      */
2931     public void addLookupLocators(LookupLocator[] locators)
2932                                  throws NoSuchObjectException, RemoteException
2933     {
2934 	readyState.check();
2935         /* Prepare outside of sync block because of possible remote call */
2936         prepareNewLocators(locatorToJoinPreparer,locators);
2937         concurrentObj.writeLock();
2938         try {
2939             ((DiscoveryLocatorManagement)joinMgrLDM).addLocators(locators);
2940             thisServicesLocators 
2941                      = ((DiscoveryLocatorManagement)joinMgrLDM).getLocators();
2942             addLogRecord
2943                      (new LookupLocatorsChangedLogObj(thisServicesLocators));
2944         } finally {
2945             concurrentObj.writeUnlock();
2946         }
2947     }//end addLookupLocators
2948 
2949     /** 
2950      * Remove a set of LookupLocator objects from the lookup discovery
2951      * service's managed set of locators. The managed set of locators is the
2952      * set of LookupLocator objects corresponding to the specific lookup
2953      * services with which the lookup discovery service wishes to register
2954      * (join).
2955      * <p>
2956      * Note that any leases granted to the lookup discovery service by
2957      * lookup services that do not correspond to any of the locators
2958      * remaining in the managed set will be cancelled at those lookup
2959      * services.
2960      *
2961      * @param  locators array of net.jini.core.discovery.LookupLocator objects to
2962      *         remove from the managed set of locators
2963      * 
2964      * @throws java.rmi.NoSuchObjectException if this method is called during
2965      *         service initialization or shutdown processing
2966      * 
2967      * @throws java.rmi.RemoteException typically, this exception occurs when
2968      *         there is a communication failure between the client and the
2969      *         lookup discovery service. When this exception does occur, the
2970      *         new locators may or may not have been removed successfully.
2971      *
2972      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#removeLookupLocators
2973      * @see net.jini.admin.JoinAdmin#removeLookupLocators
2974      */
2975     public void removeLookupLocators(LookupLocator[] locators)
2976                                  throws NoSuchObjectException, RemoteException
2977     {
2978 	readyState.check();
2979         /* Prepare outside of sync block because of possible remote call */
2980         prepareNewLocators(locatorToJoinPreparer,locators);
2981         concurrentObj.writeLock();
2982         try {
2983             ((DiscoveryLocatorManagement)joinMgrLDM).removeLocators(locators);
2984             thisServicesLocators 
2985                      = ((DiscoveryLocatorManagement)joinMgrLDM).getLocators();
2986             addLogRecord
2987                      (new LookupLocatorsChangedLogObj(thisServicesLocators));
2988         } finally {
2989             concurrentObj.writeUnlock();
2990         }
2991     }//end removeLookupLocators
2992 
2993     /** 
2994      * Replace the lookup discovery service's managed set of locators with
2995      * a new set of locators. The managed set of locators is the set of
2996      * LookupLocator objects corresponding to the specific lookup services
2997      * with which the lookup discovery service wishes to register (join).
2998      * <p>
2999      * Note that any leases granted to the lookup discovery service by
3000      * lookup services whose corresponding locator is removed from the
3001      * managed set will be cancelled at those lookup services. The lookup
3002      * services corresponding to the new locators in the managed set
3003      * will be discovered and joined.
3004      *
3005      * @param  locators array of net.jini.core.discovery.LookupLocator objects with
3006      *         which to replace the current managed set of locators
3007      *         remove from the managed set of locators
3008      * 
3009      * @throws java.rmi.NoSuchObjectException if this method is called during
3010      *         service initialization or shutdown processing
3011      * 
3012      * @throws java.rmi.RemoteException typically, this exception occurs when
3013      *         there is a communication failure between the client and the
3014      *         lookup discovery service. When this exception does occur, the
3015      *         locators in the managed set may or may not have been replaced
3016      *         successfully.
3017      *
3018      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#setLookupLocators
3019      * @see net.jini.admin.JoinAdmin#setLookupLocators
3020      */
3021     public void setLookupLocators(LookupLocator[] locators)
3022                                  throws NoSuchObjectException, RemoteException
3023     {
3024 	readyState.check();
3025         /* Prepare outside of sync block because of possible remote call */
3026         prepareNewLocators(locatorToJoinPreparer,locators);
3027         concurrentObj.writeLock();
3028         try {
3029             ((DiscoveryLocatorManagement)joinMgrLDM).setLocators(locators);
3030             thisServicesLocators 
3031                     = ((DiscoveryLocatorManagement)joinMgrLDM).getLocators();
3032             addLogRecord
3033                      (new LookupLocatorsChangedLogObj(thisServicesLocators));
3034         } finally {
3035             concurrentObj.writeUnlock();
3036         }
3037     }//end setLookupLocators
3038     /* END org.apache.river.fiddler.proxy.Fiddler --> org.apache.river.fiddler.proxy.FiddlerAdmin
3039      *                                           --> net.jini.admin.JoinAdmin
3040      * -------------------------------------------------------------------- */
3041 
3042     /* -------------------------------------------------------------------- 
3043      * BEGIN org.apache.river.fiddler.proxy.Fiddler
3044      *                            --> org.apache.river.fiddler.proxy.FiddlerAdmin
3045      *                                  --> org.apache.river.admin.DestroyAdmin
3046      */
3047     /**
3048      * Destroy the lookup discovery service, if possible, including its
3049      * persistent storage. This method will typically spawn a separate
3050      * thread to do the actual work asynchronously, so a successful
3051      * return from this method usually does not mean that the service
3052      * has been destroyed.
3053      * 
3054      * @throws java.rmi.NoSuchObjectException if this method is called during
3055      *         service initialization or shutdown processing
3056      * 
3057      * @throws java.rmi.RemoteException typically, this exception occurs when
3058      *         there is a communication failure between the client and the
3059      *         lookup discovery service. When this exception does occur, the
3060      *         lookup discovery service may or may not have been successfully
3061      *         destroyed.
3062      *
3063      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#destroy
3064      * @see org.apache.river.admin.DestroyAdmin#destroy
3065      */
3066     public void destroy() throws NoSuchObjectException, RemoteException {
3067 	readyState.check();
3068         destroyDo();
3069     }//end destroy
3070     /* END org.apache.river.fiddler.proxy.Fiddler
3071      *                          --> org.apache.river.fiddler.proxy.FiddlerAdmin
3072      *                                  --> org.apache.river.admin.DestroyAdmin
3073      * -------------------------------------------------------------------- */
3074 
3075     /* -------------------------------------------------------------------- 
3076      * BEGIN org.apache.river.fiddler.proxy.Fiddler
3077      *                                --> org.apache.river.fiddler.proxy.FiddlerAdmin
3078      */
3079     /**
3080      * Changes the least upper bound applied to all lease durations granted
3081      * by the lookup discovery service.
3082      * <p>
3083      * This method is a mechanism for an entity with the appropriate
3084      * privileges to administratively change the value of the least upper
3085      * bound that will be applied by the Fiddler implementation of the lookup
3086      * discovery service when determining the duration to assign to the lease
3087      * on a requested registration.
3088      *
3089      * @param newBound <code>long</code> value representing the new least
3090      *        upper bound (in milliseconds) on the set of all possible
3091      *        lease durations that may be granted
3092      * 
3093      * @throws java.rmi.NoSuchObjectException if this method is called during
3094      *         service initialization or shutdown processing
3095      * 
3096      * @throws java.rmi.RemoteException typically, this exception occurs when
3097      *         there is a communication failure between the client and the
3098      *         lookup discovery service. When this exception does occur, the
3099      *         bound value may or may not have been changed successfully.
3100      *
3101      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#setLeaseBound
3102      * @see org.apache.river.admin.FiddlerAdmin#setLeaseBound
3103      */
3104     @Override
3105     public void setLeaseBound(long newBound)
3106                                  throws NoSuchObjectException, RemoteException
3107     {
3108 	readyState.check();
3109         concurrentObj.writeLock();
3110         try {
3111 	    if (newBound > leaseMax) {
3112                 throw new IllegalArgumentException("max duration exceeded");
3113             }//endif
3114             leaseBound = newBound;
3115             addLogRecord(new LeaseBoundSetLogObj(newBound));
3116         } finally {
3117             concurrentObj.writeUnlock();
3118         }
3119     }//end setLeaseBound
3120 
3121     /**
3122      * Retrieves the least upper bound applied to all lease durations granted
3123      * by the lookup discovery service.
3124      *
3125      * @return <code>long</code> value representing the current least
3126      *         upper bound (in milliseconds) on the set of all possible
3127      *         lease durations that may be granted
3128      * 
3129      * @throws java.rmi.NoSuchObjectException if this method is called during
3130      *         service initialization or shutdown processing
3131      * 
3132      * @throws java.rmi.RemoteException typically, this exception occurs when
3133      *         there is a communication failure between the client and the
3134      *         lookup discovery service.
3135      *
3136      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#getLeaseBound
3137      * @see org.apache.river.admin.FiddlerAdmin#getLeaseBound
3138      */
3139     public long getLeaseBound() throws NoSuchObjectException, RemoteException {
3140 	readyState.check();
3141         concurrentObj.readLock();
3142         try {
3143             return leaseBound;
3144         } finally {
3145             concurrentObj.readUnlock();
3146         }
3147     }//end getLeaseBound
3148 
3149     /**
3150      * Change the weight factor applied by the lookup discovery service
3151      * to the snapshot size during the test to determine whether or not
3152      * to take a "snapshot" of the system state.
3153      *
3154      * @param weight weight factor for snapshot size
3155      * 
3156      * @throws java.rmi.NoSuchObjectException if this method is called during
3157      *         service initialization or shutdown processing
3158      * 
3159      * @throws java.rmi.RemoteException typically, this exception occurs when
3160      *         there is a communication failure between the client and the
3161      *         lookup discovery service. When this exception does occur, the
3162      *         weight factor may or may not have been changed successfully.
3163      *
3164      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#setPersistenceSnapshotWeight
3165      * @see org.apache.river.admin.FiddlerAdmin#setPersistenceSnapshotWeight
3166      */
3167     public void setPersistenceSnapshotWeight(float weight)
3168                                throws NoSuchObjectException, RemoteException
3169     {
3170 	readyState.check();
3171         concurrentObj.writeLock();
3172         try {
3173             snapshotWt = weight;
3174             addLogRecord(new SnapshotWeightSetLogObj(weight));
3175         } finally {
3176             concurrentObj.writeUnlock();
3177         }
3178     }//end setPersistenceSnapshotWeight
3179 
3180     /**
3181      * Retrieve the weight factor applied by the lookup discovery service
3182      * to the snapshot size during the test to determine whether or not to
3183      * take a "snapshot" of the system state.
3184      * 
3185      * @return float value corresponding to the weight factor for snapshot
3186      *         size
3187      * 
3188      * @throws java.rmi.NoSuchObjectException if this method is called during
3189      *         service initialization or shutdown processing
3190      * 
3191      * @throws java.rmi.RemoteException typically, this exception occurs when
3192      *         there is a communication failure between the client and the
3193      *         lookup discovery service.
3194      *
3195      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy#getPersistenceSnapshotWeight
3196      * @see org.apache.river.admin.FiddlerAdmin#getPersistenceSnapshotWeight
3197      */
3198     public float getPersistenceSnapshotWeight()
3199                                  throws NoSuchObjectException, RemoteException
3200     {
3201 	readyState.check();
3202         concurrentObj.readLock();
3203         try {
3204             return snapshotWt;
3205         } finally {
3206             concurrentObj.readUnlock();
3207         }
3208     }//end getPersistenceSnapshotWeight
3209 
3210     /**
3211      * Change the value of the size threshold of the snapshot; which is
3212      * employed by the lookup discovery service in the test to determine
3213      * whether or not to take a "snapshot" of the system state.
3214      *
3215      * @param threshold size threshold for taking a snapshot
3216      * 
3217      * @throws java.rmi.NoSuchObjectException if this method is called during
3218      *         service initialization or shutdown processing
3219      * 
3220      * @throws java.rmi.RemoteException typically, this exception occurs when
3221      *         there is a communication failure between the client and the
3222      *         lookup discovery service. When this exception does occur, the
3223      *         threshold may or may not have been changed successfully.
3224      *
3225      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy
3226      *                                       #setPersistenceSnapshotThreshold
3227      * @see org.apache.river.admin.FiddlerAdmin#setPersistenceSnapshotThreshold
3228      */
3229     public void setPersistenceSnapshotThreshold(int threshold)
3230                                  throws NoSuchObjectException, RemoteException
3231     {
3232 	readyState.check();
3233         concurrentObj.writeLock();
3234         try {
3235             snapshotThresh = threshold;
3236             addLogRecord(new SnapshotThresholdSetLogObj(threshold));
3237         } finally {
3238             concurrentObj.writeUnlock();
3239         }
3240     }//end setPersistenceSnapshotThreshold
3241 
3242     /**
3243      * Retrieve the value of the size threshold of the snapshot; which is
3244      * employed by the lookup discovery service in the test to determine
3245      * whether or not to take a "snapshot" of the system state.
3246      * 
3247      * @return int value corresponding to the size threshold of the snapshot
3248      * 
3249      * @throws java.rmi.NoSuchObjectException if this method is called during
3250      *         service initialization or shutdown processing
3251      * 
3252      * @throws java.rmi.RemoteException typically, this exception occurs when
3253      *         there is a communication failure between the client and the
3254      *         lookup discovery service.
3255      *
3256      * @see org.apache.river.fiddler.proxy.FiddlerAdminProxy
3257      *                                       #getPersistenceSnapshotThreshold
3258      * @see org.apache.river.admin.FiddlerAdmin#getPersistenceSnapshotThreshold
3259      */
3260     public int getPersistenceSnapshotThreshold()
3261                                  throws NoSuchObjectException, RemoteException
3262     {
3263 	readyState.check();
3264         concurrentObj.readLock();
3265         try {
3266 	    return snapshotThresh;
3267         } finally {
3268             concurrentObj.readUnlock();
3269         }
3270     }//end getPersistenceSnapshotThreshold
3271 
3272     /* END org.apache.river.fiddler.proxy.Fiddler --> org.apache.river.fiddler.proxy.FiddlerAdmin
3273      * -------------------------------------------------------------------- */
3274 
3275     /* -------------------------------------------------------------------- 
3276      * BEGIN org.apache.river.fiddler.proxy.Fiddler
3277      *                          --> net.jini.lookup.ServiceProxyAccessor
3278      */
3279     /**
3280      * Public method that facilitates the use of the mechanism provided by
3281      * {@link org.apache.river.start.ServiceStarter} to create an activatable
3282      * instance of this server.
3283      * 
3284      * @throws java.rmi.NoSuchObjectException if this method is called during
3285      *         service initialization or shutdown processing
3286      * 
3287      * @return the outer (smart) proxy for the server
3288      */
3289     public Object getServiceProxy() throws NoSuchObjectException {
3290 	readyState.check();
3291         concurrentObj.readLock();
3292         try {
3293             return outerProxy;
3294         } finally {
3295             concurrentObj.readUnlock();
3296         }
3297     }//end getServiceProxy
3298 
3299     /*  END org.apache.river.fiddler.proxy.Fiddler 
3300      *                        --> net.jini.lookup.ServiceProxyAccessor   */
3301     /* -------------------------------------------------------------------- */
3302 
3303     /* -------------------------------------------------------------------- 
3304      * BEGIN org.apache.river.fiddler.proxy.Fiddler
3305      */
3306     /**
3307      * Returns the unique identifier generated (or recovered) by the backend
3308      * implementation of the lookup discovery service when an instance of
3309      * that service is constructed. This ID is typically used to determine
3310      * equality between the proxies of any two instances of the lookup
3311      * discovery service.
3312      * 
3313      * @return the unique ID that was generated (or recovered) by the
3314      *         backend implementation of the lookup discovery service
3315      *         at creation time
3316      * 
3317      * @throws java.rmi.NoSuchObjectException if this method is called during
3318      *         service initialization or shutdown processing
3319      * 
3320      * @throws java.rmi.RemoteException typically, this exception occurs when
3321      *         there is a communication failure between the client and the
3322      *         server. When this exception does occur, the registration may
3323      *         or may not have completed successfully.
3324      */
3325     public Uuid getProxyID() throws NoSuchObjectException, RemoteException {
3326 	readyState.check();
3327         concurrentObj.readLock();
3328         try {
3329             return proxyID;
3330         } finally {
3331             concurrentObj.readUnlock();
3332         }
3333     }//end getProxyID
3334 
3335     /**
3336      * Registers with the lookup discovery service. When a client invokes
3337      * this method, it requests that the lookup discovery service perform
3338      * discovery processing on its behalf.
3339      *
3340      * @param groups        String array, none of whose elements may be null,
3341      *                      consisting of zero or more names of groups to
3342      *                      which lookup services to discover belong.
3343      *                      A null value or an empty array
3344      *                      (DiscoveryGroupManagement.ALL_GROUPS or 
3345      *                      DiscoveryGroupManagement.NO_GROUPS) are both
3346      *                      acceptable.
3347      * @param locators      array of zero or more non-null LookupLocator
3348      *                      objects, each corresponding to a specific lookup
3349      *                      service to discover. If either the empty array
3350      *                      or null is passed to this argument, then no
3351      *                      locator discovery will be performed for the
3352      *                      associated registration.
3353      * @param listener      a non-null instance of RemoteEventListener. This 
3354      *                      argument specifies the entity that will receive
3355      *                      events notifying the registration that a lookup
3356      *                      service of interest has been discovered. A 
3357      *                      non-null value must be passed to this argument,
3358      *                      otherwise a NullPointerException will be thrown
3359      *                      and the registration.
3360      * @param handback      null or an instance of MarshalledObject. This
3361      *                      argument specifies an object that will be 
3362      *                      included in the notification event that the
3363      *                      lookup discovery service sends to the registered
3364      *                      listener.
3365      * @param leaseDuration long value representing the amount of time (in
3366      *                      milliseconds) for which the resources of the
3367      *                      lookup discovery service are being requested.
3368      *
3369      * @return an instance of FiddlerRegistration which implements the
3370      *         LookupDiscoveryRegistration interface, and acts as a proxy
3371      *         to the registration-related methods of the backend server
3372      *         of the Fiddler implementation of the lookup discovery service
3373      * 
3374      * @throws java.rmi.NoSuchObjectException if this method is called during
3375      *         service initialization or shutdown processing
3376      * 
3377      * @throws java.rmi.RemoteException typically, this exception occurs when
3378      *         there is a communication failure between the client and the
3379      *         server. When this exception does occur, the registration may
3380      *         or may not have completed successfully.
3381      *
3382      * @throws java.lang.NullPointerException this exception occurs when
3383      *         null is input to the <code>listener</code> parameter, as well
3384      *         as when one or more of the elements of the <code>groups</code>
3385      *         parameter is null.
3386      *
3387      * @throws java.lang.IllegalArgumentException this exception occurs when
3388      *         the value input to the <code>leaseDuration</code> parameter
3389      *         is neither positive, Lease.FOREVER, nor Lease.ANY.
3390      *
3391      * @see net.jini.discovery.LookupDiscoveryService
3392      */
3393     public LookupDiscoveryRegistration register(String[] groups,
3394                                                 LookupLocator[] locators,
3395                                                 RemoteEventListener listener,
3396                                                 MarshalledObject handback,
3397                                                 long leaseDuration)
3398                                                   throws NoSuchObjectException,
3399                                                          RemoteException
3400     {
3401 	readyState.check();
3402         /* The spec says that a null locators array implies no loc discovery */
3403         if( locators == null) {
3404             locators = new LookupLocator[0];
3405         }//endif
3406         if(containsNullElement(groups)) {
3407             throw new NullPointerException(" on call to register() method, at "
3408                                           +"least one null element in groups");
3409         } else if (containsNullElement(locators)) {
3410             throw new NullPointerException(" on call to register() method, at "
3411                                         +"least one null element in locators");
3412         } else if (listener == null) {
3413             throw new NullPointerException(" null listener input to "
3414                                            +"register() method");
3415         }//endif
3416         /* Prepare the locators associated with the requested registration 
3417          * outside of the sync block because of possible remote call.
3418          */
3419         prepareNewLocators(locatorToDiscoverPreparer,locators);
3420         LookupDiscoveryRegistration reg = null;
3421         concurrentObj.writeLock();
3422         try {
3423             /*  Grant the registration request and add the registration to
3424              *  to this service's state.
3425              *
3426              *  The addition of a registration to this service's state
3427              *  typically involves the modification of the managed sets in 
3428              *  the discovery manager, which usually involves starting the 
3429              *  discovery protocol. An IOException can occur when the 
3430              *  discovery protocol fails to start. When such an exception 
3431              *  does occur, register an ERROR status attribute (along with 
3432              *  a Comment attribute describing the nature of the problem) to 
3433              *  all lookup services with which this service is registered. 
3434              *
3435              *  Administrative clients, as well as clients that use this 
3436              *  service should have registered for notification of the 
3437              *  existence of this attribute.
3438              */
3439             reg = registerDo(groups,locators,listener,handback,leaseDuration);
3440         } catch(RemoteException e) {
3441             /* Catch, log, and rethrow so RemoteException is not included
3442              * in the catch block for IOException below.
3443              */
3444             problemLogger.log(Level.INFO,
3445                               "cannot grant registration request", e);
3446             throw e;
3447         } catch(IOException e) {
3448             problemLogger.log(Level.INFO, "cannot grant registration "
3449                               +"request - multicast problem", e);
3450             Entry[] errorAttrs 
3451                   = new Entry[] { new FiddlerStatus(StatusType.ERROR),
3452                                   new Comment("Failure during registration")
3453                                 };
3454             joinMgr.addAttributes(errorAttrs,true);
3455         } finally {
3456             concurrentObj.writeUnlock();
3457         }
3458         return reg;
3459     }//end register
3460 
3461     /**
3462      * This method is the "backend" server counterpart to the method of
3463      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3464      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3465      * returned by this service when a client requests a registration.
3466      * <p>
3467      * This method returns an array consisting of proxies to the lookup
3468      * service(s) that have already been discovered for the registration
3469      * corresponding to the <code>registrationID</code> input parameter.
3470      * Each element of the return set is a marshalled instance of the
3471      * <code>ServiceRegistrar</code> interface.
3472      *
3473      * @param registrationID unique identifier assigned to the registration
3474      *                       from which the set of registrars is being 
3475      *                       retrieved
3476      * 
3477      * @return an array of MarshalledObject objects where each element is
3478      *         is a marshalled instance of ServiceRegistrar.
3479      * 
3480      * @throws java.rmi.NoSuchObjectException if this method is called during
3481      *         service initialization or shutdown processing
3482      * 
3483      * @throws java.rmi.RemoteException typically, this exception occurs when
3484      *         there is a communication failure between the client and the
3485      *         lookup discovery service.
3486      * 
3487      * @throws org.apache.river.proxy.ThrowThis which is a non-remote "wrapper"
3488      *         class used to wrap various remote exceptions (for example,
3489      *         NoSuchObjectException) that this method wishes to throw.
3490      *         When a service is implemented as a smart proxy with a
3491      *         backend server, and a method on the backend which was invoked
3492      *         through the proxy wishes to explicitly throw a particular
3493      *         remote exception, it cannot simply throw that exception if
3494      *         it wishes that exception to be visible to the proxy running
3495      *         on the "client side". This is because when the backend throws
3496      *         any remote exception, the RMI sub-system automatically wraps
3497      *         that exception in a java.rmi.ServerException. Thus, the proxy
3498      *         will only be able to "see" the ServerException (the actual
3499      *         exception that the backend tried to throw is "buried" in the
3500      *         detail field of the ServerException). Thus, in order to allow
3501      *         the proxy access to the actual remote exception this method
3502      *         throws, that exception wraps the desired remote exception in
3503      *         the non-remote exception ThrowThis; which will not be wrapped
3504      *         in a ServerException.
3505      *
3506      *         This method throws a NoSuchObjectException wrapped in a
3507      *         ThrowThis exception whenever the <code>registrationID</code>
3508      *         parameter references an invalid or non-existent registration.
3509      *
3510      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#getRegistrars
3511      * @see net.jini.discovery.LookupDiscoveryRegistration#getRegistrars
3512      */
3513     public MarshalledObject[] getRegistrars(Uuid registrationID)
3514                        throws NoSuchObjectException, RemoteException, ThrowThis
3515     {
3516 	readyState.check();
3517         concurrentObj.readLock();
3518         try {
3519             RegistrationInfo regInfo
3520                    = (RegistrationInfo)(registrationByID.get(registrationID));
3521             if(regInfo == null) {
3522                 throw new ThrowThis
3523                         (new NoSuchObjectException(
3524 		"Invalid registration ID on call to getRegistrars() method"));
3525             }//endif
3526             Collection mVals = (regInfo.discoveredRegsMap).values(); 
3527             return ( (MarshalledObject[])(mVals).toArray
3528                                         (new MarshalledObject[mVals.size()]) );
3529         } finally {
3530             concurrentObj.readUnlock();
3531         }
3532     }//end getRegistrars
3533 
3534     /**
3535      * This method is the "backend" server counterpart to the method of
3536      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3537      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3538      * returned by this service when a client requests a registration.
3539      * <p>
3540      * This method returns an array consisting of the names of the groups
3541      * whose members are lookup services the lookup discovery service will
3542      * attempt to discover for the registration corresponding to the current
3543      * instance of this class. This set of group names is referred to as the
3544      * registration's 'managed set of groups'.
3545      * <p>
3546      * If the registration's managed set of groups is currently empty, then
3547      * the empty array is returned. If the lookup discovery service currently
3548      * has no managed set of groups for the registration through which the
3549      * request is being made, then null will be returned.
3550      *
3551      * @param registrationID unique identifier assigned to the registration
3552      *                       from which the set of groups is being retrieved
3553      * 
3554      * @return a String array containing the elements of the managed set of
3555      *         groups for the registration.
3556      * 
3557      * @throws java.rmi.NoSuchObjectException if this method is called during
3558      *         service initialization or shutdown processing
3559      *
3560      * @throws java.rmi.RemoteException typically, this exception occurs when
3561      *         there is a communication failure between the client and the
3562      *         lookup discovery service.
3563      * 
3564      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
3565      *         org.apache.river.proxy.ThrowThis exception whenever the
3566      *         <code>registrationID</code> parameter references an invalid
3567      *         or non-existent registration. Refer to the description of the
3568      *         <code>getRegistrars</code> method for more information on
3569      *         this exception.
3570      *
3571      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#getGroups
3572      * @see net.jini.discovery.LookupDiscoveryRegistration#getGroups
3573      */
3574     public String[] getGroups(Uuid registrationID) 
3575                        throws NoSuchObjectException, RemoteException, ThrowThis
3576     {
3577 	readyState.check();
3578         concurrentObj.readLock();
3579         try {
3580             RegistrationInfo regInfo
3581                    = (RegistrationInfo)(registrationByID.get(registrationID));
3582             if(regInfo == null) {
3583                 throw new ThrowThis(
3584 		    new NoSuchObjectException(
3585 			"Invalid registration ID on call to getGroups() method"
3586 		    )
3587 		);
3588             }//endif
3589             String[] groups = null;
3590             if(regInfo.groups == null) {
3591                 groups = DiscoveryGroupManagement.ALL_GROUPS;
3592             } else {
3593                 groups = (String[])(regInfo.groups).toArray
3594                                            (new String[regInfo.groups.size()]);
3595             }//endif (regInfo.groups == null)
3596             return groups;
3597         } finally {
3598             concurrentObj.readUnlock();
3599         }
3600     }//end getGroups
3601 
3602     /**
3603      * This method is the "backend" server counterpart to the method of
3604      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3605      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3606      * returned by this service when a client requests a registration.
3607      * <p>
3608      * This method returns an array consisting of the the LookupLocator
3609      * objects corresponding to specific lookup services the lookup discovery
3610      * service will attempt to discover for for the registration
3611      * corresponding to the current instance of this class. This set of
3612      * locators is referred to as the registration's 'managed set of locators'.
3613      * <p>
3614      * If the registration's managed set of locators is currently empty, then
3615      * the empty array is returned. If the lookup discovery service currently
3616      * has no managed set of locators for the registration through which the
3617      * request is being made, then null will be returned.
3618      *
3619      * @param registrationID unique identifier assigned to the registration
3620      *                       from which the set of locators is being retrieved
3621      * 
3622      * @return array consisting of net.jini.core.discovery.LookupLocator
3623      *         objects corresponding to the elements of the managed set of
3624      *         locators for the registration.
3625      * 
3626      * @throws java.rmi.NoSuchObjectException if this method is called during
3627      *         service initialization or shutdown processing
3628      *
3629      * @throws java.rmi.RemoteException typically, this exception occurs when
3630      *         there is a communication failure between the client and the
3631      *         lookup discovery service.
3632      * 
3633      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
3634      *         org.apache.river.proxy.ThrowThis exception whenever the
3635      *         <code>registrationID</code> parameter references an invalid
3636      *         or non-existent registration. Refer to the description of the
3637      *         <code>getRegistrars</code> method for more information on
3638      *         this exception.
3639      *
3640      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#getLocators
3641      * @see net.jini.discovery.LookupDiscoveryRegistration#getLocators
3642      */
3643     public LookupLocator[] getLocators(Uuid registrationID)
3644                        throws NoSuchObjectException, RemoteException, ThrowThis
3645     {
3646 	readyState.check();
3647         concurrentObj.readLock();
3648         try {
3649             RegistrationInfo regInfo
3650                    = (RegistrationInfo)(registrationByID.get(registrationID));
3651             if(regInfo == null) {
3652                 throw new ThrowThis(
3653 		    new NoSuchObjectException(
3654 			"Invalid registration ID on call to getLocators() method"
3655 		    )
3656 		);
3657             }//endif
3658             return (LookupLocator[])(regInfo.locators).toArray
3659                                   (new LookupLocator[regInfo.locators.size()]);
3660         } finally {
3661             concurrentObj.readUnlock();
3662         }
3663     }//end getLocators
3664 
3665     /**
3666      * This method is the "backend" server counterpart to the method of
3667      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3668      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3669      * returned by this service when a client requests a registration.
3670      * <p>
3671      * This method first tests the input set of group names for validity and 
3672      * throws the appropriate exception should any irregularities be found.
3673      * It then adds the input set of group names to the managed set of groups
3674      * associated with the registration.
3675      * 
3676      * @param registrationID unique identifier assigned to the registration
3677      *                       to which the set of groups being augmented
3678      *                       corresponds
3679      * @param groups         a String array, none of whose elements may be
3680      *                       null, consisting of the group names with which to
3681      *                       augment the registration's managed set of groups.
3682      * <p>
3683      *                       If any element of this parameter duplicates any
3684      *                       other element of this parameter, the duplicate
3685      *                       will be ignored. If any element of this parameter
3686      *                       duplicates any element of the registration's
3687      *                       current managed set of groups, the duplicate will
3688      *                       be ignored.
3689      * <p>
3690      *                       If the empty set is input, then the registration's
3691      *                       managed set of groups will not change. If null is
3692      *                       input, this method will throw a
3693      *                       <code>NullPointerException</code>.
3694      * 
3695      * @throws java.lang.IllegalStateException this exception occurs when
3696      *         the <code>addGroups</code> method of the discovery
3697      *         manager is invoked after the <code>terminate</code> method 
3698      *         of that manager is called. When this happens, in addition to
3699      *         propagating this exception, this method also registers an 
3700      *         ERROR status attribute (along with a Comment attribute
3701      *         describing the nature of the problem) with all lookup services
3702      *         with which this service is registered. Administrative clients,
3703      *         as well as clients that use this service should register
3704      *         for notification of the existence of this attribute.
3705      * 
3706      * @throws java.lang.UnsupportedOperationException this exception 
3707      *         occurs when the registration corresponding to the
3708      *         <code>registrationID</code> parameter has no managed set of
3709      *         groups to which to add the elements of the input parameter.
3710      *         That is, the registration's current managed set of groups is
3711      *         null. When a registration's managed set of groups is null,
3712      *         it means that all groups are being discovered for that 
3713      *         registration; thus, requesting that a set of groups be added
3714      *         to the set of all groups makes no sense.
3715      *
3716      * @throws java.lang.NullPointerException this exception occurs when
3717      *         either null is input to the <code>groups</code> parameter, 
3718      *         or one or more of the elements of the <code>groups</code> 
3719      *         parameter is null. If a null <code>groups</code> parameter 
3720      *         is input, the registration is requesting that all groups be 
3721      *         added to its current managed set of groups; which is not 
3722      *         allowed. (Note that if a registration wishes to change its 
3723      *         managed set of groups from a finite set of names to "all 
3724      *         groups", it should invoke setGroups with a null input.)
3725      * 
3726      * @throws java.rmi.NoSuchObjectException if this method is called during
3727      *         service initialization or shutdown processing
3728      *
3729      * @throws java.rmi.RemoteException typically, this exception occurs when
3730      *         there is a communication failure between the client and the
3731      *         lookup discovery service. When this exception does occur, the
3732      *         registration's managed set of groups may or may not have been
3733      *         successfully augmented.
3734      *
3735      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
3736      *         org.apache.river.proxy.ThrowThis exception whenever the
3737      *         <code>registrationID</code> parameter references an invalid
3738      *         or non-existent registration. Refer to the description of the
3739      *         <code>getRegistrars</code> method for more information on
3740      *         this exception.
3741      *
3742      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#addGroups
3743      * @see net.jini.discovery.LookupDiscoveryRegistration#addGroups
3744      */
3745     public void addGroups(Uuid registrationID, String[] groups)
3746                        throws NoSuchObjectException, RemoteException, ThrowThis
3747     {
3748 	readyState.check();
3749         concurrentObj.writeLock();
3750         try {
3751             RegistrationInfo regInfo
3752                    = (RegistrationInfo)(registrationByID.get(registrationID));
3753             if(regInfo == null) {
3754                 throw new ThrowThis(
3755 		    new NoSuchObjectException(
3756 			"Invalid registration ID on call to addGroups() method"
3757 		    )
3758 		);
3759             }//endif
3760             /* Check the input for validity */
3761             if(groups == null) { // asking that all groups be added
3762                 throw new NullPointerException(
3763 " on call to addGroups() method, cannot add 'ALL_GROUPS' (the null set) to a registration's set of groups to discover"
3764 		);
3765             } else if(containsNullElement(groups)) { // null element
3766                 throw new NullPointerException(
3767 " on call to addGroups() method, at least one null element in groups parameter"
3768 		);
3769             } else if (regInfo.groups == null) { // all groups being discovered
3770                 throw new UnsupportedOperationException
3771                                               (" on call to addGroups() "
3772                                                +"method, cannot add a set of"
3773                                                +"groups to a set already "
3774                                                +"configured for 'ALL_GROUPS' "
3775                                                +"(the null set)");
3776             }//endif
3777             /* Augment the current set of groups with the input set */
3778             addGroupsDo(regInfo, groups);
3779             addLogRecord(new GroupsAddedToRegistrationLogObj
3780                                              (regInfo.registrationID,groups));
3781         } finally {
3782             concurrentObj.writeUnlock();
3783         }
3784     }//end addGroups
3785 
3786     /**
3787      * This method is the "backend" server counterpart to the method of
3788      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3789      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3790      * returned by this service when a client requests a registration.
3791      * <p>
3792      * This method first tests the input set of group names for validity and 
3793      * throws the appropriate exception should any irregularities be found.
3794      * It then queues a <code>SetGroupsTask</code> which performs the 
3795      * actual replacement.
3796      * <p>
3797      * 
3798      * @param registrationID unique identifier assigned to the registration
3799      *                       to which the set of groups being replaced
3800      *                       corresponds
3801      * @param groups         a String array, none of whose elements may be 
3802      *                       null, consisting of the group names with which to 
3803      *                       replace the names in this registration's managed 
3804      *                       set of groups.
3805      * <p>
3806      *                       If any element of this parameter duplicates any 
3807      *                       other element of this parameter, the duplicate 
3808      *                       will be ignored.
3809      * <p>
3810      *                       If the empty set is input, then group discovery 
3811      *                       for the registration will cease. If null is input,
3812      *                       the lookup discovery service will attempt to 
3813      *                       discover all as yet undiscovered lookup services 
3814      *                       located within its multicast radius and, upon 
3815      *                       discovery of any such lookup service, will send 
3816      *                       to the registration's listener an event signaling
3817      *                       that discovery.
3818      * 
3819      * @throws java.lang.NullPointerException this exception occurs when one
3820      *         or more of the elements of the groups parameter is null.
3821      * 
3822      * @throws java.rmi.NoSuchObjectException if this method is called during
3823      *         service initialization or shutdown processing
3824      *
3825      * @throws java.rmi.RemoteException typically, this exception occurs when
3826      *         there is a communication failure between the client and the
3827      *         lookup discovery service. When this exception does occur, the
3828      *         registration's managed set of groups may or may not have been
3829      *         successfully replaced.
3830      *
3831      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
3832      *         org.apache.river.proxy.ThrowThis exception whenever the
3833      *         <code>registrationID</code> parameter references an invalid
3834      *         or non-existent registration. Refer to the description of the
3835      *         <code>getRegistrars</code> method for more information on
3836      *         this exception.
3837      *
3838      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#setGroups
3839      * @see net.jini.discovery.LookupDiscoveryRegistration#setGroups
3840      */
3841     public void setGroups(Uuid registrationID, String[] groups)
3842                        throws NoSuchObjectException, RemoteException, ThrowThis
3843     {
3844 	readyState.check();
3845         concurrentObj.writeLock();
3846         try {
3847             RegistrationInfo regInfo
3848                    = (RegistrationInfo)(registrationByID.get(registrationID));
3849             if(regInfo == null) {
3850                 throw new ThrowThis(
3851 		    new NoSuchObjectException(
3852 			"Invalid registration ID on call to setGroups() method"
3853 		    )
3854 		);
3855             }//endif
3856             /* Check the input for validity */
3857             if(containsNullElement(groups)) { // null element
3858                 throw new NullPointerException(
3859 " on call to setGroups() method, at least one null element in groups parameter"
3860 		);
3861             } else if ((groups == null) && (regInfo.groups == null)) {
3862                 /* null input, but already set to ALL groups; do nothing */
3863                 return;
3864             }//endif
3865             /* Replace the current groups with the current groups */
3866             setGroupsDo(regInfo, groups);
3867             addLogRecord(new GroupsSetInRegistrationLogObj
3868                                              (regInfo.registrationID,groups));
3869         } finally {
3870             concurrentObj.writeUnlock();
3871         }
3872     }//end setGroups
3873 
3874     /**
3875      * This method is the "backend" server counterpart to the method of
3876      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3877      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3878      * returned by this service when a client requests a registration.
3879      * <p>
3880      * This method first tests the input set of group names for validity and 
3881      * throws the appropriate exception should any irregularities be found.
3882      * It then queues a <code>RemoveGroupsTask</code> which performs the 
3883      * actual removal.
3884      * 
3885      * @param registrationID unique identifier assigned to the registration
3886      *                       to which the set of groups being removed
3887      *                       corresponds
3888      * @param groups         a String array, none of whose elements may be
3889      *                       null, consisting of the group names to delete 
3890      *                       from the registration's managed set of groups.
3891      * <p>
3892      *                       If any element of this parameter duplicates any 
3893      *                       other element of this parameter, the duplicate 
3894      *                       will be ignored. If any element of this parameter
3895      *                       is not currently contained in the registration's
3896      *                       managed set, no action is taken with respect to
3897      *                       that element.
3898      * <p>
3899      *                       If the empty set is input, the registration's 
3900      *                       managed set of groups will not change. If null is 
3901      *                       input, this method will throw a 
3902      *                       <code>NullPointerException</code>.
3903      * 
3904      * @throws java.lang.UnsupportedOperationException this exception 
3905      *         occurs when the registration corresponding to the
3906      *         <code>registrationID</code> parameter has no managed set 
3907      *         of groups from which to remove elements of the input parameter.
3908      *         That is, the registration's current managed set of groups is
3909      *         null. Thus, requesting that a set of groups be removed from
3910      *         null set makes no sense. 
3911      *
3912      * @throws java.lang.NullPointerException this exception occurs when
3913      *         either null is input to the <code>groups</code> parameter, 
3914      *         or one or more of the elements of the <code>groups</code> 
3915      *         parameter is null. If a null <code>groups</code> parameter 
3916      *         is input, the registration is requesting that all groups be 
3917      *         removed from its current managed set of groups; which is not 
3918      *         allowed. (Note that if a registration wishes to change its 
3919      *         managed set of groups from "all groups" to "no groups", it
3920      *         it should invoke setGroups with a zero length 
3921      *         <code>String</code> input.)
3922      * 
3923      * @throws java.rmi.NoSuchObjectException if this method is called during
3924      *         service initialization or shutdown processing
3925      *
3926      * @throws java.rmi.RemoteException typically, this exception occurs when
3927      *         there is a communication failure between the client and the
3928      *         lookup discovery service. When this exception does occur, the
3929      *         registration's managed set of groups may or may not have been
3930      *         successfully modified.
3931      * 
3932      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
3933      *         org.apache.river.proxy.ThrowThis exception whenever the
3934      *         <code>registrationID</code> parameter references an invalid
3935      *         or non-existent registration. Refer to the description of the
3936      *         <code>getRegistrars</code> method for more information on
3937      *         this exception.
3938      *
3939      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#removeGroups
3940      * @see net.jini.discovery.LookupDiscoveryRegistration#removeGroups
3941      */
3942     public void removeGroups(Uuid registrationID, String[] groups)
3943                        throws NoSuchObjectException, RemoteException, ThrowThis
3944     {
3945 	readyState.check();
3946         concurrentObj.writeLock();
3947         try {
3948             RegistrationInfo regInfo
3949                    = (RegistrationInfo)(registrationByID.get(registrationID));
3950             if(regInfo == null) {
3951                 throw new ThrowThis(
3952 		    new NoSuchObjectException(
3953 		    "Invalid registration ID on call to removeGroups() method"
3954 		    )
3955 		);
3956             }//endif
3957             /* Check the input for validity */
3958             if(groups == null) { // asking that all groups be removed
3959                 throw new NullPointerException(
3960 " on call to removeGroups() method, cannot remove 'ALL_GROUPS' (the null set) from a registration's set of groups to discover"
3961 		);
3962             } else if(containsNullElement(groups)) { // null element
3963                 throw new NullPointerException(
3964 " on call to removeGroups() method, at least one null element in groups parameter"
3965 		);
3966             } else if (regInfo.groups == null) { // all groups being discovered
3967                 throw new UnsupportedOperationException(
3968 " on call to removeGroups() method, cannot remove a set of groups from a set already configured for 'ALL_GROUPS' (the null set)"
3969 		);
3970             }//endif
3971             /* Remove the requested groups */
3972             removeGroupsDo(regInfo, groups);
3973             logInfoGroups("\nAfter Group Removal --");
3974             addLogRecord(new GroupsRemovedFromRegistrationLogObj
3975                                              (regInfo.registrationID,groups));
3976         } finally {
3977             concurrentObj.writeUnlock();
3978         }
3979     }//end removeGroups
3980 
3981     /**
3982      * This method is the "backend" server counterpart to the method of
3983      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
3984      * proxy (an instance of <code>FiddlerRegistration</code>) that is
3985      * returned by this service when a client requests a registration.
3986      * <p>
3987      * This method first tests the input set of group names for validity and 
3988      * throws the appropriate exception should any irregularities be found.
3989      * It then adds the input set of LookupLocator objects to the managed set
3990      * of locators associated with the registration.
3991      * 
3992      * @param registrationID unique identifier assigned to the registration
3993      *                       to which the set of locators being augmented
3994      *                       corresponds
3995      * @param locators       an array, none of whose elements may be null, 
3996      *                       consisting of the LookupLocator objects with 
3997      *                       which to augment the registration's managed set 
3998      *                       of locators.
3999      * <p>
4000      *                       If any element of this parameter duplicates any 
4001      *                       other element of this parameter, the duplicate 
4002      *                       will be ignored. If any element of this parameter 
4003      *                       duplicates any element of the registration's 
4004      *                       managed set of locators, the duplicate will be 
4005      *                       ignored.
4006      * <p>
4007      *                       If the empty set is input, then the registration's
4008      *                       managed set of locators will not change. If null
4009      *                       is input, this method will throw a
4010      *                       <code>NullPointerException</code>.
4011      * 
4012      * @throws java.lang.IllegalStateException this exception occurs when
4013      *         the <code>addLocators</code> method of the discovery
4014      *         manager is invoked after the <code>terminate</code> method 
4015      *         of that manager is called. When this happens, in addition to
4016      *         propagating this exception, this method also registers an 
4017      *         ERROR status attribute (along with a Comment attribute
4018      *         describing the nature of the problem) with all lookup services
4019      *         with which this service is registered. Administrative clients,
4020      *         as well as clients that use this service should register
4021      *         for notification of the existence of this attribute.
4022      * 
4023      * @throws java.lang.NullPointerException this exception occurs when
4024      *         either null is input to the <code>locators</code> parameter, 
4025      *         or one or more of the elements of the <code>locators</code> 
4026      *         parameter is null.
4027      * 
4028      * @throws java.rmi.NoSuchObjectException if this method is called during
4029      *         service initialization or shutdown processing
4030      *
4031      * @throws java.rmi.RemoteException typically, this exception occurs when
4032      *         there is a communication failure between the client and the
4033      *         lookup discovery service. When this exception does occur, the
4034      *         registration's managed set of locators may or may not have
4035      *         been successfully augmented.
4036      * 
4037      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
4038      *         org.apache.river.proxy.ThrowThis exception whenever the
4039      *         <code>registrationID</code> parameter references an invalid
4040      *         or non-existent registration. Refer to the description of the
4041      *         <code>getRegistrars</code> method for more information on
4042      *         this exception.
4043      *
4044      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#addLocators
4045      * @see net.jini.discovery.LookupDiscoveryRegistration#addLocators
4046      */
4047     public void addLocators(Uuid registrationID, LookupLocator[] locators)
4048                        throws NoSuchObjectException, RemoteException, ThrowThis
4049 
4050     {
4051 	readyState.check();
4052         /* Prepare outside of sync block because of possible remote call */
4053         prepareNewLocators(locatorToDiscoverPreparer,locators);
4054         concurrentObj.writeLock();
4055         try {
4056             RegistrationInfo regInfo
4057                    = (RegistrationInfo)(registrationByID.get(registrationID));
4058             if(regInfo == null) {
4059                 throw new ThrowThis(
4060 		    new NoSuchObjectException(
4061 			"Invalid registration ID on call to addLocators() method"
4062 		    )
4063 		);
4064             }//endif
4065             /* Check the input for validity */
4066             if(locators == null) {
4067                 throw new NullPointerException(
4068 " on call to addLocators() method, cannot add null to a registration's set of locators to discover"
4069 		);
4070             } else if(containsNullElement(locators)) { // null element
4071                 throw new NullPointerException(
4072 " on call to addLocators() method, at least one null element in locators parameter"
4073 		);
4074             }//endif(locators == null)
4075             /* Augment the current set of locators with the input set */
4076             addLocatorsDo(regInfo, locators);
4077             addLogRecord(new LocsAddedToRegistrationLogObj
4078                                            (regInfo.registrationID,locators));
4079         } finally {
4080             concurrentObj.writeUnlock();
4081         }
4082     }//end addLocators
4083 
4084     /**
4085      * This method is the "backend" server counterpart to the method of
4086      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
4087      * proxy (an instance of <code>FiddlerRegistration</code>) that is
4088      * returned by this service when a client requests a registration.
4089      * <p>
4090      * This method first tests the input set of locators for validity and 
4091      * throws the appropriate exception should any irregularities be found.
4092      * It then queues a <code>SetLocatorsTask</code> which performs the 
4093      * actual replacement.
4094      * 
4095      * @param registrationID unique identifier assigned to the registration
4096      *                       to which the set of locators being replaced
4097      *                       corresponds
4098      * @param locators       an array, none of whose elements may be null,
4099      *                       consisting of the LookupLocator objects with 
4100      *                       which to replace the locators in the 
4101      *                       registration's managed set of locators.
4102      * <p>
4103      *                       If any element of this parameter duplicates any 
4104      *                       other element of this parameter, the duplicate 
4105      *                       will be ignored.
4106      * <p>
4107      *                       If the empty array is input, then locator 
4108      *                       discovery for the registration will cease. If 
4109      *                       null is input, this method will throw a 
4110      *                       <code>NullPointerException</code>.
4111      * 
4112      * @throws java.lang.NullPointerException this exception occurs when
4113      *         either null is input to the <code>locators</code> parameter, 
4114      *         or one or more of the elements of the <code>locators</code> 
4115      *         parameter is null.
4116      * 
4117      * @throws java.rmi.NoSuchObjectException if this method is called during
4118      *         service initialization or shutdown processing
4119      *
4120      * @throws java.rmi.RemoteException typically, this exception occurs when
4121      *         there is a communication failure between the client and the
4122      *         lookup discovery service. When this exception does occur, the
4123      *         registration's managed set of locators may or may not have
4124      *         been successfully replaced.
4125      * 
4126      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
4127      *         org.apache.river.proxy.ThrowThis exception whenever the
4128      *         <code>registrationID</code> parameter references an invalid
4129      *         or non-existent registration. Refer to the description of the
4130      *         <code>getRegistrars</code> method for more information on
4131      *         this exception.
4132      *
4133      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#setLocators
4134      * @see net.jini.discovery.LookupDiscoveryRegistration#setLocators
4135      */
4136     public void setLocators(Uuid registrationID, LookupLocator[] locators)
4137                        throws NoSuchObjectException, RemoteException, ThrowThis
4138     {
4139 	readyState.check();
4140         /* Prepare outside of sync block because of possible remote call */
4141         prepareNewLocators(locatorToDiscoverPreparer,locators);
4142         concurrentObj.writeLock();
4143         try {
4144             RegistrationInfo regInfo
4145                    = (RegistrationInfo)(registrationByID.get(registrationID));
4146             if(regInfo == null) {
4147                 throw new ThrowThis(
4148 		    new NoSuchObjectException(
4149 			"Invalid registration ID on call to setLocators() method"
4150 		    )
4151 		);
4152             }//endif
4153             /* Check the input for validity */
4154             if(locators == null) {
4155                 throw new NullPointerException(
4156 " on call to setLocators() method, cannot replace a registration's current set of locators with null"
4157 		);
4158             } else if(containsNullElement(locators)) { // null element
4159                 throw new NullPointerException(
4160 " on call to setLocators() method, at least one null element in locators parameter"
4161 		);
4162             }//endif(locators == null)
4163             setLocatorsDo(regInfo, locators);
4164             addLogRecord(new LocsSetInRegistrationLogObj
4165                                            (regInfo.registrationID,locators));
4166         } finally {
4167             concurrentObj.writeUnlock();
4168         }
4169     }//end setLocators
4170 
4171     /**
4172      * This method is the "backend" server counterpart to the method of
4173      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
4174      * proxy (an instance of <code>FiddlerRegistration</code>) that is
4175      * returned by this service when a client requests a registration.
4176      * <p>
4177      * This method first tests the input set of locators for validity and 
4178      * throws the appropriate exception should any irregularities be found.
4179      * It then queues a <code>RemoveLocatorsTask</code> which performs the 
4180      * actual removal.
4181      * 
4182      * @param registrationID unique identifier assigned to the registration
4183      *                       to which the set of locators being removed
4184      *                       corresponds
4185      * @param locators       an array, none of whose elements may be null,
4186      *                       consisting of the LookupLocator objects to remove
4187      *                       from the registration's managed set of locators.
4188      * <p>
4189      *                       If any element of this parameter duplicates any
4190      *                       other element of this parameter, the duplicate
4191      *                       will be ignored.
4192      * <p>
4193      *                       If the empty set is input, the managed set of
4194      *                       locators will not change. If null is input,
4195      *                       this method will throw a 
4196      *                       <code>NullPointerException</code>.
4197      * 
4198      * @throws java.lang.NullPointerException this exception occurs when
4199      *         either null is input to the <code>locators</code> parameter, 
4200      *         or one or more of the elements of the <code>locators</code> 
4201      *         parameter is null.
4202      * 
4203      * @throws java.rmi.NoSuchObjectException if this method is called during
4204      *         service initialization or shutdown processing
4205      *
4206      * @throws java.rmi.RemoteException typically, this exception occurs when
4207      *         there is a communication failure between the client and the
4208      *         lookup discovery service. When this exception does occur, the
4209      *         registration's managed set of locators may or may not have
4210      *         been successfully modified.
4211      * 
4212      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
4213      *         org.apache.river.proxy.ThrowThis exception whenever the
4214      *         <code>registrationID</code> parameter references an invalid
4215      *         or non-existent registration. Refer to the description of the
4216      *         <code>getRegistrars</code> method for more information on
4217      *         this exception.
4218      *
4219      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#removeLocators
4220      * @see net.jini.discovery.LookupDiscoveryRegistration#removeLocators
4221      */
4222     public void removeLocators(Uuid registrationID, LookupLocator[] locators)
4223                        throws NoSuchObjectException, RemoteException, ThrowThis
4224     {
4225 	readyState.check();
4226         /* Prepare outside of sync block because of possible remote call */
4227         prepareNewLocators(locatorToDiscoverPreparer,locators);
4228         concurrentObj.writeLock();
4229         try {
4230             RegistrationInfo regInfo
4231                    = (RegistrationInfo)(registrationByID.get(registrationID));
4232             if(regInfo == null) {
4233                 throw new ThrowThis(
4234 		    new NoSuchObjectException(
4235 		    "Invalid registration ID on call to removeLocators() method"
4236 		    )
4237 		);
4238             }//endif
4239             /* Check the input for validity */
4240             if(locators == null) {
4241                 throw new NullPointerException(
4242 " on call to removeLocators() method, cannot remove null from a registration's set of locators to discover"
4243 		);
4244             } else if(containsNullElement(locators)) { // null element
4245                 throw new NullPointerException(
4246 " on call to removeLocators() method, at least one null element in locators parameter"
4247 		);
4248             }//endif(locators == null)
4249             /* Remove the requested set of locators from the current set */
4250             removeLocatorsDo(regInfo, locators);
4251             addLogRecord(new LocsRemovedFromRegistrationLogObj
4252                                            (regInfo.registrationID,locators));
4253         } finally {
4254             concurrentObj.writeUnlock();
4255         }
4256     }//end removeLocators
4257 
4258     /**
4259      * This method is the "backend" server counterpart to the method of
4260      * the same name provided by the <code>LookupDiscoveryRegistration</code> 
4261      * proxy (an instance of <code>FiddlerRegistration</code>) that is
4262      * returned by this service when a client requests a registration.
4263      * <p>
4264      * This method informs the lookup discovery service of the existence of
4265      * an unavailable lookup service and requests that the lookup discovery
4266      * service discard the unavailable lookup service and make it eligible
4267      * to be re-discovered.
4268      * 
4269      * @param registrationID unique identifier assigned to the registration
4270      *                       making the current discard request
4271      * @param registrar      a reference to the lookup service that the lookup
4272      *                       discovery service is being asked to discard.
4273      * <p>
4274      *                       If this parameter equals none of the lookup
4275      *                       services contained in the managed set of lookup
4276      *                       services for this registration, no action will
4277      *                       be taken.
4278      * 
4279      * @throws java.lang.NullPointerException this exception occurs when
4280      *         null is input to the registrar parameter.
4281      * 
4282      * @throws java.rmi.NoSuchObjectException if this method is called during
4283      *         service initialization or shutdown processing
4284      *
4285      * @throws java.rmi.RemoteException typically, this exception occurs when
4286      *         there is a communication failure between the client and the
4287      *         lookup discovery service. When this exception does occur, 
4288      *         the lookup service may or may not have been successfully
4289      *         discarded.
4290      * 
4291      * @throws java.rmi.NoSuchObjectException wrapped in an instance of
4292      *         org.apache.river.proxy.ThrowThis exception whenever the
4293      *         <code>registrationID</code> parameter references an invalid
4294      *         or non-existent registration. Refer to the description of the
4295      *         <code>getRegistrars</code> method for more information on
4296      *         this exception.
4297      *
4298      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#discard
4299      * @see net.jini.discovery.LookupDiscoveryRegistration#discard
4300      */
4301     public void discard(Uuid registrationID, ServiceRegistrar registrar)
4302                        throws NoSuchObjectException, RemoteException, ThrowThis
4303     {
4304 	readyState.check();
4305         concurrentObj.writeLock();
4306         try {
4307             logInfoDiscard("\ndiscard: ",registrationID);
4308             RegistrationInfo regInfo
4309                    = (RegistrationInfo)(registrationByID.get(registrationID));
4310             if(regInfo == null) {
4311                 throw new ThrowThis(
4312 		    new NoSuchObjectException(
4313 			"Invalid registration ID on call to discard() method"
4314 		    )
4315 		);
4316             }//endif
4317             if(registrar == null) {
4318                 throw new NullPointerException(
4319 		" on call to discard() method, null input for registrar to discard"
4320 		);
4321             }//endif
4322             if( regIsElementOfRegSet(registrar,discoveryMgr.getRegistrars()) ){
4323                 /* This must be the first discard request for this registrar 
4324                  * because the discovery manager has not discarded it yet.
4325                  * When the discovery manager discards the registrar, a
4326                  * local discarded event is sent to the listener.discarded
4327                  * method which queues a DiscardedEventTask which will 
4328                  * remove the discarded registrar from the registration's
4329                  * set of discovered registrars and then send a remote
4330                  * discarded event to the registration's listener.
4331                  */
4332                 logInfoDiscard("  Registrar IS an element of Mgr's "
4333                                +"discovered registrars ... discarding "
4334                                +"from discovery manager");
4335                 regInfo.discardFlag = true; //discard due to external request
4336                 discoveryMgr.discard(registrar);
4337             } else {
4338                 logInfoDiscard("  Registrar NOT an element of Mgr's "
4339                                +"discovered registrars ... queuing "
4340                                +"new DiscardRegistrarTask");
4341                 /* For all subsequent discard requests, remove the registrar
4342                  * from the registration's set of discovered registrars and
4343                  * send a remote discarded event, but don't ask the discovery
4344                  * manager to discard the registrar.
4345                  */
4346                 executorService.execute(new DiscardRegistrarTask(regInfo,registrar));
4347             }//endif
4348         } finally {
4349             concurrentObj.writeUnlock();
4350         }
4351     }//end discard
4352 
4353     /**
4354      * This method is the "backend" server counterpart to the 
4355      * <code>renew</code> method specified by the <code>Lease</code> interface,
4356      * implemented in the <code>org.apache.river.lease.AbstractLease</code> class,
4357      * and invoked by way of the <code>doRenew</code> method of the
4358      * <code>FiddlerLease</code> class; an instance of which is
4359      * returned by the <code>getLease</code> method of the
4360      * <code>LookupDiscoveryRegistration</code> proxy (an instance of
4361      * <code>FiddlerRegistration</code>) that is returned by this service
4362      * when a client requests a registration.
4363      * <p>
4364      * This method renews the lease corresponding to the given 
4365      * <code>registrationID</code> and <code>leaseID</code> parameters,
4366      * granting a new duration that is less than or equal to the requested
4367      * duration value contained in the <code>duration</code> parameter.
4368      *
4369      * @param registrationID unique identifier assigned to the registration
4370      *                       to which the lease being renewed corresponds
4371      * @param leaseID        identifier assigned by the lease grantor to the
4372      *                       lease being renewed
4373      * @param duration       the requested duration for the lease being renewed
4374      *
4375      * @return <code>long</code> value representing the actual duration that
4376      *         was granted for the renewed lease. Note that the actual
4377      *         duration granted and returned by this method may be less than
4378      *         the duration requested.
4379      *
4380      * @throws net.jini.core.lease.UnknownLeaseException this exception occurs
4381      *         when the lease being renewed does not exist, or is unknown
4382      *         to the lease grantor; typically because the lease has expired.
4383      * 
4384      * @throws java.rmi.NoSuchObjectException if this method is called during
4385      *         service initialization or shutdown processing
4386      *         
4387      * @throws java.rmi.RemoteException typically, this exception occurs when
4388      *         there is a communication failure between the client and the
4389      *         server. When this exception does occur, the lease may or may
4390      *         not have been renewed successfully.
4391      *
4392      * @see net.jini.core.lease.Lease#renew
4393      * @see org.apache.river.lease.AbstractLease#renew
4394      * @see org.apache.river.lease.AbstractLease#doRenew
4395      * @see org.apache.river.fiddler.proxy.FiddlerLease#doRenew
4396      */
4397     public long renewLease(Uuid registrationID,
4398                            Uuid leaseID,
4399                            long duration)
4400            throws UnknownLeaseException, NoSuchObjectException, RemoteException
4401     {
4402 	readyState.check();
4403         concurrentObj.priorityWriteLock();
4404         try {
4405             RegistrationInfo regInfo
4406                    = (RegistrationInfo)(registrationByID.get(registrationID));
4407             if(regInfo == null) {
4408                 throw new UnknownLeaseException
4409                                  ("\n    Invalid registration ID "+ registrationID + " on call to "
4410                                   +"renewLease() method"
4411                                   +"\n    The lease may have expired or been "
4412                                   +"cancelled");
4413             }//endif
4414             /* Renew the lease */
4415             long newDuration = 0;
4416             newDuration = renewLeaseDo(regInfo, leaseID, duration);
4417             logInfoLease("Renewed lease: ",registrationID,leaseID);
4418             /* The call to addLogRecord is in renewLeaseDo */
4419             return newDuration;
4420         } finally {
4421             concurrentObj.writeUnlock();
4422         }
4423     }//end renewLease
4424 
4425     /**
4426      * This methods renews all leases from a <code>LeaseMap</code>, 
4427      * where each element of the map is a lease on a registration with
4428      * ID corresponding to an element of the <code>registrationIDs</code> 
4429      * parameter.
4430      * <p>
4431      * This method is the "backend" server counterpart to the 
4432      * <code>renewAll</code> method specified by the
4433      * <code>LeaseMap</code> interface, implemented in the 
4434      * <code>org.apache.river.lease.AbstractLeaseMap</code> class, and 
4435      * invoked by way of the <code>renewAll</code> method of the
4436      * <code>FiddlerLease</code> class; an instance of which is
4437      * returned by the <code>getLease</code> method of the
4438      * <code>LookupDiscoveryRegistration</code> proxy (an instance of
4439      * <code>FiddlerRegistration</code>) that is returned by this service
4440      * when a client requests a registration.
4441      *
4442      * @param registrationIDs array containing the unique identifiers assigned
4443      *                        to the each registration to which each lease 
4444      *                        to be renewed corresponds
4445      * @param leaseIDs        array containing the identifiers assigned by the
4446      *                        lease grantor to each lease being renewed
4447      * @param durations       array containing the requested durations for 
4448      *                        each lease being renewed
4449      * 
4450      * @return an instance of FiddlerRenewResults containing data corresponding
4451      *         to the results (granted durations or exceptions) of each
4452      *         renewal attempt
4453      * 
4454      * @throws java.rmi.NoSuchObjectException if this method is called during
4455      *         service initialization or shutdown processing
4456      * 
4457      * @throws java.rmi.RemoteException typically, this exception occurs when
4458      *         there is a communication failure between the client and the
4459      *         server. When this exception does occur, this method may or
4460      *         may not have complete its processing successfully.
4461      *
4462      * @see net.jini.core.lease.LeaseMap#renewAll
4463      */
4464     public FiddlerRenewResults renewLeases(Uuid[] registrationIDs,
4465                                            Uuid[] leaseIDs,
4466                                            long[] durations)
4467                                  throws NoSuchObjectException, RemoteException
4468     {
4469 	readyState.check();
4470         concurrentObj.priorityWriteLock();
4471         try {
4472             return renewLeasesDo(registrationIDs, leaseIDs, durations);
4473             /* The call to addLogRecord is in renewLeasesDo */
4474         } finally {
4475             concurrentObj.writeUnlock();
4476         }
4477     }//end renewLeases
4478 
4479     /**
4480      * This method is the "backend" server counterpart to the 
4481      * <code>cancel</code> method specified by the <code>Lease</code>
4482      * interface and implemented in the <code>FiddlerLease</code> class; an
4483      * instance of which is returned by the <code>getLease</code> method
4484      * of the <code>LookupDiscoveryRegistration</code> proxy (an instance of
4485      * <code>FiddlerRegistration</code>) that is returned by this service
4486      * when a client requests a registration.
4487      * <p>
4488      * This method cancels the lease corresponding to the given 
4489      * <code>registrationID</code> and <code>leaseID</code> parameters.
4490      *
4491      * The cancellation of a lease typically involves the modification of the
4492      * managed sets in the discovery manager, which usually involves starting
4493      * the discovery protocol.  An IOException can occur when the discovery
4494      * protocol fails to start. When such an exception does occur, this 
4495      * method registers an ERROR status attribute (along with a Comment
4496      * attribute describing the nature of the problem) to all lookup services
4497      * with which this service is registered. 
4498      *
4499      * Administrative clients, as well as clients that use this service should
4500      * have registered for notification of the existence of this attribute.
4501      *
4502      * @param registrationID unique identifier assigned to the registration
4503      *                       to which the lease being cancelled corresponds
4504      * @param leaseID        identifier assigned by the lease grantor to the
4505      *                       lease that is to be cancelled
4506      *
4507      * @throws net.jini.core.lease.UnknownLeaseException this exception occurs
4508      *         when the lease being cancelled is unknown to the lease grantor.
4509      * 
4510      * @throws java.rmi.NoSuchObjectException if this method is called during
4511      *         service initialization or shutdown processing
4512      * 
4513      * @throws java.rmi.RemoteException typically, this exception occurs when
4514      *         there is a communication failure between the client and the
4515      *         server. When this exception does occur, the lease may or may
4516      *         not have been cancelled successfully.
4517      *
4518      * @see net.jini.core.lease.Lease#cancel
4519      */
4520     public void cancelLease(Uuid registrationID,
4521                             Uuid leaseID)
4522            throws UnknownLeaseException, NoSuchObjectException, RemoteException
4523     {
4524 	readyState.check();
4525         concurrentObj.writeLock();
4526         try {
4527             RegistrationInfo regInfo
4528                    = (RegistrationInfo)(registrationByID.get(registrationID));
4529             if(regInfo == null) {
4530                 throw new UnknownLeaseException
4531                                  ("\n    Invalid registration ID on call to "
4532                                   +"cancelLease() method"
4533                                   +"\n    The lease may have expired or been "
4534                                   +"cancelled");
4535             }//endif
4536             /* Cancel the lease */
4537             try {
4538                 cancelLeaseDo(regInfo, leaseID);
4539                 logInfoLease("Cancelled lease: ",registrationID,leaseID);
4540 	    } catch(IOException e) {
4541                 String eStr = "Failure while cancelling the lease on "
4542                               +"registration with ID = "+registrationID;
4543                 if( problemLogger.isLoggable(Level.INFO) ) {
4544                     problemLogger.log(Level.INFO, eStr, e);
4545                 }//endif
4546                 Entry[] errorAttrs = 
4547                            new Entry[] { new FiddlerStatus(StatusType.ERROR),
4548                                          new Comment(eStr)
4549                                        };
4550                 joinMgr.addAttributes(errorAttrs,true);
4551 	    }
4552             addLogRecord(new LeaseCancelledLogObj
4553                                           (regInfo.registrationID, leaseID));
4554         } finally {
4555             concurrentObj.writeUnlock();
4556         }
4557     }//end cancelLease
4558 
4559     /**
4560      * Cancels all leases from a <code>LeaseMap</code>.
4561      * <p>
4562      * For each element in the <code>registrationIDs</code> parameter,
4563      * this method will cancel the corresponding element in the
4564      * <code>leaseIDs</code> parameter.
4565      *
4566      * @param registrationIDs array containing the unique identifiers assigned
4567      *                        to the each registration to which each lease 
4568      *                        to be cancelled corresponds
4569      * @param leaseIDs        array containing the identifiers assigned by the
4570      *                        lease grantor to each lease being cancelled
4571      * 
4572      * @throws java.rmi.NoSuchObjectException if this method is called during
4573      *         service initialization or shutdown processing
4574      * 
4575      * @throws java.rmi.RemoteException typically, this exception occurs when
4576      *         there is a communication failure between the client and the
4577      *         server. When this exception does occur, this method may or
4578      *         may not have complete its processing successfully.
4579      * 
4580      * @return array consisting of any exceptions that may have occurred 
4581      *         while attempting to cancel one of the leases in the map.
4582      *
4583      * @see net.jini.core.lease.LeaseMap#cancelAll
4584      */
4585     public Exception[] cancelLeases(Uuid[] registrationIDs,
4586                                     Uuid[] leaseIDs)
4587                                   throws NoSuchObjectException, RemoteException
4588     {
4589 	readyState.check();
4590         concurrentObj.writeLock();
4591         try {
4592             /* don't bother to weed out unknown leases, so log first */
4593             addLogRecord( new LeasesCancelledLogObj(registrationIDs,leaseIDs));
4594             return cancelLeasesDo(registrationIDs, leaseIDs);
4595         } finally {
4596             concurrentObj.writeUnlock();
4597         }
4598     }//end cancelLeases
4599     /* END org.apache.river.fiddler.proxy.Fiddler ----------------------------------- */
4600     /* ************************* END Public Methods *********************** */
4601 
4602     /* **************** BEGIN Private Static Utility Methods ************** */
4603     /** Return a new array containing the elements of the input array parameter
4604      *  with the input element parameter appended to the end of the array.
4605      */
4606     private static Object[] appendArray(Object[] array, Object elt) {
4607 	int len = array.length;
4608 	Object[] newArray =
4609 	    (Object[])Array.newInstance(array.getClass().getComponentType(),
4610 					len + 1);
4611 	System.arraycopy(array, 0, newArray, 0, len);
4612 	newArray[len] = elt;
4613 	return newArray;
4614     }//end appendArray
4615 
4616     /** Bounds the duration by the value of the <code>bound</code> parameter,
4617      *  and checks for negative value.
4618      */
4619     private static long applyBoundToLeaseDuration(long leaseDuration,
4620                                                   long bound)
4621     {
4622         long newLeaseDuration = leaseDuration;
4623         if ( (leaseDuration == Lease.ANY) || (leaseDuration > bound) ) {
4624             newLeaseDuration = bound;
4625         } else if (leaseDuration < 0) {
4626             throw new IllegalArgumentException("negative lease duration");
4627         }//endif
4628         return newLeaseDuration;
4629     }//end applyBoundToLeaseDuration
4630 
4631     /** Determines if any element in the input array is null.
4632      *  @param arr Object array to examine for null elements
4633      *  @return true if any element is found to be null, false otherwise.
4634      *          Note that this means that if the array itself is null
4635      *          or has a non-positive length, false is returned (because
4636      *          the input parameter still does not contain a null element).
4637      */
4638     private static boolean containsNullElement(Object[] arr) {
4639         if( (arr == null) || (arr.length == 0) ) return false;
4640         for(int i=0;i<arr.length;i++) {
4641             if(arr[i] == null) return true;
4642         }//end loop
4643         return false;
4644     }//end containsNullElement
4645 
4646     /** This method determines if a particular registration (regInfo) is
4647      *  interested in discovering, through group discovery, the registrar
4648      *  belonging to a given set of member groups.
4649      *
4650      *  @param regGroups     array of the member groups from the registrar
4651      *                       (cannot be null)
4652      *  @param desiredGroups groups the registration wishes to discover
4653      *                       (can be null = ALL_GROUPS)
4654      * 
4655      *  @return <code>true</code> if at least one of the registrar's member
4656      *          groups is contained in the registration's set of groups to
4657      *          discover; <code>false</code> otherwise
4658      */
4659     private static boolean interested(String[] regGroups, Set desiredGroups) {
4660         if(desiredGroups == null) return true;
4661         if(desiredGroups.size() == 0) return false;
4662 	for(int i=0;i<regGroups.length;i++) {
4663             if( desiredGroups.contains(regGroups[i]) ) return true;
4664         }//end loop
4665 	return false;
4666     }//end interested
4667 
4668     /** This method determines if a particular registration (regInfo) is
4669      *  interested in discovering, through either locator discovery
4670      *  or group discovery, the registrar having a given locator and 
4671      *  belonging to a given set of member groups.
4672      *
4673      * @param regLoc          locator of the registrar (cannot be null)
4674      * @param regGroups       array of the member groups from the registrar
4675      *                        (cannot be null)
4676      * @param desiredLocators locators the registration wishes to discover
4677      * @param desiredGroups   groups the registration wishes to discover
4678      *                        (can be null = ALL_GROUPS)
4679      * 
4680      *  @return <code>true</code> if either the registrar's locator is
4681      *          contained in the registration's set of locators to discover,
4682      *          or at least one of the registrar's member groups is contained
4683      *          in the registration's set of groups to discover;
4684      *          <code>false</code> otherwise
4685      */
4686     private static boolean interested(LookupLocator regLoc,
4687                                       String[] regGroups,
4688                                       Set desiredLocators,
4689                                       Set desiredGroups)
4690     {
4691         if(locSetContainsLoc(desiredLocators,regLoc)) return true;
4692         return interested(regGroups,desiredGroups);
4693     }//end interested
4694 
4695     /** This method returns a mapping in which the key values are registrars,
4696      *  and the map values are the member groups of the corresponding
4697      *  registrar key. The registrar and member groups from the input map
4698      *  are selected to be included in the returned mapping if and only if
4699      *  the key value under consideration is a registrar that belongs to none
4700      *  of the desired groups of the given registration (<code>regInfo</code>).
4701      *  That is, the registrars referenced in the returned mapping are the
4702      *  registrars that are no longer of interest - through group discovery
4703      *  - to the given registration.
4704      *
4705      * @param regMap map whose key values are registrars, and whose map
4706      *                values are data structures of type
4707      *                <code>LocatorGroupsStruct</code> that contain the
4708      *                associated locator and member groups of the
4709      *                corresponding registrar key; the elements of the
4710      *                return map are selected from this mapping
4711      * @param regInfo the data structure record corresponding to the 
4712      *                registration whose groups-to-discover will be used
4713      *                to select the elements from <code>regMap</code> to
4714      *                include in the return mapping
4715      * 
4716      *  @return a registrar-to-groups map in which each registrar in the map
4717      *          is from the <code>regMap</code> parameter, and belongs to none
4718      *          of the desired groups referenced in the <code>regInfo</code>
4719      *          parameter
4720      */
4721     private static Map getUndesiredRegsByGroup(Map regMap, 
4722                                                    RegistrationInfo regInfo)
4723     {
4724         Set<String> desiredGroups = regInfo.groups;
4725         HashMap undesiredRegMap = new HashMap(regMap.size());
4726         Set eSet = regMap.entrySet();
4727         for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
4728             Map.Entry pair = (Map.Entry)itr.next();
4729             String[] regGroups = ((LocatorGroupsStruct)pair.getValue()).groups;
4730             if( !interested(regGroups,desiredGroups) ) {
4731                 undesiredRegMap.put((ServiceRegistrar)pair.getKey(),regGroups);
4732             }//endif
4733         }//end loop
4734         return undesiredRegMap;
4735     }//end getUndesiredRegsByGroup
4736 
4737     /** This method returns a subset of the given registrar-to-locators
4738      *  mapping (<code>regMap</code>). An element of the given mapping is
4739      *  selected to be included in the returned mapping if and only if
4740      *  the key value of the element is a registrar whose locator equals
4741      *  none of the desired locators of the given registration
4742      *  (<code>regInfo</code>). That is, the registrars referenced in
4743      *  the returned mapping are the registrars that are no longer of
4744      *  interest - through locator discovery - to the given registration.
4745      *
4746      *  This method returns a mapping in which the key values are registrars,
4747      *  and the map values are the locators of the corresponding registrar
4748      *  key. The registrar and locators from the input map are selected to
4749      *  be included in the returned mapping if and only if the key value
4750      *  under consideration is a registrar whose locator equals none of the
4751      *  desired locators of the given registration (<code>regInfo</code>).
4752      *  That is, the registrars referenced in the returned mapping are the
4753      *  registrars that are no longer of interest - through locator discovery
4754      *  - to the given registration.
4755      *
4756      * @param regMap map whose key values are registrars, and whose map
4757      *                values are data structures of type
4758      *                <code>LocatorGroupsStruct</code> that contain the
4759      *                associated locator and member groups of the
4760      *                corresponding registrar key; the elements of the
4761      *                return map are selected from this mapping
4762      * @param regInfo the data structure record corresponding to the 
4763      *                registration whose locators-to-discover will be used
4764      *                to select the elements from <code>regMap</code> to
4765      *                include in the return mapping
4766      * 
4767      *  @return a registrars-to-locators map in which each registrar key in
4768      *          the map is from the <code>regMap</code> parameter, and has
4769      *          a locator equal to none of the desired locators referenced
4770      *          in the <code>regInfo</code> parameter
4771      */
4772     private static Map getUndesiredRegsByLocator(Map regMap, 
4773                                                  RegistrationInfo regInfo)
4774     {
4775 	Set<LookupLocator> desiredLocators = regInfo.locators;
4776         HashMap undesiredRegMap = new HashMap(regMap.size());
4777         Set eSet = regMap.entrySet();
4778         for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
4779             Map.Entry pair = (Map.Entry)itr.next();
4780             LookupLocator regLocator
4781                              = ((LocatorGroupsStruct)pair.getValue()).locator;
4782             if(!locSetContainsLoc(desiredLocators,regLocator)) {
4783                 undesiredRegMap.put((ServiceRegistrar)pair.getKey(),
4784                                     regLocator);
4785             }//endif
4786         }//end loop
4787         return undesiredRegMap;
4788     }//end getUndesiredRegsByLocator
4789 
4790     /**
4791      * Marshals each element of the <code>Entry[]</code> array parameter.
4792      * This method is <code>static</code> so that it may called from
4793      * the <code>static</code> <code>LogRecord</code> classes when a set
4794      * of attributes is being logged to persistent storage.
4795      *
4796      * @param fiddlerImpl reference to the current instance of this service
4797      * @param attrs       <code>Entry[]</code> array consisting of the
4798      *                    attributes to marshal
4799      * @return array of <code>MarshalledObject[]</code>, where each element
4800      *         corresponds to an attribute in marshalled form 
4801      */
4802     private static MarshalledObject[] marshalAttributes
4803                                                    (FiddlerImpl fiddlerImpl,
4804                                                     Entry[] attrs)
4805     {
4806         if(attrs == null) return new MarshalledObject[0];
4807         List<MarshalledObject> marshalledAttrs = new ArrayList<MarshalledObject>();
4808         for(int i=0;i<attrs.length;i++) {
4809             /* Do not let an attribute problem prevent the service from
4810              * continuing to operate
4811              */
4812             try {
4813                 marshalledAttrs.add(
4814                     new MarshalledInstance(attrs[i]).convertToMarshalledObject());
4815             } catch(Throwable e) {
4816                 if( problemLogger.isLoggable(Level.INFO) ) {
4817                     problemLogger.log(Level.INFO,
4818                                       "Error while marshalling attribute["+i
4819                                       +"] ("+attrs[i]+")", e);
4820                 }//endif
4821             }
4822         }//end loop
4823         return ((MarshalledObject[])(marshalledAttrs.toArray
4824                              (new MarshalledObject[marshalledAttrs.size()])));
4825     }//end marshalAttributes
4826 
4827     /**
4828      * Unmarshals each element of the <code>MarshalledObject[]</code> array
4829      * parameter. This method is <code>static</code> so that it may called
4830      * from the <code>static</code> <code>LogRecord</code> classes when a
4831      * set of attributes is being recovered from persistent storage.
4832      *
4833      * @param fiddlerImpl     reference to the current instance of this service
4834      * @param marshalledAttrs <code>MarshalledObject[]</code> array consisting
4835      *                        of the attributes to unmarshal
4836      * @return array of <code>Entry[]</code>, where each element corresponds
4837      *         to an attribute that was successfully unmarshalled
4838      */
4839     private static Entry[] unmarshalAttributes
4840                                           (FiddlerImpl fiddlerImpl,
4841                                            MarshalledObject[] marshalledAttrs)
4842     {
4843         if(marshalledAttrs == null) return new Entry[0];
4844         ArrayList attrs = new ArrayList();
4845         for(int i=0;i<marshalledAttrs.length;i++) {
4846             /* Do not let an attribute problem prevent the service from
4847              * continuing to operate
4848              */
4849             try {
4850                 attrs.add( (Entry)( new MarshalledInstance(marshalledAttrs[i]).get(false) ) );
4851             } catch(Throwable e) {
4852                 if( problemLogger.isLoggable(Level.INFO) ) {
4853                     problemLogger.log(Level.INFO,
4854                                       "Error while unmarshalling attribute["+i
4855                                       +"]", e);
4856                 }//endif
4857             }
4858         }//end loop
4859         return ((Entry[])(attrs.toArray(new Entry[attrs.size()])));
4860     }//end unmarshalAttributes
4861 
4862     /** Using the given <code>ProxyPreparer</code>, attempts to prepare each
4863      *  element of the given <code>LookupLocator</code> array; replacing the
4864      *  original element of the array with the result of the call to the
4865      *  method <code>ProxyPreparer.prepareProxy</code>. If any attempt to
4866      *  prepare an element of the given array fails due to an exception,
4867      *  this method will propagate that exception.
4868      *
4869      *  This method is a convenience method that is typically used to
4870      *  prepare new locators the service should discover and join that
4871      *  are inserted into, or removed from, the service's state through
4872      *  the use of one of the following methods:
4873      *  <ul><li> <code>addLookupLocators</code>
4874      *      <li> <code>setLookupLocators</code>
4875      *      <li> <code>removeLookupLocators</code>
4876      *      <li> <code>addLocators</code>
4877      *      <li> <code>setLocators</code>
4878      *      <li> <code>removeLocators</code>
4879      *  </ul>
4880      * 
4881      * @param preparer the preparer to use to prepare each element of the
4882      *                 input array
4883      * @param locators array of <code>LookupLocator</code> instances in which
4884      *                 each element will be prepared.
4885      * 
4886      * @throws RemoteException   when preparation of any of the elements
4887      *                           of the input array fails because of a
4888      *                           <code>RemoteException</code>
4889      * @throws SecurityException when preparation of any of the elements
4890      *                           of the input array fails because of a
4891      *                           <code>SecurityException</code>
4892      */
4893     private static void prepareNewLocators(ProxyPreparer preparer,
4894                                            LookupLocator[] locators) 
4895                                                        throws RemoteException 
4896     {
4897         for (int i=0; i<locators.length; i++) {
4898             locators[i] = (LookupLocator)preparer.prepareProxy(locators[i]);
4899         }//end loop
4900     }//end prepareNewLocators
4901 
4902     /** Using the given <code>ProxyPreparer</code>, attempts to prepare each
4903      *  element of the given <code>LookupLocator</code> array; and returns
4904      *  a new array containing the prepared locators. If any attempt to
4905      *  prepare an element of the given array fails due to an exception,
4906      *  this method will skip to the next locator in that input array.
4907      *
4908      *  This method is a convenience method that is typically used to
4909      *  re-prepare the previously prepared locators that are retrieved
4910      *  from the service's persisted state during recovery.
4911      * 
4912      * @param preparer the preparer to use to prepare each element of the
4913      *                 input array
4914      * @param locators array of <code>LookupLocator</code> instances in which
4915      *                 each element will be prepared.
4916      * 
4917      * @return array of <code>LookupLocator</code> instances in which each
4918      *         element of the returned array is the result of successful proxy
4919      *         preparation of the corresponding element of the input array
4920      */
4921     private static LookupLocator[] prepareOldLocators(ProxyPreparer preparer,
4922                                                       LookupLocator[] locators)
4923     {
4924         ArrayList locsList = new ArrayList(locators.length);
4925         for(int i=0; i<locators.length; i++) {
4926             try {
4927                 locsList.add(preparer.prepareProxy(locators[i]) );
4928             } catch(Throwable e) {
4929                 if( problemLogger.isLoggable(Level.INFO) ) {
4930                     problemLogger.log(Level.INFO,"failure preparing recovered "
4931                                                  +"lookup locator["+i+"]", e);
4932                 }//endif
4933             }
4934         }//end loop
4935         if(locators.length != locsList.size()) {
4936             if( problemLogger.isLoggable(Levels.HANDLED) ) {
4937                 problemLogger.log(Levels.HANDLED,
4938                                   "number of requested recovered "
4939                                   +"lookup locators = "+locators.length);
4940                 problemLogger.log(Levels.HANDLED, "number of successfully "
4941                                   +"prepared recovered lookup locators = "
4942                                   +locsList.size());
4943                 for(int i=0; i<locsList.size(); i++) {
4944                     problemLogger.log(Levels.HANDLED, "successfully prepared "
4945                                       +"recovered lookup locator = "
4946                                       +locsList.get(i));
4947                 }//end loop
4948             }//endif
4949         }//endif
4950         return ( (LookupLocator[])locsList.toArray
4951                                        (new LookupLocator[locsList.size()]) );
4952     }//end prepareOldLocators
4953 
4954     /** Using the given <code>ProxyPreparer</code>, attempts to prepare each
4955      *  element of the given <code>Set</code> of <code>LookupLocator</code>
4956      *  instances; and returns a new <code>Set</code> containing the prepared
4957      *  locators. If any attempt to prepare an element of the given 
4958      *  <code>Set</code> fails due to an exception, this method will skip
4959      *  to the next locator in that input <code>Set</code>.
4960      *
4961      *  This method is a convenience method that is typically used to
4962      *  re-prepare the previously prepared locators that are retrieved
4963      *  from the service's persisted state during recovery.
4964      * 
4965      * @param preparer the preparer to use to prepare each element of the
4966      *                 input array
4967      * @param locators <code>Set</code> of <code>LookupLocator</code>
4968      *                 instances in which each element will be prepared.
4969      * 
4970      * @return <code>Set</code> of <code>LookupLocator</code> instances in
4971      *         which each element of the returned <code>Set</code> is the
4972      *         result of successful proxy preparation of the corresponding
4973      *         element of the input <code>Set</code>
4974      */
4975     private static Set prepareOldLocators(ProxyPreparer preparer,
4976                                           Set locators)
4977     {
4978         Set locSet = new HashSet(locators.size());
4979         LookupLocator[] locsArray =
4980              prepareOldLocators( preparer,
4981                                 (LookupLocator[])locators.toArray
4982                                         (new LookupLocator[locators.size()]) );
4983         for(int i=0; i<locsArray.length; i++) {
4984             locSet.add(locsArray[i]);
4985         }//end loop
4986         return locSet;
4987     }//end prepareOldLocators
4988 
4989     /** Searches the given set of locators for the given individual locator,
4990      *  returning <code>true</code> if the indicated locator is found in the
4991      *  set; <code>false</code> otherwise.
4992      *
4993      *  This method is a convenience method that is called instead of calling
4994      *  only the <code>contains</code> method on the <code>Set</code>
4995      *  parameter. This is necessary because the <code>equals</code> method
4996      *  on <code>LookupLocator</code> performs a simple <code>String</code>
4997      *  compare of the host names referenced by the locators being compared.
4998      *  Such a comparison can result in a "false negative" when the hostname
4999      *  returned by a remote system provides a fully-qualified hostname
5000      *  (ex. "myhost.subdomain.mycompany.com"), but clients of this service
5001      *  indicate interest in a locator using only the unqualified hostname
5002      *  (ex. "myhost"). In this case, both host names are legal and 
5003      *  functionally equivalent, but the <code>equals</code> method on 
5004      *  <code>LookupLocator</code> will interpret them as unequal; resulting
5005      *  in failure to discover locators that actually should be discovered.
5006      *
5007      *  To address the problem described above, this method will do the 
5008      *  following when attempting to determine whether the given locator
5009      *  is contained in the given set of locators:
5010      *
5011      *    1. Apply <code>Set</code>.<code>contains</code> which uses
5012      *       <code>LookupLocator</code>.<code>equals</code> to determine
5013      *       if the given locator is an element of the given set of locators.
5014      *    2. If the <code>Set</code>.<code>contains</code> method returns
5015      *       <code>false</code>, then iterate through the elements of the
5016      *       given set, retrieving and comparing the port and
5017      *       <code>InetAddress</code> of each element to the port and
5018      *       <code>InetAddress</code> of the given locator.
5019      * 
5020      * @param locSet this method will determine whether or not the given
5021      *               locator is contained in this <code>Set</code> of
5022      *               <code>LookupLocator</code>s.
5023      * @param loc    this method will determine whether or not this
5024      *               <code>LookupLocator</code> is contained in the given set.
5025      * 
5026      * @return <code>true</code> if the given set of locators contains the
5027      *         given locator; <code>false</code> otherwise.
5028      */
5029     private static boolean locSetContainsLoc(Set locSet, LookupLocator loc) {
5030         if( locSet.contains(loc) ) return true;//try LookupLocator.equals first
5031         /* Set containment test failed. Iterate through the set. */
5032         int port0 = loc.getPort();
5033         InetAddress addr0 = null;
5034         for(Iterator itr = locSet.iterator(); itr.hasNext(); ) {
5035             LookupLocator nextLoc = (LookupLocator)itr.next();
5036             if(nextLoc.getPort() != port0) continue;//try next port in set
5037             if(addr0 == null) {//only need to retrieve addr0 once
5038                 try {
5039                     addr0 = InetAddress.getByName(loc.getHost());
5040                 } catch(Exception e) {
5041                     problemLogger.log(Levels.HANDLED,
5042                                       "problem retrieving address by name", e);
5043                     return false;
5044                 }
5045             }//endif
5046             InetAddress addr1 = null;
5047             try {
5048                 addr1 = InetAddress.getByName(nextLoc.getHost());
5049             } catch(Exception e) {
5050                 problemLogger.log(Level.FINEST,
5051                                   "problem retrieving address by name", e);
5052                 continue;//try next address in set
5053             }
5054             if( addr1.equals(addr0) ) return true;
5055         }//end loop
5056         return false;
5057     }//end locSetContainsLoc
5058 
5059     
5060     /** Common entry point for initialization of the service in any of its
5061      *  possible modes: transient, non-activatable-persistent, or 
5062      *  activatable-persistent; with or without performing a JAAS login.
5063      */
5064     private static FiddlerInit init(String[] configArgs, boolean persistent) 
5065             throws IOException, ConfigurationException, LoginException{
5066         try {
5067             return init(configArgs, persistent, null);
5068         } catch (ActivationException e){
5069             // swallow will never happen because it's null.
5070             return null;
5071         }
5072     }
5073     
5074     private static FiddlerInit init(String[] configArgs, 
5075                                     boolean persistent, 
5076                                     ActivationID activeID)
5077                                            throws IOException,
5078                                                   ConfigurationException,
5079                                                   LoginException,
5080                                                   ActivationException
5081     {
5082        
5083         Configuration config = ConfigurationProvider.getInstance
5084                                        ( configArgs,
5085                                          (FiddlerImpl.class).getClassLoader() );
5086 
5087         LoginContext loginContext = (LoginContext)config.getEntry(COMPONENT_NAME,
5088                                                      "loginContext",
5089                                                      LoginContext.class,
5090                                                      null);
5091         if(loginContext != null) {
5092             return initWithLogin(config, persistent, loginContext, activeID);
5093         } else {
5094             return new FiddlerInit(config, persistent, activeID, null);
5095         }//endif
5096         
5097     }//end init
5098 
5099     /** Initialization with JAAS login as the <code>Subject</code> referenced
5100      *  in the given <code>loginContext</code>.
5101      */
5102     private static FiddlerInit initWithLogin( final Configuration config,
5103                                 final boolean persistent, 
5104                                 final LoginContext loginContext, 
5105                                 final ActivationID activeID)
5106                                                  throws IOException,
5107                                                         ConfigurationException,
5108                                                         LoginException,
5109                                                         ActivationException
5110     {
5111         loginContext.login();
5112         try {
5113             return Subject.doAsPrivileged( loginContext.getSubject(),
5114                                     new PrivilegedExceptionAction<FiddlerInit>() {
5115                                         public FiddlerInit run() throws Exception {
5116                                             return new FiddlerInit(config, persistent, activeID, loginContext);
5117                                         }//end run
5118                                     },
5119                                     null );//end doAsPrivileged
5120         } catch (Throwable e) {
5121             if(e instanceof PrivilegedExceptionAction)  e = e.getCause();
5122             if(e instanceof IOException)  throw (IOException)e;
5123             if(e instanceof ConfigurationException) 
5124                                           throw (ConfigurationException)e;
5125             if (e instanceof ActivationException) throw (ActivationException) e;
5126             throw new RuntimeException(e);
5127         }
5128     }//end initWithLogin
5129     
5130     /* **************** END Private Static Utility Methods ***************** */
5131     
5132     /* BEGIN public start method*/
5133     public void start() throws IOException, ActivationException, ConfigurationException, LoginException, ClassNotFoundException 
5134     {
5135         synchronized (this){
5136             if (started) return;
5137             started = true;
5138         }
5139         concurrentObj.writeLock();
5140         try {
5141         AccessController.doPrivileged(new PrivilegedExceptionAction(){
5142 
5143             @Override
5144             public Object run() throws Exception {
5145                 if (persistent){
5146                     logHandler.setFiddler(FiddlerImpl.this);
5147                     inRecovery = true;
5148                     log.recover();
5149                     inRecovery = false;
5150                 }
5151                 
5152                 /* For the two persistent versions of this service (activatable and
5153                  * non-activatable), state recovery is complete. For the non-persistent
5154                  * version of this service, no state recovery occurred (because it
5155                  * wasn't necessary).
5156                  *
5157                  * For the two persistent versions, there is a circumstance in which
5158                  * 'one time', initial items must be retrieved from the configuration:
5159                  * when the service is started for the very first time. For the
5160                  * non-persistent version, those items will be retrieved every time
5161                  * the service is started.
5162                  *
5163                  * The flag 'initialStartup' is used below to determine whether
5164                  * or not to retrieve the initial configuration items. This is the
5165                  * only purpose for that flag. 
5166                  *
5167                  * For either persistent version of the service, the flag's value
5168                  * will be changed to false during the startup process only when
5169                  * there already exists a 'snapshot' of the service's state from
5170                  * a previous run. This is because the flag's value is only
5171                  * changed during the recovery of the snapshot (see the method
5172                  * recoverSnapshot()). Note that the only time such a snapshot
5173                  * should NOT already exist at startup, is when the service is
5174                  * being started for the very first time. Thus, when either
5175                  * persistent version of the service is started for the first
5176                  * time, the service's configuration is consulted for the initial
5177                  * values of the items below; otherwise, when the service is being
5178                  * re-started (after a crash for example), the values used for
5179                  * those items will be the values retrieved above during recovery
5180                  * of the service's persistent state.
5181                  * 
5182                  * With respect to the non-persistent version of the service, the
5183                  * values of the items below will always be retrieved at startup.
5184                  * This is because the non-persistent version of the service never
5185                  * attempts to recover previously stored state; thus, the flag's
5186                  * value will never change. Note that this will be true even if a
5187                  * snapshot exists from a previous run of one of the persistent
5188                  * versions of the service. 
5189                  *
5190                  * The service's Uuid is also handled here.
5191                  */
5192                 if(initialStartup) {
5193                     if(log != null) {
5194                         snapshotWt = ((Float)config.getEntry
5195                                                  (FiddlerImpl.COMPONENT_NAME,
5196                                                   "initialPersistenceSnapshotWeight",
5197                                                   float.class,
5198                                                   new Float(snapshotWt))).floatValue();
5199                         snapshotThresh =
5200                                Config.getIntEntry
5201                                           (config,
5202                                            FiddlerImpl.COMPONENT_NAME,
5203                                            "initialPersistenceSnapshotThreshold",
5204                                            snapshotThresh, 0, Integer.MAX_VALUE);
5205                     }//endif(log != null)
5206                     leaseBound = Config.getLongEntry(config,
5207                                                      FiddlerImpl.COMPONENT_NAME,
5208                                                      "initialLeaseBound",
5209                                                      leaseBound, 0, Long.MAX_VALUE);
5210                     /* Get any additional attributes with which to associate this
5211                      * service when registering it with any lookup services.
5212                      */
5213                     Entry[] initAttrs = (Entry[])config.getEntry
5214                                                            (FiddlerImpl.COMPONENT_NAME,
5215                                                             "initialLookupAttributes",
5216                                                             Entry[].class,
5217                                                             null );
5218                     if(initAttrs != null) {
5219                         ArrayList attrsList
5220                            = new ArrayList(thisServicesAttrs.length+initAttrs.length);
5221                         for(int i=0;i<thisServicesAttrs.length;i++) {
5222                             attrsList.add(thisServicesAttrs[i]);
5223                         }//end loop
5224                         for(int i=0;i<initAttrs.length;i++) {
5225                             attrsList.add(initAttrs[i]);
5226                         }//end loop
5227                         thisServicesAttrs = (Entry[])attrsList.toArray
5228                                                         (new Entry[attrsList.size()]);
5229                     }//endif(initAttrs != null)
5230 
5231                     /* Get the initial groups this service should join. */
5232                     thisServicesGroups =
5233                          (String[])config.getEntry(FiddlerImpl.COMPONENT_NAME, 
5234                                                    "initialLookupGroups", 
5235                                                    String[].class, 
5236                                                    thisServicesGroups);
5237                     /* Get the initial locators this service should join. */
5238                     thisServicesLocators =
5239                              (LookupLocator[])config.getEntry(FiddlerImpl.COMPONENT_NAME, 
5240                                                               "initialLookupLocators", 
5241                                                               LookupLocator[].class, 
5242                                                               new LookupLocator[0]);
5243                     if(thisServicesLocators == null) {
5244                         thisServicesLocators = new LookupLocator[0];
5245                     }//endif
5246 
5247                     /* Generate the private, universally unique (over space and time)
5248                      * ID that will be used by the outer proxy to test for equality
5249                      * with other proxies.
5250                      */
5251                     proxyID = UuidFactory.generate();
5252                 }//endif(initialStartup)
5253 
5254                 /* The proxyID should never be null at this point. It should have
5255                  * been either recovered from the persisted state, or generated above.
5256                  */
5257                 if(proxyID == null) throw new NullPointerException("proxyID == null");
5258                 /* Take a snapshot of the current state to "clean up" the log file,
5259                  * and to record the items set above.
5260                  */
5261                 if(log != null) log.snapshot();
5262                 /* The service ID used to register this service with lookup services
5263                  * is always derived from the proxyID that is associated with the
5264                  * service for the lifetime of the service.
5265                  */
5266                 serviceID = new ServiceID(proxyID.getMostSignificantBits(),
5267                                           proxyID.getLeastSignificantBits());
5268                 
5269                         /* Export this service */
5270                 innerProxy = (Fiddler)serverExporter.export(FiddlerImpl.this);
5271 
5272                 /* Create the outer (smart) proxy that is registered with lookups */
5273                 outerProxy = FiddlerProxy.createServiceProxy(innerProxy, proxyID);
5274                 /* Create the proxy that can be used to administer this service */
5275                 adminProxy = FiddlerAdminProxy.createAdminProxy(innerProxy, proxyID);
5276 
5277                 /* Start the discovery mechanism for all recovered registrations */
5278                 discoveryMgr.addDiscoveryListener(discoveryListener);
5279 
5280                 /* Advertise the services provided by this entity */
5281                 joinMgr = new JoinManager(outerProxy, thisServicesAttrs,
5282                                           serviceID, joinMgrLDM, null,
5283                                           config);
5284                 ((DiscoveryLocatorManagement)joinMgrLDM).setLocators
5285                                                                 (thisServicesLocators);
5286                 ((DiscoveryGroupManagement)joinMgrLDM).setGroups(thisServicesGroups);
5287 
5288                 /* start up all the daemon threads */
5289                 leaseExpireThread.start();
5290                 if(log != null) {
5291                     snapshotThread.start();
5292                 }
5293                 logInfoStartup();
5294                 readyState.ready(); 
5295                 return null;
5296             }
5297             
5298         } , context);
5299         } catch (PrivilegedActionException e) {
5300             Throwable t = e.getCause();
5301             cleanupInitFailure();
5302             handleActivatableInitThrowable(t);
5303         } finally {
5304             logHandler = null;
5305             concurrentObj.writeUnlock();
5306         }
5307     } 
5308     /* END public start method */
5309 
5310     /* BEGIN Private Shutdown Methods -------------------------------------- */
5311     /* Called in the constructor when failure occurs during the initialization
5312      * process. Un-does any work that may have already been completed; for
5313      * example, un-exports the service if it has already been exported,
5314      * terminates any threads that may have been started, etc.
5315      */
5316     private void cleanupInitFailure() {
5317         if(innerProxy != null)  {
5318             try {
5319                 serverExporter.unexport(true);
5320             } catch(Throwable t) { }
5321         }//endif
5322 
5323         if(executorService != null)  {
5324             try {
5325                 executorService.shutdown();
5326             } catch(Throwable t) { }
5327         }//endif
5328 
5329         if(joinMgr != null)  {
5330             try {
5331                 joinMgr.terminate();
5332             } catch(Throwable t) { }
5333         }//endif
5334 
5335         if(joinMgrLDM != null)  {
5336             try {
5337                 joinMgrLDM.terminate();
5338             } catch(Throwable t) { }
5339         }//endif
5340 
5341         if(discoveryMgr != null)  {
5342             try {
5343                 discoveryMgr.terminate();
5344             } catch(Throwable t) { }
5345         }//endif
5346 
5347         if(leaseExpireThread != null)  {
5348             try {
5349                 leaseExpireThread.interrupt();
5350                 leaseExpireThread.join();
5351             } catch(Throwable t) { }
5352         }//endif
5353 
5354         if(snapshotThread != null)  {
5355             try {
5356                 snapshotThread.interrupt();
5357                 snapshotThread.join();
5358             } catch(Throwable t) { }
5359         }//endif
5360     }//end cleanupInitFailure
5361 
5362     /* Convenience method called in the constructor or the activatable version
5363      * of this service when failure occurs during the initialization process.
5364      * Logs and rethrows the given <code>Throwable</code> so the constructor
5365      * doesn't have to.
5366      */
5367     private void handleActivatableInitThrowable(Throwable t) 
5368                                             throws IOException,
5369                                                    ActivationException,
5370                                                    ConfigurationException,
5371                                                    LoginException,
5372                                                    ClassNotFoundException
5373     {
5374         handleInitThrowable(t);
5375         if (t instanceof ActivationException) {
5376             throw (ActivationException)t;
5377         } else if (t instanceof ClassNotFoundException) { 
5378             /* instanceof LoginException would have already been rethown so wasn't 
5379              * reachable I suspect ClassNotFoundException is what the implementer wanted.
5380              */
5381             throw (ClassNotFoundException)t;
5382         } else {
5383             throw new AssertionError(t);
5384         }//endif
5385     }//end handleInitThrowable
5386 
5387     /* Convenience method called in the constructor or the non-activatable 
5388      * version of this service when failure occurs during the initialization
5389      * process. Logs and rethrows the given <code>Throwable</code> so the
5390      * constructor doesn't have to.
5391      */
5392     private void handleInitThrowable(Throwable t) 
5393                                             throws IOException,
5394                                                    ConfigurationException,
5395                                                    LoginException
5396     {
5397         problemLogger.log(Level.SEVERE, "cannot initialize the service", t);
5398         if (t instanceof IOException) {
5399             throw (IOException)t;
5400         } else if (t instanceof ConfigurationException) {
5401             throw (ConfigurationException)t;
5402         } else if (t instanceof LoginException) {
5403             throw (LoginException)t;
5404         } else if (t instanceof RuntimeException) {
5405             throw (RuntimeException)t;
5406         } else if (t instanceof Error) {
5407             throw (Error)t;
5408         }//endif
5409     }//end handleInitThrowable
5410 
5411     /**
5412      * Called by the public method <code>destroy</code> as well as by the
5413      * <code>apply</code> method in the various LogObj classes that,
5414      * during recovery, modify the managed sets of the discovery manager;
5415      * and, while doing so, experience an IOException when the multicast
5416      * request protocol fails to start during recovery (an un-recoverable
5417      * exception).
5418      *
5419      * This method destroys the lookup discovery service, if possible,
5420      * including its persistent storage. This method spawns a separate
5421      * thread to do the actual work asynchronously, so a successful
5422      * return from this method usually does not mean that the service
5423      * has been destroyed.
5424      *
5425      * @see org.apache.river.fiddler.proxy.FiddlerImpl#destroy
5426      */
5427     private void destroyDo() {
5428         (new DestroyThread()).start();
5429     }//end destroyDo
5430     /* END Private Shutdown Methods ---------------------------------------- */
5431 
5432     /* BEGIN Private Registration Methods ---------------------------------- */
5433     /**
5434      * This method is called by the public method <code>register</code>.
5435      * This method creates a registration object that is an instance of
5436      * <code>FiddlerRegistration</code> which implements the interface
5437      * <code>LookupDiscoveryRegistration</code>, and acts as a proxy to the
5438      * registration-related methods of the backend server of the Fiddler
5439      * implementation of the lookup discovery service.
5440      * <p>
5441      * This method also associates with the registration all information
5442      * needed by the registration to participate in the lookup discovery
5443      * service, information such as: IDs, lease information, event information.
5444      * 
5445      * @param groups        names of groups whose members are the lookup
5446      *                      services to discover.
5447      * @param locators      instances of LookupLocator, each corresponding to
5448      *                      a specific lookup service to discover.
5449      * @param listener      the entity that will receive events notifying the
5450      *                      registration that a lookup service of interest has
5451      *                      been discovered.
5452      * @param handback      the object that will be included in every
5453      *                      notification event sent to the registered listener.
5454      * @param leaseDuration long value representing the amount of time (in
5455      *                      milliseconds) for which the resources of the
5456      *                      lookup discovery service are being requested.
5457      *                      
5458      * @return an instance of FiddlerRegistration which implements the
5459      *         LookupDiscoveryRegistration interface, and acts as a proxy
5460      *         to the registration-related methods of the backend server
5461      *         of the Fiddler implementation of the lookup discovery service
5462      *
5463      * @throws java.io.IOException this exception occurs when the multicast
5464      *         request protocol fails to start.
5465      *
5466      * @throws java.rmi.RemoteException this exception occurs when the
5467      *         attempt to prepare the listener fails due to a
5468      *         <code>RemoteException</code>
5469      * 
5470      * @throws java.lang.SecurityException this exception occurs when the
5471      *         attempt to prepare the listener fails due to a
5472      *         <code>SecurityException</code>
5473      *
5474      * @see org.apache.river.fiddler.proxy.FiddlerImpl#register
5475      */
5476     private LookupDiscoveryRegistration registerDo
5477                                              (String[] groups,
5478                                               LookupLocator[] locators,
5479                                               RemoteEventListener listener,
5480                                               MarshalledObject handback,
5481                                               long leaseDuration)
5482                                                        throws RemoteException,
5483                                                               IOException
5484     {
5485         /* Input okay. Create the registration and associated information */
5486 	long curTime    = System.currentTimeMillis();
5487         leaseDuration   = applyBoundToLeaseDuration(leaseDuration,
5488                                                     leaseBound);
5489         Uuid regID      = UuidFactory.generate();
5490         Uuid leaseID    = regID;//use same ID since Reg "wraps" the lease
5491         long expiration = curTime + leaseDuration;
5492 
5493         /* Prepare the new listener */
5494         listener = (RemoteEventListener)listenerPreparer.prepareProxy
5495                                                                    (listener);
5496         RegistrationInfo regInfo = new RegistrationInfo( regID,
5497                                                          groups,locators,
5498                                                          leaseID,expiration,
5499                                                          curEventID,handback,
5500                                                          listener );
5501         curEventID++;
5502 	addRegistration(regInfo);
5503         logInfoRegistration("\nadded registration:  registrationID = ",regID);
5504         addLogRecord(new RegistrationGrantedLogObj(regInfo));
5505         /* Queue task for sending a discovered event */
5506         executorService.execute(new NewRegistrationTask(regInfo));
5507 	/* See if the expire thread needs to wake up earlier */
5508 	if (expiration < minExpiration) {
5509 	    minExpiration = expiration;
5510             leaseExpireThreadSyncObj.signal();
5511 //	    concurrentObj.waiterNotify(leaseExpireThreadSyncObj);
5512 	}
5513         FiddlerLease regLease =
5514                    FiddlerLease.createLease
5515                            (innerProxy, proxyID, regID, leaseID, expiration);
5516         EventRegistration eventReg = new EventRegistration( regInfo.eventID,
5517                                                             outerProxy,
5518                                                             regLease,
5519                                                             regInfo.seqNum );
5520         logInfoGroups();
5521         logInfoLocators();
5522         FiddlerRegistration regObj = FiddlerRegistration.createRegistration
5523                                                 (innerProxy, regID, eventReg);
5524         logInfoRegistration("\ncreated registration: registrationID = ",
5525                             regObj);
5526         return regObj;
5527     }//end registerDo
5528 
5529     /** 
5530      * Places the registration corresponding to the <code>regInfo</code> 
5531      * parameter in both the <code>registrationByID</code> map and the 
5532      * <code>registrationByTime</code> map. This method also updates the 
5533      * managed sets in the discovery manager in the appropriate way. This 
5534      * should be called whenever a new registration has been created 
5535      * and needs to be added to the data base (for example, the methods
5536      * <code>registerDo</code>, <code>recoverSnapshot</code> and the 
5537      * <code>apply</code> method of the <code>RegistrationGrantedLogObj</code>
5538      * class all call this method).
5539      * 
5540      * @param regInfo  the data structure record corresponding to the
5541      *                 registration that is to be added to the data base
5542      *
5543      * @throws java.io.IOException this exception occurs when the multicast
5544      *         request protocol fails to start.
5545      *
5546      * @see org.apache.river.fiddler.proxy.FiddlerImpl#registerDo
5547      * @see org.apache.river.fiddler.proxy.FiddlerImpl#recoverSnapshot
5548      */
5549     private void addRegistration(RegistrationInfo regInfo) throws IOException {
5550         if (regInfo.listener == null) {
5551 	    /* failed to recover from log */
5552             if( problemLogger.isLoggable(Level.INFO) ) {
5553                 problemLogger.log(Level.INFO, "cannot add registration (ID = "
5554                                   +regInfo.registrationID+"); failed to "
5555                                   +"unmarshal listener during recovery");
5556             }//endif
5557             return;
5558         }//endif
5559         /* First add the indicated registration */
5560         registrationByID.put(regInfo.registrationID, regInfo);
5561         registrationByTime.put(regInfo,regInfo);
5562         /* Update the set of groups managed by the discovery manager */
5563         updateDiscoveryMgrGroups();
5564         /* Update the set of locators managed by the discovery manager */
5565         updateDiscoveryMgrLocators();
5566     }//end addRegistration
5567 
5568     /** 
5569      * Removes the registration corresponding to the <code>regInfo</code> 
5570      * parameter from this service's state. Removes the registration from 
5571      * both the <code>registrationByID</code> map and the 
5572      * <code>registrationByTime</code> map. This method also updates the 
5573      * managed sets in the discovery manager in the appropriate way. This 
5574      * should be called whenever a current registration needs to be removed
5575      * from the data base (for example, when the registration's lease is
5576      * is expired in the <code>LeaseExpireThread</code>, and when the
5577      * registration's lease is cancelled in the <code>cancelLeaseDo</code>
5578      * method).
5579      * 
5580      * @param regInfo  the data structure record corresponding to the
5581      *                 registration that is to be removed from the data base
5582      *
5583      * @throws java.io.IOException this exception occurs when the multicast
5584      *         request protocol fails to start.
5585      *
5586      * @see org.apache.river.fiddler.proxy.FiddlerImpl#cancelLeaseDo
5587      */
5588     private void removeRegistration(RegistrationInfo regInfo)
5589                                                          throws IOException
5590     {
5591         /* First remove the current registration */
5592         registrationByID.remove(regInfo.registrationID);
5593         registrationByTime.remove(regInfo);
5594         logInfoRegistration("\nremoved registration: registrationID = ",
5595                             regInfo.registrationID);
5596         /* Update the set of groups managed by the discovery manager */
5597         updateDiscoveryMgrGroups();
5598         /* Update the set of locators managed by the discovery manager */
5599         updateDiscoveryMgrLocators();
5600         logInfoGroups();
5601         logInfoLocators();
5602     }//end removeRegistration
5603     /* END Private Registration Methods ------------------------------------ */
5604 
5605     /* BEGIN Private Group Management Methods ------------------------------ */
5606     /**
5607      * Called by the public method <code>addGroups</code>. This method
5608      * queues an <code>AddGroupsTask</code> which performs the actual
5609      * augmentation of the given registration's desired groups.
5610      * 
5611      * @param regInfo the data structure record corresponding to the
5612      *                registration whose managed set of groups is to be
5613      *                augmented
5614      * @param groups  a String array, none of whose elements may be null,
5615      *                consisting of the group names with which to augment the
5616      *                registration's managed set of groups.
5617      *
5618      * @see org.apache.river.fiddler.proxy.FiddlerImpl#addGroups
5619      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#addGroups
5620      * @see net.jini.discovery.LookupDiscoveryRegistration#addGroups
5621      */
5622     private void addGroupsDo(RegistrationInfo regInfo, String[] groups) {
5623         executorService.execute(new AddGroupsTask(regInfo,groups));
5624     }//end addGroupsDo
5625 
5626     /**
5627      * Called by the <code>apply</code> method of the class
5628      * <code>GroupsAddedToRegistrationLogObj</code> (which is invoked 
5629      * during state recovery). This method queues an
5630      * <code>AddGroupsTask</code> which performs the actual augmentation
5631      * of the given registration's desired groups.
5632      * <p>
5633      * @param registrationID   the ID of the data structure record
5634      *                         corresponding to the registration whose
5635      *                         managed set of groups is to be augmented
5636      * @param registrationByID the map containing all active registrations
5637      *                         managed by this service
5638      * @param groups           a <code>String</code> array, none of whose 
5639      *                         elements may be null, consisting of the group
5640      *                         names with which to augment the registration's
5641      *                         managed set of groups.
5642      * 
5643      * @see org.apache.river.fiddler.proxy.FiddlerImpl#addGroupsDo
5644      * @see org.apache.river.fiddler.proxy.FiddlerImpl#addGroups
5645      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#addGroups
5646      * @see net.jini.discovery.LookupDiscoveryRegistration#addGroups
5647      */
5648     private void addGroupsDo(Uuid registrationID,
5649                              HashMap registrationByID,
5650                              String[] groups)
5651     {
5652         addGroupsDo((RegistrationInfo)(registrationByID.get(registrationID)),
5653                      groups);
5654     }//end addGroupsDo
5655 
5656     /**
5657      * Called by the public method <code>setGroups</code>. This method
5658      * queues a <code>SetGroupsTask</code> which performs the actual
5659      * replacement.
5660      * 
5661      * @param regInfo the data structure record corresponding to the
5662      *                registration whose managed set of groups is to be
5663      *                replaced
5664      * @param groups  a String array, none of whose elements may be null, 
5665      *                consisting of the group names with which to replace the
5666      *                registration's managed set of groups.
5667      * 
5668      * @see org.apache.river.fiddler.proxy.FiddlerImpl#setGroups
5669      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#setGroups
5670      * @see net.jini.discovery.LookupDiscoveryRegistration#setGroups
5671      */
5672     private void setGroupsDo(RegistrationInfo regInfo, String[] groups) {
5673         executorService.execute(new SetGroupsTask(regInfo,groups));
5674     }//end setGroupsDo
5675 
5676     /**
5677      * Called by the <code>apply</code> method of the class
5678      * <code>GroupsSetInRegistrationLogObj</code> (which is invoked 
5679      * during state recovery). This method queues a
5680      * <code>SetGroupsTask</code> which performs the actual replacement.
5681      * <p>
5682      * @param registrationID   the ID of the data structure record
5683      *                         corresponding to the registration whose
5684      *                         managed set of groups is to be replaced
5685      * @param registrationByID the map containing all active registrations
5686      *                         managed by this service
5687      * @param groups           a <code>String</code> array, none of whose 
5688      *                         elements may be null, consisting of the group
5689      *                         names with which to replace the registration's
5690      *                         managed set of groups.
5691      * 
5692      * @see org.apache.river.fiddler.proxy.FiddlerImpl#setGroupsDo
5693      * @see org.apache.river.fiddler.proxy.FiddlerImpl#setGroups
5694      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#setGroups
5695      * @see net.jini.discovery.LookupDiscoveryRegistration#setGroups
5696      */
5697     private void setGroupsDo(Uuid registrationID,
5698                              HashMap registrationByID,
5699                              String[] groups)
5700     {
5701         setGroupsDo((RegistrationInfo)(registrationByID.get(registrationID)),
5702                      groups);
5703     }//end setGroupsDo
5704 
5705     /**
5706      * Called by the public method <code>removeGroups</code>. This method
5707      * queues a <code>RemoveGroupsTask</code> which performs the actual
5708      * removal.
5709      * 
5710      * @param regInfo the data structure record corresponding to the
5711      *                registration from whose managed set of groups the input
5712      *                set of groups is to be removed
5713      * @param groups  a String array, none of whose elements may be null,
5714      *                consisting of the group names to remove from the 
5715      *                registration's managed set of groups
5716      * 
5717      * @see org.apache.river.fiddler.proxy.FiddlerImpl#removeGroups
5718      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#removeGroups
5719      * @see net.jini.discovery.LookupDiscoveryRegistration#removeGroups
5720      */
5721     private void removeGroupsDo(RegistrationInfo regInfo, String[] groups) {
5722         executorService.execute(new RemoveGroupsTask(regInfo,groups));
5723     }//end removeGroupsDo
5724 
5725     /**
5726      * Called by the <code>apply</code> method of the class
5727      * <code>GroupsRemovedFromRegistrationLogObj</code> (which is
5728      * invoked during state recovery). This method queues a
5729      * <code>RemoveGroupsTask</code> which performs the actual removal.
5730      * <p>
5731      * @param registrationID   the ID of the data structure record
5732      *                         corresponding to the registration from whose
5733      *                         managed set of groups the input set of groups
5734      *                         is to be removed
5735      * @param registrationByID the map containing all active registrations
5736      *                         managed by this service
5737      * @param groups           a <code>String</code> array, none of whose 
5738      *                         elements may be null, consisting of the group
5739      *                         names to remove from the registration's managed
5740      *                         set of groups
5741      * 
5742      * @see org.apache.river.fiddler.proxy.FiddlerImpl#removeGroupsDo
5743      * @see org.apache.river.fiddler.proxy.FiddlerImpl#removeGroups
5744      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#removeGroups
5745      * @see net.jini.discovery.LookupDiscoveryRegistration#removeGroups
5746      */
5747     private void removeGroupsDo(Uuid registrationID,
5748                                 HashMap registrationByID,
5749                                 String[] groups)
5750     {
5751         removeGroupsDo
5752                  ((RegistrationInfo)(registrationByID.get(registrationID)),
5753                    groups);
5754     }//end removeGroupsDo
5755 
5756     /** Builds a set containing the groups from all registrations. 
5757      *
5758      *  Iterates through all the active registrations, retrieving the groups
5759      *  to discover for each registration (minus duplicates). If at least one
5760      *  registration has requested that all groups be discovered, stops
5761      *  iterating (because the set must be ALL_GROUPS) and returns null.
5762      */
5763     private String[] getGroupsFromAllRegs() {
5764         HashSet  groupSet = new HashSet();
5765         for(Iterator itr=registrationByID.values().iterator();itr.hasNext();) {
5766             RegistrationInfo rInfo = (RegistrationInfo)itr.next();
5767             if(rInfo.groups == null) {
5768                 return DiscoveryGroupManagement.ALL_GROUPS;
5769             } else {
5770                 groupSet.addAll(rInfo.groups);
5771             }//end if
5772         }//end loop
5773         return (String[])groupSet.toArray(new String[groupSet.size()]);
5774     }//end getGroupsFromAllRegs
5775 
5776     /** This method returns a registrar-to-data-structure map in which each
5777      *  registrar key in the returned map is one of the keys from the
5778      *  the global map <code>allDiscoveredRegs</code>, and the corresponding 
5779      *  value is the (locator,groups) pair that corresponds to that registrar
5780      *  key in <code>allDiscoveredRegs</code>
5781      *  
5782      *  An element of <code>allDiscoveredRegs</code> is selected to have
5783      *  its registrar and associated (locator,groups) pair be included in the
5784      *  returned mapping if and only if the key value of the element is a
5785      *  registrar that belongs to at least one of the desired groups of the
5786      *  given registration (<code>regInfo</code> parameter). That is, the
5787      *  registrars referenced in the returned mapping are the registrars that
5788      *  are of interest - through group discovery - to the given registration.
5789      *
5790      *  Note that this method must be called from within a synchronization
5791      *  block.
5792      *
5793      * @param regInfo the data structure record corresponding to the
5794      *                registration whose groups-to-discover will be used to
5795      *                select the elements from <code>allDiscoveredRegs</code>
5796      *                to include in the return mapping
5797      * 
5798      *  @return a mapping in which the key values are registrars that,
5799      *          in addition to being elements of the global map
5800      *          <code>allDiscoveredRegs</code>, also belong to at least one
5801      *          of the desired groups referenced in the <code>regInfo</code>
5802      *          parameter; and whose map values are data structures of type
5803      *          <code>LocatorGroupsStruct</code> that contain the associated
5804      *          locator and member groups of the corresponding registrar key
5805      */
5806     private Map getDesiredRegsByGroup(RegistrationInfo regInfo) {
5807         Set<String> desiredGroups = regInfo.groups;
5808         HashMap desiredRegMap = new HashMap(allDiscoveredRegs.size());
5809         Set eSet = allDiscoveredRegs.entrySet();
5810         for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
5811             Map.Entry pair = (Map.Entry)itr.next();
5812             LocatorGroupsStruct locGroupsStruct
5813                                        = (LocatorGroupsStruct)pair.getValue();
5814             if( interested(locGroupsStruct.groups,desiredGroups) ) {
5815                 desiredRegMap.put((ServiceRegistrar)pair.getKey(),
5816                                   locGroupsStruct);
5817             }//endif
5818         }//end loop
5819         return desiredRegMap;
5820     }//end getDesiredRegsByGroup
5821 
5822     /** Modifies the discovery manager's managed set of groups in the
5823      *  appropriate way; that is, adds to the discovery manager's managed
5824      *  set of groups, any new groups-of-interest, across all registrations,
5825      *  and removes any groups that are no longer of interest to any of the
5826      *  registrations. This method is typically called after the groups of
5827      *  a particular registration have been modified.
5828      *
5829      *  Note that this method must be called from within a synchronization
5830      *  block.
5831      */
5832     private void updateDiscoveryMgrGroups() {
5833         /* Build the set containing the groups from all registrations. */
5834         String[] groupsAcrossAllRegs = getGroupsFromAllRegs();
5835         /* Update the discovery manager's set of groups to discover. Let
5836          * the manager sort out duplicates and any already-discovered groups.
5837          */
5838         try {
5839             discoveryMgr.setGroups( groupsAcrossAllRegs );
5840         } catch (IOException e) {
5841             String warnStr = "IOException: on call to setGroups() "
5842                              +"method of discovery manager";
5843             problemLogger.log(Level.INFO, warnStr, e);
5844             Entry[] warnAttrs = new Entry[]
5845                                       { new FiddlerStatus(StatusType.WARNING),
5846                                         new Comment(warnStr)
5847                                       };
5848             joinMgr.addAttributes(warnAttrs,true);
5849         } catch(IllegalStateException e) {
5850             String errStr = "IllegalStateException: discovery "
5851                             +"manager's setGroups() method was called "
5852                             +"after the manager was terminated";
5853             problemLogger.log(Level.INFO, errStr, e);
5854             Entry[] errorAttrs = new Entry[]
5855                                         { new FiddlerStatus(StatusType.ERROR),
5856                                           new Comment(errStr)
5857                                         };
5858             joinMgr.addAttributes(errorAttrs,true);
5859             throw new IllegalStateException(" discovery manager's "
5860                                             +"setGroups() method was "
5861                                             +"called after the manager "
5862                                             +"was terminated");
5863         }//end try
5864     }//end updateDiscoveryMgrGroups
5865     /* END Private Group Management Methods -------------------------------- */
5866 
5867     /* BEGIN Private Locator Management Methods ---------------------------- */
5868     /**
5869      * Called by the public method <code>addLocators</code>. This method
5870      * queues an <code>AddLocatorsTask</code> which performs the actual
5871      * augmentation of the given registration's desired locators.
5872      * 
5873      * @param regInfo  the data structure record corresponding to the
5874      *                 registration whose managed set of locators is to be
5875      *                 augmented
5876      * @param locators an array, none of whose elements may be null,
5877      *                 consisting of the <code>LookupLocator</code> objects
5878      *                 with which to augment the registration's managed set
5879      *                 of locators.
5880      * 
5881      * @see org.apache.river.fiddler.proxy.FiddlerImpl#addLocators
5882      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#addLocators
5883      * @see net.jini.discovery.LookupDiscoveryRegistration#addLocators
5884      */
5885     private void addLocatorsDo(RegistrationInfo regInfo,
5886                                LookupLocator[]  locators)
5887     {
5888         executorService.execute(new AddLocatorsTask(regInfo,locators));
5889     }//end addLocatorsDo
5890 
5891     /**
5892      * Called by the <code>apply</code> method of the class
5893      * <code>LocsAddedToRegistrationLogObj</code> (which is invoked 
5894      * during state recovery). This method queues an
5895      * <code>AddLocatorsTask</code> which performs the actual augmentation
5896      * of the given registration's desired locators.
5897      * <p>
5898      * @param registrationID   the ID of the data structure record
5899      *                         corresponding to the registration whose
5900      *                         managed set of locators is to be augmented
5901      * @param registrationByID the map containing all active registrations
5902      *                         managed by this service
5903      * @param locators         an array, none of whose elements may be null,
5904      *                         consisting of the <code>LookupLocator</code>
5905      *                         objects with which to augment the
5906      *                         registration's managed set of locators.
5907      * 
5908      * @see org.apache.river.fiddler.proxy.FiddlerImpl#addLocatorsDo
5909      * @see org.apache.river.fiddler.proxy.FiddlerImpl#addLocators
5910      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#addLocators
5911      * @see net.jini.discovery.LookupDiscoveryRegistration#addLocators
5912      */
5913     private void addLocatorsDo(Uuid registrationID,
5914                                HashMap registrationByID,
5915                                LookupLocator[] locators)
5916     {
5917         addLocatorsDo((RegistrationInfo)(registrationByID.get(registrationID)),
5918                       locators);
5919     }//end addLocatorsDo
5920 
5921     /**
5922      * Called by the public method <code>setLocators</code>. This method
5923      * queues a <code>SetLocatorsTask</code> which performs the 
5924      * actual replacement.
5925      * 
5926      * @param regInfo  the data structure record corresponding to the
5927      *                 registration whose managed set of locators is to be
5928      *                 replaced
5929      * @param locators an array, none of whose elements may be null,
5930      *                 consisting of the <code>LookupLocator</code> objects
5931      *                 with which to replace the registration's managed set
5932      *                 of locators.
5933      * 
5934      * @see org.apache.river.fiddler.proxy.FiddlerImpl#setLocators
5935      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#setLocators
5936      * @see net.jini.discovery.LookupDiscoveryRegistration#setLocators
5937      */
5938     private void setLocatorsDo(RegistrationInfo regInfo,
5939                                LookupLocator[]  locators)
5940     {
5941         executorService.execute(new SetLocatorsTask(regInfo,locators));
5942     }//end setLocatorsDo
5943 
5944     /**
5945      * Called by the <code>apply</code> method of the class
5946      * <code>LocsSetInRegistrationLogObj</code> (which is invoked 
5947      * during state recovery). This method queues a
5948      * <code>SetLocatorsTask</code> which performs the actual replacement.
5949      * <p>
5950      * @param registrationID   the ID of the data structure record
5951      *                         corresponding to the registration whose
5952      *                         managed set of locators is to be replaced
5953      * @param registrationByID the map containing all active registrations
5954      *                         managed by this service
5955      * @param locators         an array, none of whose elements may be null,
5956      *                         consisting of the <code>LookupLocator</code>
5957      *                         objects with which to replace the
5958      *                         registration's managed set of locators.
5959      *
5960      * @see org.apache.river.fiddler.proxy.FiddlerImpl#setLocatorsDo
5961      * @see org.apache.river.fiddler.proxy.FiddlerImpl#setLocators
5962      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#setLocators
5963      * @see net.jini.discovery.LookupDiscoveryRegistration#setLocators
5964      */
5965     private void setLocatorsDo(Uuid registrationID,
5966                                HashMap registrationByID,
5967                                LookupLocator[] locators)
5968     {
5969         setLocatorsDo((RegistrationInfo)(registrationByID.get(registrationID)),
5970                       locators);
5971     }//end setLocatorsDo
5972 
5973     /**
5974      * Called by the public method <code>removeLocators</code>. This method
5975      * queues a <code>RemoveLocatorsTask</code> which performs the 
5976      * actual removal.
5977      * 
5978      * @param regInfo  the data structure record corresponding to the
5979      *                 registration from whose managed set of locators the
5980      *                 input set of locators is to be removed
5981      * @param locators an array, none of whose elements may be null,
5982      *                 consisting of the <code>LookupLocator</code> objects
5983      *                 to remove from the registration's managed set of 
5984      *                 locators.
5985      *
5986      * @see org.apache.river.fiddler.proxy.FiddlerImpl#removeLocators
5987      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#removeLocators
5988      * @see net.jini.discovery.LookupDiscoveryRegistration#removeLocators
5989      */
5990     private void removeLocatorsDo(RegistrationInfo regInfo,
5991                                   LookupLocator[]  locators)
5992     {
5993         executorService.execute(new RemoveLocatorsTask(regInfo,locators));
5994     }//end removeLocatorsDo
5995 
5996     /**
5997      * Called by the <code>apply</code> method of the class
5998      * <code>LocsRemovedFromRegistrationLogObj</code> (which is
5999      * invoked during state recovery). This method queues a
6000      * <code>RemoveLocatorsTask</code> which performs the actual removal.
6001      * <p>
6002      * @param registrationID   the ID of the data structure record
6003      *                         corresponding to the registration from whose
6004      *                         managed set of groups the input set of locators
6005      *                         is to be removed
6006      * @param registrationByID the map containing all active registrations
6007      *                         managed by this service
6008      * @param locators         an array, none of whose elements may be null,
6009      *                         consisting of the <code>LookupLocator</code>
6010      *                         objects to remove from the registration's
6011      *                         managed set of locators.
6012      *
6013      * @see org.apache.river.fiddler.proxy.FiddlerImpl#removeLocatorsDo
6014      * @see org.apache.river.fiddler.proxy.FiddlerImpl#removeLocators
6015      * @see org.apache.river.fiddler.proxy.FiddlerRegistration#removeLocators
6016      * @see net.jini.discovery.LookupDiscoveryRegistration#removeLocators
6017      */
6018     private void removeLocatorsDo(Uuid registrationID,
6019                                   HashMap registrationByID,
6020                                   LookupLocator[] locators)
6021     {
6022         removeLocatorsDo
6023                    ((RegistrationInfo)(registrationByID.get(registrationID)),
6024                     locators);
6025     }//end removeLocatorsDo
6026 
6027     /** Builds a set containing the locators from all registrations. 
6028      *
6029      *  Iterates through all the active registrations, retrieving the locators
6030      *  to discover for each registration (minus duplicates).
6031      */
6032     private LookupLocator[] getLocatorsFromAllRegs() {
6033         HashSet locatorSet = new HashSet();
6034         for(Iterator itr=registrationByID.values().iterator();itr.hasNext();) {
6035             RegistrationInfo rInfo = (RegistrationInfo)itr.next();
6036             if(rInfo.locators == null) {
6037                 throw new AssertionError
6038                             ("registration contains a null set of locators");
6039             } else {
6040                 locatorSet.addAll(rInfo.locators);
6041             }//end if
6042         }//end loop
6043         return (LookupLocator[])locatorSet.toArray
6044                                         (new LookupLocator[locatorSet.size()]);
6045     }//end getLocatorsFromAllRegs
6046 
6047     /** This method returns a registrar-to-data-structure map in which each
6048      *  registrar key in the returned map is one of the keys from the
6049      *  the global map <code>allDiscoveredRegs</code>, and the corresponding 
6050      *  value is the (locator,groups) pair that corresponds to that registrar
6051      *  key in <code>allDiscoveredRegs</code>
6052      *  
6053      *  An element of <code>allDiscoveredRegs</code> is selected to have
6054      *  its registrar and associated (locator,groups) pair be included in the
6055      *  returned mapping if and only if the key value under consideration is
6056      *  a registrar whose locator equals one of the desired locators of the
6057      *  given registration (<code>regInfo</code> parameter). That is, the
6058      *  registrars referenced in the returned mapping are the registrars that
6059      *  are of interest - through locator discovery - to the given
6060      *  registration.
6061      *
6062      * @param regInfo the data structure record corresponding to the
6063      *                registration whose locators-to-discover will be used to
6064      *                select the elements from <code>allDiscoveredRegs</code>
6065      *                to include in the return mapping
6066      * 
6067      *  @return a mapping in which the key values are registrars that,
6068      *          in addition to being elements of the global map
6069      *          <code>allDiscoveredRegs</code>, have locators that are
6070      *          elements of the set of desired locators referenced in the
6071      *          <code>regInfo</code> parameter; and whose map values are
6072      *          data structures of type <code>LocatorGroupsStruct</code> that
6073      *          contain the associated locator and member groups of the
6074      *          corresponding registrar key
6075      */
6076     private Map getDesiredRegsByLocator(RegistrationInfo regInfo) {
6077         Set<LookupLocator> desiredLocators = regInfo.locators;
6078         HashMap desiredRegMap = new HashMap(allDiscoveredRegs.size());
6079         Set eSet = allDiscoveredRegs.entrySet();
6080         for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
6081             Map.Entry pair = (Map.Entry)itr.next();
6082             LocatorGroupsStruct locGroupsStruct
6083                                       = (LocatorGroupsStruct)pair.getValue();
6084             if(locSetContainsLoc(desiredLocators,locGroupsStruct.locator)) {
6085                 desiredRegMap.put((ServiceRegistrar)pair.getKey(),
6086                                   locGroupsStruct);
6087             }//endif
6088         }//end loop
6089         return desiredRegMap;
6090     }//end getDesiredRegsByLocator
6091 
6092     /** Modifies the discovery manager's managed set of locators in the
6093      *  appropriate way; that is, adds to the discovery manager's managed
6094      *  set of locators, any new locators-of-interest, across all
6095      *  registrations, and removes any locators that are no longer of
6096      *  interest to any of the registrations. This method is typically
6097      *  called after the locators of a particular registration have been
6098      *  modified.
6099      *
6100      *  Note that this method must be called from within a synchronization
6101      *  block.
6102      */
6103     private void updateDiscoveryMgrLocators() {
6104         /* Build the set containing the locators from all registrations. */
6105         LookupLocator[] locsAcrossAllRegs = getLocatorsFromAllRegs();
6106         /* Update the discovery manager's set of locators to discover. Let
6107          * the manager sort out duplicates and any already-discovered locators.
6108          */
6109         try {
6110             discoveryMgr.setLocators( locsAcrossAllRegs );
6111         } catch(IllegalStateException e) {
6112             String errStr = "IllegalStateException: discovery "
6113                             +"manager's setLocators() method was called "
6114                             +"after the manager was terminated";
6115             problemLogger.log(Level.INFO, errStr, e);
6116             Entry[] errorAttrs = new Entry[]
6117                                         { new FiddlerStatus(StatusType.ERROR),
6118                                           new Comment(errStr)
6119                                         };
6120             joinMgr.addAttributes(errorAttrs,true);
6121             throw new IllegalStateException(" discovery manager's "
6122                                             +"setLocators() method was "
6123                                             +"called after the manager "
6124                                             +"was terminated");
6125         }//end try
6126     }//end updateDiscoveryMgrLocators
6127     /* END Private Locator Management Methods ------------------------------ */
6128 
6129     /* BEGIN Private Lease Renewal Methods --------------------------------- */
6130     /**
6131      * Called by the public method <code>renewLease</code>. This method renews
6132      * the lease corresponding to the <code>registrationID</code> and 
6133      * <code>leaseID</code> parameters, granting a new duration that is
6134      * less than or equal to the requested duration value contained in
6135      * the <code>duration</code> parameter.
6136      * 
6137      * @param regInfo  the data structure record corresponding to the
6138      *                 registration whose lease is to be renewed
6139      * @param leaseID  identifier assigned by the lease grantor to the lease
6140      *                 that is to be renewed
6141      * @param duration the requested duration for the lease being renewed
6142      *
6143      * @throws net.jini.core.lease.UnknownLeaseException this exception occurs
6144      *         when the lease being renewed is unknown to the lease grantor.
6145      *
6146      * @return <code>long</code> value representing the actual duration that
6147      *         was granted for the renewed lease. Note that the actual
6148      *         duration granted and returned by this method may be less than
6149      *         the duration requested.
6150      *
6151      * @see org.apache.river.fiddler.proxy.FiddlerImpl#renewLease
6152      */
6153     private long renewLeaseDo(RegistrationInfo regInfo,
6154                               Uuid leaseID,
6155                               long duration) throws UnknownLeaseException
6156     {
6157         long curTime       = System.currentTimeMillis();
6158         long newExpiration = renewLeaseInt(regInfo,leaseID,duration,curTime);
6159         addLogRecord(new LeaseRenewedLogObj
6160                            (regInfo.registrationID, leaseID, newExpiration));
6161         return (newExpiration - curTime);
6162     }//end renewLeaseDo
6163 
6164     /**
6165      * Called by the method <code>renewLeaseDo</code>. This method takes
6166      * a requested duration for the lease corresponding to the
6167      * <code>registrationID</code> and <code>leaseID</code> parameters,
6168      * converts that duration to an absolute expiration (based on the local
6169      * clock) with the appropriate bound applied, and attempts to renew 
6170      * the lease, granting the new expiration time on the lease.
6171      * 
6172      * @param regInfo  the data structure record corresponding to the
6173      *                 registration whose lease is to be renewed
6174      * @param leaseID  identifier assigned by the lease grantor to the lease
6175      *                 that is to be renewed
6176      * @param duration the requested duration for the lease being renewed
6177      *
6178      * @return <code>long</code> value representing the new absolute
6179      *         expiration time granted for the renewed lease.
6180      *
6181      * @throws net.jini.core.lease.UnknownLeaseException this exception occurs
6182      *         when the lease being renewed is unknown to the lease grantor.
6183      *
6184      * @see org.apache.river.fiddler.proxy.FiddlerImpl#renewLeaseDo
6185      */
6186     private long renewLeaseInt(RegistrationInfo regInfo,
6187                                Uuid leaseID,
6188                                long duration,
6189                                long curTime)  throws UnknownLeaseException
6190     {
6191         if(duration == Lease.ANY) {
6192             duration = leaseBound;
6193         } else if(duration <= 0) {
6194             throw new IllegalArgumentException("non-positive lease duration");
6195         }//endif
6196         if (    (regInfo == null) 
6197              || ( !(regInfo.leaseID).equals(leaseID) )
6198              || (regInfo.leaseExpiration <= curTime) ) {
6199             throw new UnknownLeaseException();
6200         }//endif
6201         if(    (duration > leaseBound)
6202 	    && (duration > (regInfo.leaseExpiration - curTime)) ) {
6203             duration = Math.max( (regInfo.leaseExpiration - curTime),
6204                                  leaseBound );
6205         }//endif
6206         long newExpiration = curTime + duration;
6207         /* Force a re-sort in the registrationByTime map */
6208         registrationByTime.remove(regInfo);       // force first re-sort
6209         regInfo.leaseExpiration = newExpiration;  // modify outside of map
6210         registrationByTime.put(regInfo, regInfo); // insert & force new sort
6211         /* see if the expire thread needs to wake up earlier */
6212         if (newExpiration < minExpiration) {
6213             minExpiration = newExpiration;
6214             leaseExpireThreadSyncObj.signal();
6215         }//endif
6216         return newExpiration;
6217     }//end renewLeaseInt
6218 
6219     /**
6220      * This method performs the final steps in the process of renewing the
6221      * lease on the registration corresponding to the <code>regInfo</code>
6222      * and <code>leaseID</code> parameters, granting a requested absolute
6223      * expiration time for that lease.
6224      * 
6225      * @param regInfo    the data structure record corresponding to the
6226      *                   registration whose lease is to be renewed
6227      * @param leaseID    identifier assigned by the lease grantor to the lease
6228      *                   that is to be renewed
6229      * @param expiration the requested absolute expiration time for the lease
6230      *                   being renewed
6231      */
6232     private void renewLeaseAbs(RegistrationInfo regInfo,
6233                                Uuid leaseID,
6234                                long expiration)
6235     {
6236         if ( (regInfo == null) || (regInfo.leaseID != leaseID) ) return;
6237         /* The act of renewing a registration's lease simply involves 
6238          * changing the expiration time stored in the registration's
6239          * data structure. But because the registrationByTime sorts its
6240          * elements by time, changing the value of the leaseExpiration field
6241          * of the regInfo parameter "in place" would typically result in 
6242          * the registrationByTime map being out of order. Contrast this with
6243          * the modification of the registration's non-time-dependent fields
6244          * (for example, adding, replacing or removing groups or locators),
6245          * where modifying the input regInfo parameter will modify the
6246          * appropriate field of the corresponding element of both the
6247          * registrationByID and registrationByTime maps; and the order 
6248          * will still be valid in the registrationByTime map because the
6249          * time-dependent data has not changed.
6250          * 
6251          * Thus, in order to maintain a valid ordering in the 
6252          * registrationByTime map, a re-sort is forced by first removing
6253          * from that map the element corresponding to regInfo, resetting
6254          * the leaseExpiration field of that object, and then re-inserting
6255          * the modified regInfo into the map, which forces another sort.
6256          */
6257         registrationByTime.remove(regInfo);       // force first re-sort
6258         regInfo.leaseExpiration = expiration;     // modify outside of map
6259         registrationByTime.put(regInfo, regInfo); // insert & force new sort
6260     }//end renewLeaseAbs
6261 
6262     /**
6263      * Called by the <code>apply</code> method of the class
6264      * <code>LeaseRenewedLogObj</code> (which is invoked during state
6265      * recovery). This method performs the final steps in the process
6266      * of renewing the lease on the registration corresponding to the
6267      * <code>registrationID</code> and <code>leaseID</code> parameters,
6268      * granting a requested absolute expiration time for that lease.
6269      * 
6270      * @param registrationID   the ID of the data structure record
6271      *                         corresponding to the registration whose
6272      *                         lease is to be renewed
6273      * @param registrationByID the map containing all active registrations
6274      *                         managed by this service
6275      * @param leaseID          identifier assigned by the lease grantor to
6276      *                         the lease that is to be renewed
6277      * @param expiration       the requested absolute expiration time for the
6278      *                         lease being renewed
6279      *
6280      * @see org.apache.river.fiddler.proxy.FiddlerImpl#renewLeaseDo
6281      */
6282     private void renewLeaseAbs(Uuid registrationID,
6283                                HashMap registrationByID,
6284                                Uuid leaseID,
6285                                long expiration)
6286     {
6287         renewLeaseAbs((RegistrationInfo)(registrationByID.get(registrationID)),
6288                       leaseID,expiration);
6289     }//end renewLeaseAbs
6290 
6291     /**
6292      * Called by the public method renewLeases to renew all of
6293      * the leases from a <code>LeaseMap</code> that correspond to the
6294      * elements of the <code>registrationIDs</code> and <code>leaseIDs</code>
6295      * parameters, granting as new durations the corresponding elements
6296      * of the <code>duration</code> parameter.
6297      * <p>
6298      * Note that all of the input parameters must be the same length.
6299      *
6300      * @param registrationIDs array containing the unique identifiers assigned
6301      *                        to the each registration to which each lease 
6302      *                        to be renewed corresponds
6303      * @param leaseIDs        array containing the identifiers assigned by the
6304      *                        lease grantor to each lease being renewed
6305      * @param durations       array containing the requested durations for 
6306      *                        each lease being renewed
6307      * 
6308      * @return an instance of FiddlerRenewResults containing data corresponding
6309      *         to the results (granted durations or exceptions) of each
6310      *         renewal attempt
6311      *
6312      * @see org.apache.river.fiddler.proxy.FiddlerImpl#renewLeases
6313      */
6314     private FiddlerRenewResults renewLeasesDo(Uuid[] registrationIDs,
6315 				              Uuid[] leaseIDs,
6316 				              long[] durations)
6317     {
6318         long curTime = System.currentTimeMillis();
6319         Exception[] exceptions = null;
6320         for (int i = 0; i < registrationIDs.length; i++) {
6321             RegistrationInfo regInfo
6322                 = (RegistrationInfo)(registrationByID.get(registrationIDs[i]));
6323             try {
6324                 durations[i] = renewLeaseInt(regInfo, leaseIDs[i],
6325                                              durations[i], curTime);
6326             } catch (Exception e) {
6327                 durations[i] = -1;
6328                 if (exceptions == null) {
6329                     exceptions = new Exception[]{e};
6330                 } else {
6331                     exceptions = (Exception[])appendArray(exceptions, e);
6332                 }//endif
6333             }
6334 	}//end loop
6335         /* don't bother to weed out problem leases */
6336         addLogRecord
6337              ( new LeasesRenewedLogObj(registrationIDs,leaseIDs,durations) );
6338         for (int i = registrationIDs.length; --i >= 0; ) {
6339             if (durations[i] >= 0) {
6340                 durations[i] -= curTime;
6341             }//endif
6342         }//end loop
6343         return new FiddlerRenewResults(durations, exceptions);
6344     }//end renewLeasesDo
6345 
6346     /**
6347      * Using the absolute expiration times contained in the
6348      * <code>expirations</code> parameter, renews all of the leases 
6349      * from a <code>LeaseMap</code> that correspond to the elements 
6350      * of the <code>registrationIDs</code> and <code>leaseIDs</code>
6351      * parameters; skipping any negative expiration times.
6352      * <p>
6353      * Note that all of the input parameters must be the same length.
6354      *
6355      * @param registrationIDs array containing the unique identifiers assigned
6356      *                        to the each registration to which each lease 
6357      *                        to be renewed corresponds
6358      * @param leaseIDs        array containing the identifiers assigned by the
6359      *                        lease grantor to each lease being renewed
6360      * @param expirations     array containing the absolute expiration times
6361      *                        to which to renew each lease
6362      *
6363      * @see org.apache.river.fiddler.proxy.FiddlerImpl#renewLeases
6364      * @see org.apache.river.fiddler.proxy.FiddlerImpl#renewLeaseAbs
6365      */
6366     private void renewLeasesAbs(Uuid[] registrationIDs,
6367                                 Uuid[] leaseIDs,
6368                                 long[] expirations)
6369     {
6370         for (int i = registrationIDs.length; --i >= 0; ) {
6371             long expiration = expirations[i];
6372             if (expiration < 0)  continue;
6373             RegistrationInfo regInfo
6374                 = (RegistrationInfo)(registrationByID.get(registrationIDs[i]));
6375             renewLeaseAbs(regInfo, leaseIDs[i], expiration);
6376         }//end loop
6377     }//end renewLeasesAbs
6378     /* END Private Lease Renewal Methods ----------------------------------- */
6379 
6380     /* BEGIN Private Lease Cancellation Methods ---------------------------- */
6381     /**
6382      * Called by the public method <code>cancelLease</code>. This method
6383      * cancels the lease on the registration corresponding to the 
6384      * <code>regInfo</code> and <code>leaseID</code> parameters.
6385      * 
6386      * @param regInfo the data structure record corresponding to the
6387      *                registration whose lease is to be cancelled
6388      * @param leaseID identifier assigned by the lease grantor to the lease
6389      *                that is to be cancelled
6390      *
6391      * @throws net.jini.core.lease.UnknownLeaseException this exception occurs
6392      *         when the lease being cancelled is unknown to the lease grantor.
6393      * 
6394      * @throws java.io.IOException this exception occurs when the multicast
6395      *         request protocol experiences a failure.
6396      *
6397      * @see org.apache.river.fiddler.proxy.FiddlerImpl#cancelLease
6398      */
6399     private void cancelLeaseDo(RegistrationInfo regInfo, Uuid leaseID)
6400                                      throws UnknownLeaseException, IOException
6401     {
6402         long curTime = System.currentTimeMillis();
6403         if ( (regInfo == null) || (regInfo.leaseExpiration <= curTime) ) {
6404             throw new UnknownLeaseException();
6405         }//endif
6406         removeRegistration(regInfo);
6407         /* wake up thread if this might be the (only) earliest time */
6408         if (regInfo.leaseExpiration == minExpiration) {
6409             leaseExpireThreadSyncObj.signal();
6410         }//endif
6411     }//end cancelLeaseDo
6412 
6413     /**
6414      * Called by the <code>apply</code> method of the class
6415      * <code>LeaseCancelledLogObj</code> (which is invoked during state
6416      * recovery). This method cancels the lease on the registration
6417      * corresponding to the <code>registrationID</code> and
6418      * <code>leaseID</code> parameters.
6419      * 
6420      * @param registrationID   the ID of the data structure record
6421      *                         corresponding to the registration whose
6422      *                         lease is to be cancelled
6423      * @param registrationByID the map containing all active registrations
6424      *                         managed by this service
6425      * @param leaseID          identifier assigned by the lease grantor to
6426      *                         the lease that is to be cancelled
6427      *
6428      * @throws net.jini.core.lease.UnknownLeaseException this exception occurs
6429      *         when the lease being cancelled is unknown to the lease grantor.
6430      * 
6431      * @throws java.io.IOException this exception occurs when the multicast
6432      *         request protocol experiences a failure.
6433      *
6434      * @see org.apache.river.fiddler.proxy.FiddlerImpl#cancelLeaseDo
6435      * @see org.apache.river.fiddler.proxy.FiddlerImpl#cancelLease
6436      */
6437     private void cancelLeaseDo(Uuid registrationID,
6438                                HashMap registrationByID,
6439                                Uuid leaseID)
6440                                      throws UnknownLeaseException, IOException
6441     {
6442         cancelLeaseDo((RegistrationInfo)(registrationByID.get(registrationID)),
6443                       leaseID);
6444     }//end cancelLeaseDo
6445 
6446     /**
6447      * Called by the public method cancelLeases to the cancel all of
6448      * the leases from a <code>LeaseMap</code> that correspond to the
6449      * elements of the <code>registrationIDs</code> and <code>leaseIDs</code>
6450      * parameters.
6451      * <p>
6452      * The two input arrays must be the same length. If there are no
6453      * exceptions, the return value is null. Otherwise, the return value
6454      * has the same length as the <code>registrationIDs</code> parameter,
6455      * and has nulls for the leases that were successfully renewed.
6456      * 
6457      * @return an array containing the instances of Exception (or null for
6458      *         successfully renewed leases), each corresponding to the 
6459      *         exception that occurred upon attempting renewal.
6460      *
6461      * @see org.apache.river.fiddler.proxy.FiddlerImpl#cancelLeases
6462      */
6463     private Exception[] cancelLeasesDo(Uuid[] registrationIDs,
6464                                        Uuid[] leaseIDs)
6465     {
6466         Exception[] exceptions = null;
6467         for (int i = registrationIDs.length; --i >= 0; ) {
6468             try {
6469                 RegistrationInfo regInfo =
6470                      (RegistrationInfo)(registrationByID.get
6471                                                          (registrationIDs[i]));
6472                 cancelLeaseDo(regInfo, leaseIDs[i]);
6473             } catch (Exception e) {
6474                 if (exceptions == null) {
6475                     exceptions = new Exception[registrationIDs.length];
6476                 }//endif
6477                 exceptions[i] = e;
6478             }
6479         }//end loop
6480         return exceptions;
6481     }//end cancelLeasesDo
6482     /* END Private Lease Cancellation Methods ------------------------------ */
6483 
6484     /* BEGIN Private Event-Related Methods --------------------------------- */
6485     /** Returns the set of registrars common to both input parameters */
6486     private ServiceRegistrar[] intersectRegSets(ServiceRegistrar[] regs0,
6487                                                 ServiceRegistrar[] regs1)
6488     {
6489         HashSet regSet = new HashSet(); //no duplicates
6490         /* Compare each input element of regs0 with each element of regs1,
6491          * storing matches along the way
6492          */
6493         nextRegistrar:
6494         for(int i=0;i<regs0.length;i++) {
6495             for(int j=0;j<regs1.length;j++) {
6496                 if( (regs0[i]).equals(regs1[j]) ) {
6497                     regSet.add(regs0[i]);
6498                     continue nextRegistrar; // match found, next reg
6499                 }//endif
6500             }//end loop(j)
6501         }//end loop(i)
6502         return ( (ServiceRegistrar[])(regSet).toArray
6503                                       (new ServiceRegistrar[regSet.size()]) );
6504     }//end intersectRegSets
6505 
6506     /** Returns true if the input registrar is an element of the given array */
6507     private boolean regIsElementOfRegSet(ServiceRegistrar   reg,
6508                                          ServiceRegistrar[] regSet)
6509     {
6510         for(int i=0;i<regSet.length;i++) {
6511             if( (regSet[i]).equals(reg) )  return true;
6512         }//end loop
6513         return false;
6514     }//end regIsElementOfRegSet
6515 
6516     /**
6517      * This method determines which of the registrars in the
6518      * <code>regsMap</code> parameter belong to the set of registrars
6519      * the given <code>regInfo</code> parameter wishes to discover. It
6520      * then adds those registrars to the regInfo's map of discovered
6521      * registrars (including only those registrars that can be successfully
6522      * serialized). Finally, a discovered event containing the discovery
6523      * information that is of interest to the regInfo is built and sent
6524      * to the regInfo's listener.
6525      * 
6526      * @param regInfo the data structure record corresponding to the
6527      *                registration whose listener will receive the event
6528      * @param regsMap mapping in which the key values are previously discovered
6529      *                registrars, and the map values are data structures of
6530      *                type <code>LocatorGroupsStruct</code> that contain the
6531      *                locator and member groups of the corresponding 
6532      *                registrar key
6533      */
6534     private void maybeSendDiscoveredEvent(RegistrationInfo regInfo,
6535                                           Map regsMap)
6536     {
6537         /* Determine the registrars that are common to both the input set of
6538          * registrars (the currently or newly discovered registrars) and the
6539          * registration's set of desired registrars. These are the registrars
6540          * to send in the event.
6541          */
6542         HashMap regsToAdd = new HashMap(regsMap.size());
6543         Set eSet = regsMap.entrySet();
6544         for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
6545             Map.Entry pair = (Map.Entry)itr.next();
6546             ServiceRegistrar reg = (ServiceRegistrar)pair.getKey();
6547             LocatorGroupsStruct curStructVal
6548                                       = (LocatorGroupsStruct)pair.getValue();
6549             /* If regInfo wants ALL_GROUPS, then every registrar is desired */
6550             if(regInfo.groups == null) { // ALL_GROUPS
6551                 regsToAdd.put(reg,curStructVal);
6552             } else {//not ALL_GROUPS, reg's locator/groups desired by regInfo?
6553                 if( interested(curStructVal.locator,curStructVal.groups,
6554                                regInfo.locators,regInfo.groups) )
6555                 {
6556                     regsToAdd.put(reg,curStructVal);
6557 		}//endif
6558 	    }//endif
6559         }//end loop
6560         /* Add the registrars that are both desired and discovered to the
6561          * registration's map of discovered registrars. If any registrars
6562          * cannot be serialized, drop them.
6563          */
6564         Map regsAdded = regInfo.addToDiscoveredRegs(regsToAdd);
6565         /* Build and send a "discovered event" */
6566         RemoteDiscoveryEvent event = buildEvent(regInfo,regsAdded,false);
6567         if(event != null) {
6568             queueEvent(regInfo,event);
6569             logInfoEvents("NewReg/Discovered EventTask.run(): "
6570                           +"DISCOVERED Event was SENT\n");
6571         }//endif
6572     }//end maybeSendDiscoveredEvent
6573 
6574     /** 
6575      * Examines the <code>discardFlag</code> for each active registration
6576      * until a value of <code>true</code> is encountered or the set of
6577      * registrations is exhausted. If that flag is <code>false</code> for
6578      * all registrations, this method will return <code>null</code>;
6579      * otherwise, it will return the registration found, but with the
6580      * the value of the flag set back to <code>false</code>.
6581      *
6582      * @return the first instance of <code>RegistrationInfo</code> whose 
6583      *         <code>discardFlag</code> is set to <code>true</code>;
6584      *         returns <code>null</code> if no such registration exists.
6585      */
6586     private RegistrationInfo externalDiscardRequest() {
6587         /* Loop thru regInfo's, until one is found with a true flag*/
6588         for(Iterator itr=registrationByID.values().iterator();itr.hasNext();) {
6589             RegistrationInfo regInfo = (RegistrationInfo)itr.next();
6590             if(regInfo.discardFlag == true) {
6591                 logInfoDiscard("\nexternalDiscardRequest: "
6592                                +"discardFlag == true\n");
6593                 regInfo.discardFlag = false;
6594                 return regInfo;
6595             }//endif
6596         }//end loop
6597         return null;
6598     }//end externalDiscardRequest
6599 
6600     /**
6601      * Convenience method that creates and returns a mapping of a single
6602      * <code>ServiceRegistrar</code> instance to a set of groups.
6603      * 
6604      * @param reg       instance of <code>ServiceRegistrar</code> 
6605      *                  corresponding to the registrar to use as the key
6606      *                  to the mapping
6607      * @param groups    <code>String</code> array containing the current
6608      *                  member groups of the registrar referenced by the 
6609      *                  <code>reg</code> parameter; and which is used
6610      *                  as the value of the mapping
6611      *
6612      * @return <code>Map</code> instance containing a single mapping from
6613      *         a given registrar to its current member groups
6614      */
6615     private HashMap mapRegToGroups(ServiceRegistrar reg, String[] groups) {
6616         HashMap groupsMap = new HashMap(1);
6617         groupsMap.put(reg,groups);
6618         return groupsMap;
6619     }//end mapRegToGroups
6620 
6621     /** Given a set of registrars that have just been discarded, this method
6622      *  determines which of those registrars are contained in none of the
6623      *  discovered sets of the active registrations; and then removes
6624      *  those registrars from the global maps of registrars that are
6625      *  maintained across all registrations.
6626      *
6627      *  Note that this method must be called from within a synchronization
6628      *  block.
6629      *
6630      * @param discardedRegs set of registrars that were just discarded
6631      */
6632     private void maybeRemoveDiscardedRegsFromGlobalSet(Set discardedRegs) {
6633         for(Iterator itr = discardedRegs.iterator();itr.hasNext(); ) {
6634             maybeRemoveDiscardedRegFromGlobalSet(itr.next());
6635         }//end loop(itr)
6636     }//end maybeRemoveDiscardedRegsFromGlobalSet
6637 
6638     /** Given a registrar that has just been discarded, this method
6639      *  determines if that registrar is contained in none of the discovered
6640      *  sets of the active registrations; and then removes that registrar
6641      *  from the global maps of registrars that are maintained across all
6642      *  registrations.
6643      *
6644      *  Note that this method must be called from within a synchronization
6645      *  block.
6646      *
6647      * @param dReg set of registrars that were just discarded
6648      */
6649     private void maybeRemoveDiscardedRegFromGlobalSet(Object dReg) {
6650         for(Iterator jtr=registrationByID.values().iterator();jtr.hasNext();){
6651             RegistrationInfo regInfo = (RegistrationInfo)jtr.next();
6652             if( (regInfo.discoveredRegsMap).containsKey(dReg) ) {
6653                 return; // this dReg is in at least 1 regInfo, goto next reg
6654             }//endif
6655         }//end loop(jtr)
6656         /* discarded reg contained in no regInfo, remove it from map */
6657         allDiscoveredRegs.remove(dReg);
6658     }//end maybeRemoveDiscardedRegFromGlobalSet
6659 
6660     /** For each registrar referenced in the global map allDiscoveredRegs,
6661      *  this method replaces the associated set of member groups with the
6662      *  corresponding set in the given registrars-to-groups map. This
6663      *  method should be called only after a changed event is received 
6664      *  from the discovery manager.
6665      *
6666      *  Note that this method must be called from within a synchronization
6667      *  block.
6668      *
6669      * @param groupsMap map containing the registrars and their new groups
6670      */
6671     private void updateGroupsInGlobalSet(Map groupsMap) {
6672         Set eSet = groupsMap.entrySet();
6673         for(Iterator itr = eSet.iterator(); itr.hasNext(); ) {
6674             Map.Entry pair = (Map.Entry)itr.next();
6675             ServiceRegistrar reg = (ServiceRegistrar)pair.getKey();
6676             if(allDiscoveredRegs.containsKey(reg)) {
6677                 LookupLocator loc
6678                   = ((LocatorGroupsStruct)allDiscoveredRegs.get(reg)).locator;
6679                 String[] newGroups = (String[])pair.getValue();
6680                 LocatorGroupsStruct locGroups
6681                                      = new LocatorGroupsStruct(loc,newGroups);
6682                 allDiscoveredRegs.put(reg,locGroups);
6683             }//endif
6684         }//end loop
6685     }//end updateGroupsInGlobalSet
6686 
6687     /**
6688      * This method constructs the appropriate remote discovery event from the
6689      * information contained in the input parameters. This method encapsulates
6690      * common exception-handling functionality. If the remote discovery
6691      * event cannot be successfully constructed, null will be returned.
6692      * 
6693      * @param regInfo   the data structure record corresponding to the 
6694      *                  registration whose listener will receive the event
6695      * @param groupsMap map containing the registrars, and their corresponding
6696      *                  member groups, to include in the event
6697      * @param discarded flag indicating whether the registrars included in
6698      *                  the event have been discarded or discovered
6699      *
6700      * @return instance of <code>RemoteDiscoveryEvent</code> containing the
6701      *         appropriate information corresponding to the input registration
6702      *         record
6703      */
6704     private RemoteDiscoveryEvent buildEvent(RegistrationInfo regInfo,
6705                                             Map<ServiceRegistrar,String[]> groupsMap,
6706                                             boolean discarded)
6707     {
6708         RemoteDiscoveryEvent newEvent = null;
6709         if(groupsMap.size() > 0) {
6710             try {
6711                 newEvent = new RemoteDiscoveryEvent(outerProxy,
6712                                                     regInfo.eventID,
6713                                                     ++regInfo.seqNum,
6714                                                     regInfo.handback,
6715                                                     discarded,
6716                                                     groupsMap);
6717                 logInfoEvents(groupsMap,regInfo.eventID,regInfo.seqNum,
6718                               regInfo.handback,discarded,
6719                               eventsLogger, Level.FINE);
6720             } catch (IOException e) {
6721                 /* The constructor for <code>RemoteDiscoveryEvent</code>
6722                  * throws an <code>IOException</code> if the constructor
6723                  * fails to successfully serialize even one registrar from
6724                  * the input set (which means there is no need to send the
6725                  * event since there are no marshalled registrars to send).
6726                  * When such a situation occurs, register an ERROR status
6727                  * attribute (along with a Comment attribute describing the
6728                  * nature of the problem) to all lookup services with which
6729                  * this service is registered. 
6730                  *
6731                  * Administrative clients, as well as clients that use this 
6732                  * service should have registered for notification of the 
6733                  * existence of this attribute.
6734                  */
6735                 String eStr = "Failed to serialize ALL registrars during "
6736                               +"event construction ... could not send event";
6737                 problemLogger.log(Level.INFO, eStr, e);
6738                 Entry[] errorAttrs =
6739                 new Entry[] { new FiddlerStatus(StatusType.WARNING),
6740                               new Comment(eStr)
6741                             };
6742                 joinMgr.addAttributes(errorAttrs,true);
6743             }//end try
6744         }//endif
6745         return newEvent;
6746     }//end buildEvent
6747 
6748     /**
6749      * This method simply queues a new <code>SendEventTask</code> instance
6750      * that will send the given remote event to the given registration's
6751      * listener.
6752      * 
6753      * @param regInfo the data structure record corresponding to the 
6754      *                registration whose listener will receive the event
6755      * 
6756      * @param event   the instance of <code>RemoteDiscoveryEvent</code> to
6757      *                send to the registration's listener
6758      */
6759     private void queueEvent(RegistrationInfo regInfo,
6760                            RemoteDiscoveryEvent event)
6761     {
6762         executorService.execute(
6763 		Security.withContext(new SendEventTask(regInfo,event), context)
6764 	);
6765     }//end queueEvent
6766     /* END Private Event-Related Methods ----------------------------------- */
6767 
6768     /* BEGIN Persistent State Logging Interfaces & Classes ----------------- */
6769     /**
6770      * Writes the current state of this service to persistent storage.
6771      * <p>
6772      * A 'snapshot' of the service's current state is represented by
6773      * the data contained in certain fields defined in the service. 
6774      * That data represents many changes -- over time -- to the service's
6775      * state. This method will record that data to a file referred to as
6776      * the snapshot file.
6777      * <p>
6778      * The data written by this method to the snapshot file -- as well as
6779      * the format of the file -- is shown below:
6780      * <ul>
6781      * <li> the service's class name
6782      * <li> log format version number
6783      * <li> the unique ID of the proxy to the current instance of this service
6784      * <li> the current event ID
6785      * <li> the current join state of this service
6786      * <li> the current configuration parameters of this service
6787      * <li> contents of the container holding the current set of registrations
6788      * <li> null (termination 'marker' for the set of registrations)
6789      * </ul>
6790      * Each data item is written to the snapshot file in serialized form.
6791      * 
6792      * @see FiddlerImpl.LocalLogHandler
6793      */
6794     private void takeSnapshot(OutputStream  out) throws IOException {
6795         ObjectOutputStream stream = new ObjectOutputStream(out);
6796         /* Stable information about the current instance of this service */
6797         stream.writeUTF(FiddlerImpl.class.getName());
6798         stream.writeInt(LOG_VERSION);
6799         stream.writeObject(proxyID);
6800         /* The current event ID assigned to the discovery events being sent */
6801         stream.writeLong(curEventID);
6802         /* The current join state of this service */
6803         stream.writeObject(thisServicesGroups);
6804         stream.writeObject(thisServicesLocators);
6805         stream.writeObject( marshalAttributes(this,thisServicesAttrs) );
6806         /* The current configuration parameters of this service */
6807         stream.writeLong(leaseBound);
6808         stream.writeInt(snapshotThresh);
6809         stream.writeFloat(snapshotWt);
6810         /* The current set of registrations */
6811         for(Iterator iter=registrationByID.values().iterator();iter.hasNext();)
6812         {
6813             stream.writeObject(iter.next());
6814         }//end loop
6815         stream.writeObject(null); //termination marker
6816         stream.flush();
6817     }//end takeSnapshot
6818 
6819     /**
6820      * Retrieve the contents of the snapshot file and reconstitute the 'base'
6821      * state of this service from the retrieved data.
6822      * <p>
6823      * Refer to <code>takeSnapshot</code> for a description of the data
6824      * retrieved by this method from the snapshot file.
6825      * <p>
6826      * During recovery, the state of the service at the time of a crash
6827      * or outage is re-constructed by first reconstituting the 'base state'
6828      * from the snapshot file; and then modifying that base state according
6829      * to the records retrieved from the log file. This method is invoked to
6830      * perform the first step in that reconstruction. As each registered
6831      * service or event is retrieved, it is inserted into its appropriate
6832      * container object.
6833      * <p>
6834      * Because events can be generated before the next snapshot is taken,
6835      * the event sequence numbers must be incremented. This is because the
6836      * event specification requires that a set of event sequence numbers
6837      * corresponding to an particular event type (a particular event ID)
6838      * be monotonically increasing. Since the number of events that might
6839      * have been sent is arbitrary, each sequence number will be incremented
6840      * by a 'large' number so as to guarantee adherence to the specification.
6841      * 
6842      * @see FiddlerImpl.LocalLogHandler#takeSnapshot
6843      * @see FiddlerImpl.LocalLogHandler
6844      */
6845     private void recoverSnapshot(InputStream in)
6846                                  throws IOException, ClassNotFoundException
6847     {
6848         ObjectInputStream stream = new ObjectInputStream(in);
6849         /* Retrieve the service's stable information */
6850         if (!FiddlerImpl.class.getName().equals(stream.readUTF())) {
6851             throw new IOException("log from wrong implementation");
6852         }//endif
6853         if (stream.readInt() != LOG_VERSION) {
6854             throw new IOException("wrong log format version");
6855         }//endif
6856         proxyID = (Uuid)stream.readObject();
6857         /* Retrieve the current event ID */
6858         curEventID = stream.readLong();
6859         /* Retrieve the current join state of this service. Groups first. */
6860         thisServicesGroups   = (String[])stream.readObject();
6861         /* Retrieve and re-prepare the locators to join (drop bad locs) */
6862         thisServicesLocators = prepareOldLocators
6863                                      ( recoveredLocatorToJoinPreparer,
6864                                        (LookupLocator[])stream.readObject() );
6865         /* Retrieve the attributes to register with each lookup service. */
6866         MarshalledObject[] marshalledAttrs
6867                                     = (MarshalledObject[])stream.readObject();
6868         thisServicesAttrs = unmarshalAttributes(this, marshalledAttrs);
6869         /* Retrieve the current configuration parameters of this service */
6870         leaseBound     = stream.readLong();
6871         snapshotThresh = stream.readInt();
6872         snapshotWt     = stream.readFloat();
6873         RegistrationInfo regInfo;
6874         while ((regInfo = (RegistrationInfo)stream.readObject()) != null) {
6875             regInfo.prepare(recoveredListenerPreparer, recoveredLocatorToDiscoverPreparer);
6876             regInfo.seqNum += Integer.MAX_VALUE;
6877             addRegistration(regInfo);
6878         }//end loop
6879         initialStartup = false;
6880     }//end recoverSnapshot
6881 
6882     /**
6883      * Add a state-change record to persistent storage.
6884      * <p>
6885      * Whenever a significant change occurs to Fiddler's state, this
6886      * method is invoked to record that change in a file called a log file.
6887      * Each record written to the log file is an object reflecting both
6888      * the data used and the ACTIONS taken to make one change to that
6889      * state at a particular point in time. If the number of records
6890      * contained in the log file exceeds the pre-defined threshold,
6891      * a snapshot of Fiddler's current state will be recorded.
6892      * <p>
6893      * Whenever one of the following state changes occurs, this method
6894      * will be invoked with the appropriate implementation of the
6895      * LogRecord interface as the input argument.
6896      * <ul>
6897      * <li> new attributes were added to Fiddler's attributes with lookup
6898      * <li> Fiddler's existing attributes with lookup were modified
6899      *
6900      * <li> new groups were added to the set of groups Fiddler should join
6901      * <li> Fiddler's existing groups to join were replaced with a new set
6902      * <li> Fiddler's existing groups to join were removed
6903      *
6904      * <li> new locators were added to the set of locators Fiddler should join
6905      * <li> Fiddler's existing locators to join were replaced with a new set
6906      * <li> Fiddler's existing locators to join were removed
6907      *
6908      * <li> the bound on granted lease durations was set to a new value
6909      * <li> weight factor used to determine when to take a snapshot was changed
6910      * <li> threshold used to determine when to take a snapshot was changed
6911      *
6912      * <li> a registration request for use of Fiddler's resources was granted
6913      *
6914      * <li> groups were added to a registration's set of groups to discover
6915      * <li> the set of groups to discover for a registration was replaced
6916      * <li> the set of groups to discover for a registration was removed
6917      *
6918      * <li> locators were added to a registration's set of locators to discover
6919      * <li> the set of locators to discover for a registration was replaced
6920      * <li> the set of locators to discover for a registration was removed
6921      *
6922      * <li> a lease on a registration with Fiddler was renewed
6923      * <li> a set of registration leases with Fiddler were renewed
6924      * <li> a lease on a registration with Fiddler was cancelled
6925      * <li> a set of registration leases with Fiddler were cancelled
6926      *
6927      * </ul>
6928      * 
6929      * @see org.apache.river.reggie.RegistrarImpl.LocalLogHandler
6930      */
6931     private void addLogRecord(LogRecord rec) {
6932         if(log == null) return;//not persistent, don't log
6933 
6934         logInfoAddLogRecord(rec);
6935         try {
6936             log.update(rec, true);
6937             if (++logFileSize >= snapshotThresh) {
6938                 int snapshotSize = registrationByID.size();
6939                 if ((float)logFileSize >= snapshotWt*((float)snapshotSize)) {
6940                     /* take snapshot */
6941                     snapshotThreadSyncObj.signal();
6942 //                    concurrentObj.waiterNotify(snapshotThreadSyncObj);
6943                 }//endif
6944             }//endif
6945         } catch (Exception e) {
6946             if (!Thread.currentThread().isInterrupted()) {
6947                 /* If log updating fails, then register an ERROR status
6948                  * attribute (along with a Comment attribute describing the
6949                  * nature of the problem) to all lookup services with which
6950                  * this service is registered. 
6951                  *
6952                  * Administrative clients, as well as clients that use this
6953                  * service should have registered for notification of the
6954                  * existence of this attribute.
6955                  */
6956                 String eStr = "Failure while updating the persistent log "
6957                               +"containing the service state";
6958                 problemLogger.log(Level.INFO, eStr, e);
6959                 Entry[] errorAttrs = new Entry[]
6960                       { new FiddlerStatus(StatusType.ERROR),
6961                         new Comment(eStr)
6962                       };
6963                 joinMgr.addAttributes(errorAttrs,true);
6964             }//endif
6965         }
6966     }//end addLogRecord
6967 
6968     /**
6969      * Interface defining the method(s) that must be implemented by each of
6970      * the concrete LogObj classes. This allows for the definition of
6971      * object-dependent invocations of the appropriate implementation of
6972      * the method(s) declared in this interface.
6973      */
6974     private static interface LogRecord extends Serializable {
6975         void apply(FiddlerImpl fiddlerImpl);
6976     }
6977 
6978     /* 1. Definitions related to state changes generated by JoinAdmin */
6979 
6980     /**
6981      * LogObj class whose instances are recorded to the log file whenever
6982      * new attributes are added to this service's existing set of attributes.
6983      * 
6984      * @see FiddlerImpl.LocalLogHandler
6985      */
6986     private static class LookupAttrsAddedLogObj implements LogRecord {
6987         private static final long serialVersionUID = 4983778026976938585L;
6988         /** The attributes that were added to each lookup service with which
6989          *  this service is registered, written out in marshalled form.
6990          *  @serial
6991          */
6992         private MarshalledObject[] marshalledAttrs;
6993         /** Constructs this class and stores the attributes that were added */
6994         public LookupAttrsAddedLogObj(FiddlerImpl fiddlerImpl, Entry[] attrs) {
6995             this.marshalledAttrs = marshalAttributes(fiddlerImpl,attrs);
6996         }
6997         /** Modifies this service's state by adding (after unmarshalling) the
6998          *  elements of marshalledAttrs to the service's existing set of
6999          *  attributes.
7000          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7001          */
7002         public void apply(FiddlerImpl fiddlerImpl) {
7003             logInfoPersist("Log recovery: apply adding lookup attributes");
7004             Entry[] attrs = unmarshalAttributes(fiddlerImpl, marshalledAttrs);
7005             fiddlerImpl.thisServicesAttrs 
7006                   = LookupAttributes.add(fiddlerImpl.thisServicesAttrs,attrs);
7007         }
7008     }//end LookupAttrsAddedLogObj
7009 
7010     /**
7011      * LogObj class whose instances are recorded to the log file whenever
7012      * the attributes currently associated with this service in the lookup
7013      * services with which it is registered are modified.
7014      * 
7015      * @see FiddlerImpl.LocalLogHandler
7016      */
7017     private static class LookupAttrsModifiedLogObj implements LogRecord {
7018         private static final long serialVersionUID = 2671139518188470469L;
7019 	/** The attribute set templates used to select the attributes (from
7020          *  the service's existing set of attributes) that were modified,
7021          *  written out in marshalled form.
7022 	 *  @serial
7023 	 */
7024         private MarshalledObject[] marshalledAttrTmpls;
7025         /** The attributes with which this service's existing attributes 
7026          *  were modified, written out in marshalled form.
7027          *  @serial
7028          */
7029         private MarshalledObject[] marshalledModAttrs;
7030         /** Constructs this class and stores the modified attributes */
7031         public LookupAttrsModifiedLogObj(FiddlerImpl fiddlerImpl,
7032                                          Entry[] attrTmpls,
7033                                          Entry[] modAttrs)
7034         {
7035             this.marshalledAttrTmpls = marshalAttributes
7036                                                        (fiddlerImpl,attrTmpls);
7037             this.marshalledModAttrs = marshalAttributes(fiddlerImpl,modAttrs);
7038         }
7039         /** Modifies this service's state by modifying (after unmarshalling)
7040          *  the service's existing attributes according to the contents of
7041          *  marshalledAttrTmpls and marshalledModAttrs.
7042          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7043          */
7044         public void apply(FiddlerImpl fiddlerImpl) {
7045             logInfoPersist("Log recovery: apply modifying lookup attributes");
7046             Entry[] attrTmpls = unmarshalAttributes
7047                                             (fiddlerImpl, marshalledAttrTmpls);
7048             Entry[] modAttrs  = unmarshalAttributes
7049                                             (fiddlerImpl, marshalledModAttrs);
7050             fiddlerImpl.thisServicesAttrs 
7051                        = LookupAttributes.modify(fiddlerImpl.thisServicesAttrs,
7052                                                  attrTmpls, modAttrs);
7053         }
7054     }//end LookupAttrsModifiedLogObj
7055 
7056     /**
7057      * LogObj class whose instances are recorded to the log file whenever
7058      * the set containing the names of the groups whose members are lookup
7059      * services the lookup discovery service wishes to register with (join)
7060      * is modified in some way; for example, through the invocation of:
7061      * <code>JoinAdmin.addLookupGroups</code>,
7062      * <code>JoinAdmin.removeLookupGroups</code> or
7063      * <code>JoinAdmin.setLookupGroups</code>.
7064      * 
7065      * @see FiddlerImpl.LocalLogHandler
7066      */
7067     private static class LookupGroupsChangedLogObj implements LogRecord {
7068         private static final long serialVersionUID = -6973676200404539919L;
7069         /** The new groups whose members are the lookup services this
7070          *  service should join.
7071          *  @serial
7072          */
7073         private String[] groups;
7074 	/** Constructs this class and stores the new groups */
7075         public LookupGroupsChangedLogObj(String[] groups) {
7076             this.groups = groups;
7077         }
7078         /** Modifies this service's state by modifying this service's existing
7079          *  set of 'groups to join'.
7080          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7081          */
7082         public void apply(FiddlerImpl fiddlerImpl) {
7083             logInfoPersist
7084                        ("Log recovery: apply changing lookup groups to join");
7085             fiddlerImpl.thisServicesGroups = groups; 
7086         }
7087     }//end LookupGroupsChangedLogObj
7088 
7089     /**
7090      * LogObj class whose instances are recorded to the log file whenever
7091      * the set containing the instances of <code>LookupLocator</code> that
7092      * correspond to specific lookup services the lookup discovery service
7093      * wishes to register with (join) is modified in some way; for example,
7094      * through the invocation of:
7095      * <code>JoinAdmin.addLookupLocators</code>,
7096      * <code>JoinAdmin.removeLookupLocators</code> or
7097      * <code>JoinAdmin.setLookupLocators</code>.
7098      *
7099      *  @see FiddlerImpl.LocalLogHandler
7100      */
7101     private static class LookupLocatorsChangedLogObj implements LogRecord {
7102         private static final long serialVersionUID = 6448427261140043291L;
7103         /** The locators that correspond to the new lookup services this
7104          *  service should join.
7105          *  @serial
7106          */
7107         private LookupLocator[] locators;
7108         /** Constructs this class and stores the new locators */
7109         public LookupLocatorsChangedLogObj(LookupLocator[] locators) {
7110             this.locators = locators;
7111         }
7112         /** Modifies this service's state by modifying this service's existing
7113          *  set of 'locators to join'.
7114          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7115          */
7116         public void apply(FiddlerImpl fiddlerImpl) {
7117             logInfoPersist
7118                      ("Log recovery: apply changing lookup locators to join");
7119             fiddlerImpl.thisServicesLocators =
7120                   prepareOldLocators(fiddlerImpl.recoveredLocatorToJoinPreparer,locators);
7121         }
7122     }//end LookupLocatorsChangedLogObj
7123 
7124     /* 2. Definitions related to state changes generated by FiddlerAdmin */
7125 
7126     /**
7127      * LogObj class whose instances are recorded to the log file whenever a
7128      * new value is assigned to the least upper bound applied during the
7129      * determination of the duration of the lease on a requested registration.
7130      * 
7131      * @see FiddlerImpl.LocalLogHandler
7132      */
7133     private static class LeaseBoundSetLogObj implements LogRecord {
7134         private static final long serialVersionUID = 6084059678114714281L;
7135         /** The new least upper bound used to determine a lease's duration.
7136          *  @serial
7137          */
7138         private long newLeaseBound;
7139         /** Constructs this class and stores the new lease duration bound */
7140         public LeaseBoundSetLogObj(long newLeaseBound) {
7141             this.newLeaseBound = newLeaseBound;
7142         }
7143         /** Modifies this service's state by setting the value of the private
7144          *  leaseBound field to the value of the new bound.
7145          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7146          */
7147         public void apply(FiddlerImpl fiddlerImpl) {
7148             logInfoPersist
7149                          ("Log recovery: apply changing lease duration bound");
7150             fiddlerImpl.leaseBound = newLeaseBound; 
7151         }
7152     }//end LeaseBoundSetLogObj
7153 
7154     /**
7155      * LogObj class whose instances are recorded to the log file whenever a
7156      * new value is assigned to the Weight Factor applied to the Snapshot File
7157      * size used in the "time-to-take-a-snapshot determination" expression.
7158      * 
7159      * @see FiddlerImpl.LocalLogHandler
7160      */
7161     private static class SnapshotWeightSetLogObj implements LogRecord {
7162         private static final long serialVersionUID = -4079318959709953285L;
7163         /** The new snapshot weight factor.
7164          * @serial
7165          */
7166         private float newWeight;
7167 	/** Constructs this class and stores the new weight factor */
7168         public SnapshotWeightSetLogObj(float newWeight) {
7169             this.newWeight = newWeight;
7170         }
7171         /** Modifies this service's state by setting the value of the private
7172          *  snapshotWt field to the value of the new weight factor.
7173          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7174          */
7175         public void apply(FiddlerImpl fiddlerImpl) {
7176             logInfoPersist
7177                       ("Log recovery: apply changing snapshot weight factor");
7178             fiddlerImpl.snapshotWt = newWeight; 
7179         }
7180     }//end SnapshotWeightSetLogObj
7181 
7182     /**
7183      * LogObj class whose instances are recorded to the log file 
7184      * whenever a new value is assigned to the Threshold used in the
7185      * "time-to-take-a-snapshot determination" expression.
7186      * 
7187      * @see FiddlerImpl.LocalLogHandler
7188      */
7189     private static class SnapshotThresholdSetLogObj implements LogRecord {
7190         private static final long serialVersionUID = 2952948867001823559L;
7191         /** The new snapshot threshold.
7192          *  @serial
7193          */
7194         private int newThreshold;
7195         /** Constructs this class and stores the new snapshot threshold */
7196         public SnapshotThresholdSetLogObj(int newThreshold) {
7197             this.newThreshold = newThreshold;
7198         }
7199         /** Modifies this service's state by setting the value of the private
7200          *  snapshotThresh field to the value of the new threshold.
7201          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7202          */
7203         public void apply(FiddlerImpl fiddlerImpl) {
7204             logInfoPersist
7205                     ("Log recovery: apply changing log-to-snapshot threshold");
7206             fiddlerImpl.snapshotThresh = newThreshold; 
7207         }
7208     }//end SnapshotThresholdSetLogObj
7209 
7210     /* 3. Definitions related to state changes made to the set of active
7211      *    registrations, generated by client requests made through the
7212      *    <code>LookupDiscoveryRegistration</code> interface.
7213      */
7214     /**
7215      * LogObj class whose instances are recorded to the log file whenever
7216      * a registration is created and returned to a client.
7217      * 
7218      * @see FiddlerImpl.LocalLogHandler
7219      */
7220     private static class RegistrationGrantedLogObj implements LogRecord {
7221         private static final long serialVersionUID = 3983963008572188308L;
7222         /** Object which acts as a record of the current registration with the
7223          *  lookup discovery service; containing all of the information about
7224          *  that registration: IDs, managed sets, lease information, and
7225          *  event registration information.
7226          *  @serial
7227          */
7228         private RegistrationInfo regInfo;
7229         /** Constructs this class and stores the registration information */
7230         public RegistrationGrantedLogObj(RegistrationInfo regInfo) {
7231             this.regInfo = regInfo;
7232         }
7233         /** Modifies this service's state by registering the information
7234          *  stored in the regInfo parameter; and by updating both the event
7235          *  sequence number and the event ID for the registration.
7236          *
7237          *  Note that the granting of a registration to a client typically
7238          *  involves the modification of the managed sets in the discovery
7239          *  manager, which usually involves starting the discovery protocol.
7240          *  Since an IOException can occur when the discovery protocol fails
7241          *  to start, and since such a situation is un-recoverable, this
7242          *  method does the following: catches the exception, informs this
7243          *  service's administrator by displaying the stack trace, and exits.
7244          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7245          */
7246         public void apply(FiddlerImpl fiddlerImpl) {
7247             logInfoPersist("Log recovery: apply recording granted registration"
7248                            +" info\n              (ID = "
7249                            +regInfo.registrationID+")");
7250             regInfo.seqNum += Integer.MAX_VALUE;
7251             try {
7252                 /* Note that the locators of this recovered registration
7253                  * were already successfully prepared in the method
7254                  * RegistrationInfo.readObject() when this object was
7255                  * deserialized during recovery.
7256                  */
7257                 fiddlerImpl.addRegistration(regInfo);
7258             } catch(IOException e) {
7259                 if( problemLogger.isLoggable(Level.SEVERE) ) {
7260                     problemLogger.log(Level.SEVERE, "During log recovery "
7261                                       +"(apply addRegistration) -- failure in "
7262                                       +"multicast request protocol\n", e);
7263                 }//endif
7264                 fiddlerImpl.destroyDo();
7265             }
7266             fiddlerImpl.curEventID++;
7267         }
7268     }//end RegistrationGrantedLogObj
7269 
7270     /* 4. Definitions related to state changes made to the set of active
7271      *    registrations, generated by client requests made through the
7272      *    <code>LookupDiscoveryService</code> interface.
7273      */
7274 
7275     /**
7276      * LogObj class whose instances are recorded to the log file whenever
7277      * the managed set of groups corresponding to a registration is 
7278      * is augmented with new elements.
7279      * 
7280      * @see FiddlerImpl.LocalLogHandler
7281      */
7282     private static class GroupsAddedToRegistrationLogObj implements LogRecord {
7283         private static final long serialVersionUID = 2L;
7284         /** The ID of the data structure record corresponding to the
7285          *  registration whose managed set of groups was augmented.
7286          *  @serial
7287          */
7288         private Uuid registrationID;
7289         /** The set of groups added to the registration's managed set of groups
7290          *  @serial
7291          */
7292         private String[] groups;
7293         /** Constructs this class and stores the ID and new set of groups */
7294         public GroupsAddedToRegistrationLogObj(Uuid registrationID,
7295                                                String[] groups)
7296         {
7297             this.registrationID = registrationID;
7298             this.groups         = groups;
7299         }
7300         /** Modifies this service's state by adding the set of group names to
7301          *  registration's managed set of groups, as well as by updating the
7302          *  set of all groups (across all registrations) to discover.
7303          *
7304          *  Note that the augmentation of a registration's set of groups
7305          *  typically involves the modification of the managed sets in the
7306          *  discovery manager, which usually involves starting the discovery
7307          *  protocol. Since an IOException can occur when the discovery
7308          *  protocol fails to start, and since such a situation is
7309          *  un-recoverable, this method does the following: catches the
7310          *  exception, informs this service's administrator by displaying
7311          *  the stack trace, and exits.
7312          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7313          */
7314         public void apply(FiddlerImpl fiddlerImpl) {
7315             logInfoPersist("Log recovery: apply adding to groups to discover "
7316                            +"for registration\n              (ID = "
7317                            +registrationID+")");
7318             try {
7319                 fiddlerImpl.addGroupsDo(registrationID,
7320                                         fiddlerImpl.registrationByID,
7321                                         groups);
7322             } catch(Exception e) {
7323                 if( problemLogger.isLoggable(Level.SEVERE) ) {
7324                     problemLogger.log(Level.SEVERE, "During log recovery "
7325                                       +"(apply addGroupsDO) -- failure in "
7326                                       +"multicast request protocol\n", e);
7327                 }//endif
7328                 fiddlerImpl.destroyDo();
7329             }
7330         }//end apply
7331     }//end GroupsAddedToRegistrationLogObj
7332 
7333     /**
7334      * LogObj class whose instances are recorded to the log file whenever
7335      * a the managed set of groups corresponding to a registration is 
7336      * is replaced (set) with a new set of group names.
7337      * 
7338      * @see FiddlerImpl.LocalLogHandler
7339      */
7340     private static class GroupsSetInRegistrationLogObj implements LogRecord {
7341         static final long serialVersionUID = 2L;
7342         /** The ID of the data structure record corresponding to the
7343          *  registration whose managed set of groups was replaced.
7344          *  @serial
7345          */
7346         private Uuid registrationID;
7347         /** The set of groups that replaced the registration's current
7348          *  managed set of groups.
7349          *  @serial
7350          */
7351         private String[] groups;
7352         /** Constructs this class and stores the ID and new set of groups */
7353         public GroupsSetInRegistrationLogObj(Uuid registrationID,
7354                                              String[] groups)
7355         {
7356             this.registrationID = registrationID;
7357             this.groups         = groups;
7358         }
7359         /** Modifies this service's state by replacing the registration's
7360          *  current managed set of groups with the set of group names 
7361          *  stored in this class by the constructor, as well as by updating
7362          *  the set of all groups (across all registrations) to discover.
7363          *
7364          *  Note that the replacement of a registration's set of groups
7365          *  typically involves the modification of the managed sets in the
7366          *  discovery manager, which usually involves starting the discovery
7367          *  protocol. Since an IOException can occur when the discovery
7368          *  protocol fails to start, and since such a situation is
7369          *  un-recoverable, this method does the following: catches the
7370          *  exception, informs this service's administrator by displaying
7371          *  the stack trace, and exits.
7372          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7373          */
7374         public void apply(FiddlerImpl fiddlerImpl) {
7375             logInfoPersist("Log recovery: apply replacing groups to discover "
7376                            +"for registration\n              (ID = "
7377                            +registrationID+") ...");
7378             try {
7379                 fiddlerImpl.setGroupsDo(registrationID,
7380                                         fiddlerImpl.registrationByID,
7381                                         groups);
7382             } catch(Exception e) {
7383                 if( problemLogger.isLoggable(Level.SEVERE) ) {
7384                     problemLogger.log(Level.SEVERE, "Failure during log "
7385                                       +"recovery (apply setGroups) -- \n", e);
7386                 }//endif
7387                 fiddlerImpl.destroyDo();
7388             }
7389         }
7390     }//end GroupsSetInRegistrationLogObj
7391 
7392     /**
7393      * LogObj class whose instances are recorded to the log file whenever
7394      * one or more elements of the managed set of groups corresponding to a
7395      * registration are removed.
7396      * 
7397      * @see FiddlerImpl.LocalLogHandler
7398      */
7399     private static class GroupsRemovedFromRegistrationLogObj
7400                                                         implements LogRecord
7401     {
7402         private static final long serialVersionUID = 2L;
7403         /** The ID of the data structure record corresponding to the
7404          *  registration with managed set from which groups were removed.
7405          *  @serial
7406          */
7407         private Uuid registrationID;
7408         /** The set of groups removed from the registration's managed set 
7409          *  of groups.
7410          *  @serial
7411          */
7412         private String[] groups;
7413         /** Constructs this class and stores the ID and groups to remove */
7414         public GroupsRemovedFromRegistrationLogObj(Uuid registrationID,
7415                                                    String[] groups)
7416         {
7417             this.registrationID = registrationID;
7418             this.groups         = groups;
7419         }
7420         /** Modifies this service's state by removing the set of group names
7421          *  from registration's managed set of groups, as well as by updating
7422          *  the set of all groups (across all registrations) to discover.
7423          *
7424          *  Note that the removal of one or more group names from a
7425          *  registration's set of groups typically involves the modification 
7426          *  of the managed sets in the discovery manager, which usually 
7427          *  involves starting the discovery protocol. Since an IOException 
7428          *  can occur when the discovery protocol fails to start, and since 
7429          *  such a situation is un-recoverable, this method does the 
7430          *  following: catches the exception, informs this service's 
7431          *  administrator by displaying the stack trace, and exits.
7432          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7433          */
7434         public void apply(FiddlerImpl fiddlerImpl) {
7435             logInfoPersist("Log recovery: apply removing from groups to "
7436                            +"discover for registration\n              (ID = "
7437                            +registrationID+")");
7438             try {
7439                 fiddlerImpl.removeGroupsDo(registrationID,
7440                                            fiddlerImpl.registrationByID,
7441                                            groups);
7442 	    } catch(Exception e) {
7443                 if( problemLogger.isLoggable(Level.SEVERE) ) {
7444                     problemLogger.log(Level.SEVERE, "Failure during log "
7445                                     +"recovery (apply removeGroups) -- \n", e);
7446                 }//endif
7447 	        fiddlerImpl.destroyDo();
7448 	    }
7449         }
7450     }//end GroupsRemovedFromRegistrationLogObj
7451 
7452     /**
7453      * LogObj class whose instances are recorded to the log file whenever
7454      * the managed set of locators corresponding to a registration is 
7455      * is augmented with new elements.
7456      * 
7457      * @see FiddlerImpl.LocalLogHandler
7458      */
7459     private static class LocsAddedToRegistrationLogObj implements LogRecord {
7460         private static final long serialVersionUID = 2L;
7461         /** The ID of the data structure record corresponding to the
7462          *  registration whose managed set of locators was augmented.
7463          *  @serial
7464          */
7465         private Uuid registrationID;
7466         /** The set of locators added to the registration's managed set
7467          *  @serial
7468          */
7469         private LookupLocator[] locators;
7470         /** Constructs this class and stores the ID and new set of locators */
7471         public LocsAddedToRegistrationLogObj(Uuid registrationID,
7472                                              LookupLocator[] locators)
7473         {
7474             this.registrationID = registrationID;
7475             this.locators       = locators;
7476         }
7477         /** Modifies this service's state by adding the set of locators to
7478          *  registration's managed set of locators, as well as by updating the
7479          *  set of all locators (across all registrations) to discover.
7480          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7481          */
7482         public void apply(FiddlerImpl fiddlerImpl) {
7483             logInfoPersist("Log recovery: apply adding to locators to discover"
7484                            +" for registration\n              (ID = "
7485                            +registrationID+")");
7486             int nUnprepared = locators.length;
7487             /* Prepare the recovered locators */
7488             locators = 
7489               prepareOldLocators(fiddlerImpl.recoveredLocatorToDiscoverPreparer, locators);
7490             /* If all the locs were successfully prepared, add them to the
7491              * associated registration; otherwise, remove the registration
7492              * from the managed set. (For more information, see the comment
7493              * in RegistrationInfo.readObject()).
7494              */
7495             if(nUnprepared == locators.length) {
7496                 fiddlerImpl.addLocatorsDo( registrationID,
7497                                            fiddlerImpl.registrationByID,
7498                                            locators );
7499             } else {
7500                 if( problemLogger.isLoggable(Level.WARNING) ) {
7501                     problemLogger.log(Level.WARNING, "failure preparing "
7502                                       +"locator while recovering "
7503                                       +"LocsAddedToRegistrationLogObj "
7504                                       +"... removing registration with ID = "
7505                                       +registrationID);
7506                 }//endif
7507                 try {
7508                     fiddlerImpl.removeRegistration
7509                        ( (RegistrationInfo)(fiddlerImpl.registrationByID.get
7510                                                            (registrationID)) );
7511                 } catch(IOException e) {
7512                     String eStr = "failure removing registration (ID = "
7513                                   +registrationID
7514                                   +") after locator preparation failure";
7515                     if( problemLogger.isLoggable(Level.INFO) ) {
7516                         problemLogger.log(Level.INFO, eStr, e);
7517                     }//endif
7518                     Entry[] errorAttrs
7519                             = new Entry[]
7520                                         { new FiddlerStatus(StatusType.ERROR),
7521                                           new Comment(eStr)
7522                                         };
7523                     fiddlerImpl.joinMgr.addAttributes(errorAttrs,true);
7524                 }
7525             }//endif
7526         }//end apply
7527     }//end LocsAddedToRegistrationLogObj
7528 
7529     /**
7530      * LogObj class whose instances are recorded to the log file whenever
7531      * the managed set of locators corresponding to a registration is 
7532      * is replaced (set) with a new set of locators.
7533      * 
7534      * @see FiddlerImpl.LocalLogHandler
7535      */
7536     private static class LocsSetInRegistrationLogObj implements LogRecord {
7537         private static final long serialVersionUID = 2L;
7538         /** The ID of the data structure record corresponding to the
7539          *  registration whose managed set of locators was replaced.
7540          *  @serial
7541          */
7542         private Uuid registrationID;
7543         /** The set of locators that replaced the registration's current
7544          *  managed set of locators.
7545          *  @serial
7546          */
7547         private LookupLocator[] locators;
7548         /** Constructs this class and stores the ID and new set of locators */
7549         public LocsSetInRegistrationLogObj(Uuid registrationID,
7550                                            LookupLocator[] locators)
7551         {
7552             this.registrationID = registrationID;
7553             this.locators       = locators;
7554         }
7555         /** Modifies this service's state by replacing the registration's
7556          *  current managed set of locators with the set of locators 
7557          *  stored in this class by the constructor, as well as by updating
7558          *  the set of all locators (across all registrations) to discover.
7559          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7560          */
7561         public void apply(FiddlerImpl fiddlerImpl) {
7562             logInfoPersist("Log recovery: apply replacing locators to discover"
7563                            +" for registration\n              (ID = "
7564                            +registrationID+")");
7565             int nUnprepared = locators.length;
7566             /* Prepare the recovered locators */
7567             locators = 
7568               prepareOldLocators(fiddlerImpl.recoveredLocatorToDiscoverPreparer, locators);
7569             /* If all the locs were successfully prepared, set them in the
7570              * associated registration; otherwise, remove the registration
7571              * from the managed set. (For more information, see the comment
7572              * in RegistrationInfo.readObject()).
7573              */
7574             if(nUnprepared == locators.length) {
7575                 fiddlerImpl.setLocatorsDo( registrationID,
7576                                            fiddlerImpl.registrationByID,
7577                                            locators );
7578             } else {
7579                 if( problemLogger.isLoggable(Level.WARNING) ) {
7580                     problemLogger.log(Level.WARNING,
7581                                       "failure preparing locator while "
7582                                       +"recovering LocsSetInRegistrationLogObj"
7583                                       +" ... removing registration with ID = "
7584                                       +registrationID);
7585                 }//endif
7586                 try {
7587                     fiddlerImpl.removeRegistration
7588                        ( (RegistrationInfo)(fiddlerImpl.registrationByID.get
7589                                                            (registrationID)) );
7590                 } catch(IOException e) {
7591                     String eStr = "failure removing registration (ID = "
7592                                   +registrationID
7593                                   +") after locator preparation failure";
7594                     if( problemLogger.isLoggable(Level.WARNING) ) {
7595                         problemLogger.log(Level.WARNING, eStr, e);
7596                     }//endif
7597                     Entry[] errorAttrs
7598                             = new Entry[]
7599                                         { new FiddlerStatus(StatusType.ERROR),
7600                                           new Comment(eStr)
7601                                         };
7602                     fiddlerImpl.joinMgr.addAttributes(errorAttrs,true);
7603                 }
7604             }//endif
7605         }
7606     }//end LocsSetInRegistrationLogObj
7607 
7608     /**
7609      * LogObj class whose instances are recorded to the log file whenever
7610      * one or more elements of the managed set of locators corresponding to a
7611      * registration are removed.
7612      * 
7613      * @see FiddlerImpl.LocalLogHandler
7614      */
7615     private static class LocsRemovedFromRegistrationLogObj
7616                                                          implements LogRecord 
7617     {
7618         private static final long serialVersionUID = 2L;
7619         /** The ID of the data structure record corresponding to the
7620          *  registration with managed set from which locators were removed.
7621          *  @serial
7622          */
7623         private Uuid registrationID;
7624         /** The set of locators removed from the registration's managed set 
7625          *  of locators.
7626          *  @serial
7627          */
7628         private LookupLocator[] locators;
7629         /** Constructs this class and stores the ID and locators to remove */
7630         public LocsRemovedFromRegistrationLogObj(Uuid registrationID,
7631                                                  LookupLocator[] locators)
7632         {
7633             this.registrationID = registrationID;
7634             this.locators       = locators;
7635         }
7636         /** Modifies this service's state by removing the set of locators
7637          *  from registration's managed set of locators, as well as by updating
7638          *  the set of all locators (across all registrations) to discover.
7639          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7640          */
7641         public void apply(FiddlerImpl fiddlerImpl) {
7642             logInfoPersist("Log recovery: apply removing from locators to "
7643                            +"discover for registration\n              (ID = "
7644                            +registrationID+") ...");
7645             int nUnprepared = locators.length;
7646             /* Prepare the recovered locators */
7647             locators = 
7648               prepareOldLocators(fiddlerImpl.recoveredLocatorToDiscoverPreparer, locators);
7649             /* If all the locs were successfully prepared, remove them from
7650              * the associated registration; otherwise, remove the registration
7651              * from the managed set. (For more information, see the comment
7652              * in RegistrationInfo.readObject()).
7653              */
7654             if(nUnprepared == locators.length) {
7655                 fiddlerImpl.removeLocatorsDo( registrationID,
7656                                               fiddlerImpl.registrationByID,
7657                                               locators );
7658             } else {
7659                 if( problemLogger.isLoggable(Level.WARNING) ) {
7660                     problemLogger.log(Level.WARNING, "failure preparing "
7661                                       +"locator while recovering"
7662                                       +"LocsRemovedFromRegistrationLogObj "
7663                                       +"... removing registration with ID = "
7664                                       +registrationID);
7665                 }//endif
7666                 try {
7667                     fiddlerImpl.removeRegistration
7668                        ( (RegistrationInfo)(fiddlerImpl.registrationByID.get
7669                                                            (registrationID)) );
7670                 } catch(IOException e) {
7671                     String eStr = "failure removing registration (ID = "
7672                                   +registrationID
7673                                   +") after locator preparation failure";
7674                     if( problemLogger.isLoggable(Level.WARNING) ) {
7675                         problemLogger.log(Level.WARNING, eStr, e);
7676                     }//endif
7677                     Entry[] errorAttrs
7678                             = new Entry[]
7679                                         { new FiddlerStatus(StatusType.ERROR),
7680                                           new Comment(eStr)
7681                                         };
7682                     fiddlerImpl.joinMgr.addAttributes(errorAttrs,true);
7683                 }
7684             }//endif
7685         }
7686     }//end LocsRemovedFromRegistrationLogObj
7687 
7688     /**
7689      * LogObj class whose instances are recorded to the log file whenever
7690      * a lease on an existing registration (granted by the current backend
7691      * server of the lookup discovery service -- the lease grantor) is renewed.
7692      * 
7693      * @see FiddlerImpl.LocalLogHandler
7694      */
7695     private static class LeaseRenewedLogObj implements LogRecord {
7696         private static final long serialVersionUID = 2L;
7697         /** The ID of the data structure record corresponding to the
7698          *  registration whose lease was renewed.
7699          *  @serial
7700          */
7701         private Uuid registrationID;
7702         /** The identifier assigned by the lease grantor to the lease that was
7703          *  renewed.
7704          *  @serial
7705          */
7706         private Uuid leaseID;
7707         /** The new absolute time of expiration of the lease that was renewed.
7708          *  @serial
7709          */
7710         private long expiration;
7711         /** Constructs this class and stores the IDs and the expiration time */
7712         public LeaseRenewedLogObj(Uuid registrationID,
7713                                   Uuid leaseID,
7714                                   long expiration)
7715         {
7716             this.registrationID = registrationID;
7717             this.leaseID        = leaseID;
7718             this.expiration     = expiration;
7719         }
7720         /** Modifies this service's state by renewing the lease with ID equal
7721          *  to this class' leaseID field, and which corresponds to the 
7722          *  regInfo record. The lease will be renewed to have a new expiration
7723          *  time equal to the value of the expiration field.
7724          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7725          */
7726         public void apply(FiddlerImpl fiddlerImpl) {
7727             logInfoPersist("Log recovery: apply renewing lease for "
7728                            +"registration\n              (ID = "
7729                            +registrationID+")");
7730             fiddlerImpl.renewLeaseAbs(registrationID, 
7731                                       fiddlerImpl.registrationByID,
7732                                       leaseID, expiration);
7733         }
7734     }//end LeaseRenewedLogObj
7735 
7736     /**
7737      * LogObj class whose instances are recorded to the log file whenever
7738      * a set of leases from a <code>LeaseMap</code> are renewed.
7739      * 
7740      * @see FiddlerImpl.LocalLogHandler
7741      */
7742     private static class LeasesRenewedLogObj implements LogRecord {
7743         private static final long serialVersionUID = 2L;
7744         /** The set of unique identifiers each assigned to a registration that
7745          *  corresponds to one of the leases that was renewed.
7746          *  @serial
7747          */
7748         private Uuid[] registrationIDs;
7749         /** The set of identifiers each assigned by the lease grantor to one
7750          *  of the leases that was renewed.
7751          *  @serial
7752          */
7753         private Uuid[] leaseIDs;
7754         /** The set of new absolute expiration times of each lease that was
7755          *  renewed.
7756          *  @serial
7757          */
7758         private long[] expirations;
7759         /** Constructs this class and stores the sets of IDs and the set of
7760          *  expiration times.
7761          */
7762         public LeasesRenewedLogObj(Uuid[] registrationIDs,
7763                                    Uuid[] leaseIDs,
7764                                    long[] expirations)
7765         {
7766             this.registrationIDs = registrationIDs;
7767             this.leaseIDs        = leaseIDs;
7768             this.expirations     = expirations;
7769         }
7770         /** Modifies this service's state by renewing, with the corresponding
7771          *  expiration time, each of the leases specified by the stored IDs.
7772          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7773          */
7774         public void apply(FiddlerImpl fiddlerImpl) {
7775             logInfoPersist("Log recovery: apply renewing leases corresponding "
7776                            +"to "+registrationIDs.length
7777                            +" registration IDs");
7778             fiddlerImpl.renewLeasesAbs(registrationIDs, leaseIDs, expirations);
7779         }
7780     }//end LeasesRenewedLogObj
7781 
7782     /**
7783      * LogObj class whose instances are recorded to the log file whenever
7784      * a lease on an existing registration (granted by the current backend
7785      * server of the lookup discovery service -- the lease grantor) is
7786      * cancelled.
7787      * 
7788      * @see FiddlerImpl.LocalLogHandler
7789      */
7790     private static class LeaseCancelledLogObj implements LogRecord {
7791         private static final long serialVersionUID = 2L;
7792         /** The ID of the data structure record corresponding to the
7793          *  registration whose lease was cancelled.
7794          *  @serial
7795          */
7796         private Uuid registrationID;
7797         /** The identifier assigned by the lease grantor to the lease that was
7798          *  cancelled.
7799          *  @serial
7800          */
7801         private Uuid leaseID;
7802         /** Constructs this class and stores the IDs corresponding to the
7803          *  lease that was cancelled.
7804          */
7805         public LeaseCancelledLogObj(Uuid registrationID,
7806                                     Uuid leaseID) {
7807             this.registrationID = registrationID;
7808             this.leaseID        = leaseID;
7809         }
7810         /** Modifies this service's state by canceling the lease on the
7811          *  registration with ID equal to that stored by the constructor,
7812          *  as well as by updating the managed set of groups and locators 
7813          *  (across all registrations) in the appropriate way.
7814          *
7815          *  Note that the cancellation of a lease typically involves the
7816          *  modification of the managed sets in the discovery manager, which
7817          *  usually involves starting the discovery protocol. Since an
7818          *  IOException can occur when the discovery protocol fails to start,
7819          *  and since such a situation is un-recoverable, this method does
7820          *  the following: catches the exception, informs this service's
7821          *  administrator by displaying the stack trace, and exits.
7822          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7823          */
7824         public void apply(FiddlerImpl fiddlerImpl) {
7825             try {
7826                 logInfoPersist("Log recovery: apply cancelling lease for "
7827                                +"registration\n              (ID = "
7828                                +registrationID+")");
7829                 fiddlerImpl.cancelLeaseDo(registrationID, 
7830                                           fiddlerImpl.registrationByID,
7831                                           leaseID);
7832             } catch(IOException e) {
7833                 if( problemLogger.isLoggable(Level.SEVERE) ) {
7834                     problemLogger.log(Level.SEVERE, "During log recovery "
7835                                       +"(apply cancelLease) -- failure in "
7836                                       +"multicast request protocol\n", e);
7837                 }//endif
7838                 fiddlerImpl.destroyDo();
7839             } catch (UnknownLeaseException e) {
7840                 /* this exception should never occur when recovering */
7841             }
7842         }
7843     }//end LeaseCancelledLogObj
7844 
7845     /**
7846      * LogObj class whose instances are recorded to the log file whenever
7847      * a set of leases from a <code>LeaseMap</code> are cancelled.
7848      * 
7849      * @see FiddlerImpl.LocalLogHandler
7850      */
7851     private static class LeasesCancelledLogObj implements LogRecord {
7852         private static final long serialVersionUID = 2L;
7853         /** The set of unique identifiers each assigned to a registration that
7854          *  corresponds to one of the leases that was cancelled.
7855          *  @serial
7856          */
7857         private Uuid[] registrationIDs;
7858         /** The set of identifiers each assigned by the lease grantor to one
7859          *  of the leases that was cancelled.
7860          *  @serial
7861          */
7862         private Uuid[] leaseIDs;
7863         /** Constructs this class and stores the IDs corresponding to the
7864          *  leases that were cancelled.
7865          */
7866         public LeasesCancelledLogObj(Uuid[] registrationIDs,
7867                                      Uuid[] leaseIDs)
7868         {
7869             this.registrationIDs = registrationIDs;
7870             this.leaseIDs        = leaseIDs;
7871         }
7872         /** Modifies this service's state by canceling each of the leases
7873          *  specified by the stored IDs.
7874          *  @see FiddlerImpl.LocalLogHandler#applyUpdate
7875          */
7876         public void apply(FiddlerImpl fiddlerImpl) {
7877             logInfoPersist("Log recovery: apply cancelling leases "
7878                            +"corresponding to "+registrationIDs.length
7879                            +" registration IDs");
7880             /* Because unknown leases were not weeded out, exceptions can
7881              * occur and be propagated upward, but they can be ignored.
7882              */
7883             fiddlerImpl.cancelLeasesDo(registrationIDs, leaseIDs);
7884         }
7885     }//end LeasesCancelledLogObj
7886     /* END Persistent State Logging Interfaces & Classes ------------------- */
7887 
7888     /* BEGIN Private Logging Facility Methods ------------------------------ */
7889     /* Returns a String containing the elements of the array */
7890     private static String writeArrayElementsToString(Object[] arr) {
7891         if(arr == null) return "[]";
7892         if(arr.length <= 0) {
7893             return "[]";
7894         }//endif
7895         StringBuffer strBuf = new StringBuffer("["+arr[0]);
7896         for(int i=1;i<arr.length;i++){
7897             strBuf.append(", ").append(arr[i]);
7898         }//end loop
7899         strBuf.append("]");
7900         return strBuf.toString();
7901     }//end writeArrayElementsToString
7902 
7903     /* Logs the elements of the array to a single line of output */
7904     private static void writeArrayElements(Object[] arr,
7905                                            Logger   logger,
7906                                            Level    level)
7907     {
7908         if((arr == null) || (logger == null) || !(logger.isLoggable(level))) {
7909             return;
7910         }
7911         String writeStr = writeArrayElementsToString(arr);
7912         logger.log(level,writeStr);
7913     }//end writeArrayElements
7914 
7915     /* Returns a String containing the group names in writable form */
7916     private static String writeGroupArrayToString(String[] groups) {
7917         if(groups == null) {
7918             return "[ALL_GROUPS]";
7919         }//endif
7920         if(groups.length <= 0) {
7921             return "[]";
7922         }//endif
7923         StringBuffer strBuf = null;
7924         if(groups[0].compareTo("") == 0) {
7925             strBuf = new StringBuffer("[The PUBLIC Group");
7926         } else {
7927             strBuf = new StringBuffer("["+groups[0]);
7928         }//endif
7929         for(int i=1;i<groups.length;i++) {
7930             if(groups[i].compareTo("") == 0) {
7931                 strBuf.append(", The PUBLIC Group");
7932             } else {
7933                 strBuf.append(", ").append(groups[i]);
7934             }//endif
7935         }//end loop
7936         strBuf.append("]");
7937         return strBuf.toString();
7938     }//end writeGroupArrayToString
7939 
7940     /* Logs the group names to a single line of output */
7941     private static void writeGroupArray(String[] groups,
7942                                         Logger   logger,
7943                                         Level    level)
7944     {
7945         if( (logger == null) || !(logger.isLoggable(level)) ) return;
7946         String writeStr = writeGroupArrayToString(groups);
7947         logger.log(level,writeStr);
7948     }//end writeGroupArray
7949 
7950     /* Logs each registrar's locator on a single line of output */
7951     private static void writeRegistrarsArray(ServiceRegistrar[] regs,
7952                                              Logger             logger,
7953                                              Level              level)
7954     {
7955         if((regs == null) || (logger == null) || !(logger.isLoggable(level))) {
7956             return;
7957         }
7958         if(regs.length == 0) {
7959             logger.log(level, "[NO REGISTRARS for Event]");
7960         }//endif
7961         if(regs.length == 1) {
7962             try{
7963                 LookupLocator loc = regs[0].getLocator();
7964                 logger.log(level, "["+loc+"]");
7965             }catch(SecurityException e){
7966                 logger.log(level, "[SecurityException]");
7967             }catch(Exception e){
7968                 logger.log(level, "[Exception]");
7969             }
7970         }//endif(regs.length == 1)
7971         if(regs.length > 1) {
7972             try{
7973                 LookupLocator loc = regs[0].getLocator();
7974                 logger.log(level, "["+loc+",");
7975             }catch(SecurityException e){
7976                 logger.log(level, "[SecurityException,");
7977             }catch(Exception e){
7978                 logger.log(level, "[Exception,");
7979             }
7980             for(int i=1;i<(regs.length-1);i++){
7981                 try{
7982                     LookupLocator loc = regs[i].getLocator();
7983                     logger.log(level, loc+",");
7984                 }catch(SecurityException e){
7985                     logger.log(level, "SecurityException,");
7986                 }catch(Exception e){
7987                     logger.log(level, "Exception,");
7988                 }
7989             }//end loop
7990             try{
7991                 LookupLocator loc = regs[regs.length-1].getLocator();
7992                 logger.log(level, loc+"]");
7993             }catch(SecurityException e){
7994                 logger.log(level, "SecurityException]");
7995             }catch(Exception e){
7996                 logger.log(level, "Exception]");
7997             }
7998         }//endif(regs.length > 1)
7999     }//end writeRegistrarsArray
8000 
8001     /* Logs data on a single attribute to a file or standard output */
8002     private static void writeAttribute(Entry  attr,
8003                                        Logger logger,
8004                                        Level  level)
8005     {
8006         if((attr == null) || (logger == null) || !(logger.isLoggable(level))) {
8007             return;
8008         }
8009         if(attr instanceof BasicServiceType) {
8010             logger.log(level, "  attribute = BasicServiceType");
8011             logger.log(level, "    Display Name = "
8012                                +((BasicServiceType)(attr)).getDisplayName());
8013             logger.log(level, "    Description  = "
8014                           +((BasicServiceType)(attr)).getShortDescription());
8015         } else if(attr instanceof ServiceInfo) {
8016             logger.log(level, "  attribute = ServiceInfo");
8017             logger.log(level, "    Service Name         = "
8018                                        +((ServiceInfo)(attr)).name);
8019             logger.log(level, "    Service Manufacturer = "
8020                                       +((ServiceInfo)(attr)).manufacturer);
8021             logger.log(level, "    Service Vendor       = "
8022                                        +((ServiceInfo)(attr)).vendor);
8023             logger.log(level, "    Service Version      = "
8024                                        +((ServiceInfo)(attr)).version);
8025             logger.log(level, "    Service Model        = "
8026                                        +((ServiceInfo)(attr)).model);
8027             logger.log(level, "    Service Serial #     = "
8028                                       +((ServiceInfo)(attr)).serialNumber);
8029         } else {
8030             logger.log(level, "  attribute = "+attr);
8031         }//endif
8032     }//end writeAttribute
8033 
8034     /* Logs data on each attribute in a set to a file or standard output */
8035     private static void writeAttributes(Entry[] attrs,
8036                                         Logger  logger,
8037                                         Level   level)
8038     {
8039         if((attrs == null) || (logger == null) || !(logger.isLoggable(level))){
8040             return;
8041         }
8042         for(int i=0;i<attrs.length;i++) {
8043             if(attrs[i] == null) continue;
8044             writeAttribute(attrs[i],logger,level);
8045         }//end loop
8046         logger.log(level,"");
8047     }//end writeAttributes
8048 
8049     /* Logs startup data to file or standard output */
8050     private void logInfoStartup() {
8051         if (startupLogger.isLoggable(Level.INFO)) {
8052             startupLogger.log
8053                    (Level.INFO, "Fiddler started: {0}, {1}, {2}",
8054                     new Object[]
8055                         { (FiddlerImpl.this.serviceID).toString(),
8056                           writeGroupArrayToString(thisServicesGroups),
8057                           writeArrayElementsToString(thisServicesLocators) } );
8058         }//endif
8059         if( startupLogger.isLoggable(Level.CONFIG) ) {
8060             if(persistDir != null) {
8061                 startupLogger.log(Level.CONFIG,
8062                                   " Persistent state directory:  {0}",
8063                                   persistDir);
8064             }//endif
8065             startupLogger.log(Level.CONFIG,
8066                             "Attributes to register in each lookup service: ");
8067             writeAttributes(thisServicesAttrs,startupLogger,Level.CONFIG);
8068         }//endif
8069     }//end logInfoStartup
8070 
8071     /* Logs shutdown/destroy data to file or standard output */
8072     private void logInfoShutdown() {
8073         if (startupLogger.isLoggable(Level.INFO)) {
8074             startupLogger.log
8075                    (Level.INFO, "Fiddler destroyed: {0}, {1}, {2}",
8076                     new Object[]
8077                         { (FiddlerImpl.this.serviceID).toString(),
8078                           writeGroupArrayToString(thisServicesGroups),
8079                           writeArrayElementsToString(thisServicesLocators) } );
8080         }//endif
8081     }//end logInfoShutdown
8082 
8083     /* Logs information related to the run method of a particular task */
8084     private void logInfoTasks(String str) {
8085         if( tasksLogger.isLoggable(Level.FINEST) ) {
8086             tasksLogger.log(Level.FINEST, str);
8087         }//endif
8088     }//end logInfoTasks
8089 
8090     /* Logs information about events that are sent */
8091     private void logInfoEvents(String str) {
8092         if( eventsLogger.isLoggable(Level.FINE) ) {
8093             eventsLogger.log(Level.FINE, str);
8094         }//endif
8095     }//end logInfoEvents
8096 
8097     /* Logs information about events that are sent */
8098     private void logInfoEvents(Map  groupsMap,
8099                                long eventID,
8100                                long seqNum,
8101                                MarshalledObject handback,
8102                                boolean discarded,
8103                                Logger logger,
8104                                Level  level)
8105     {
8106         if( (logger == null) || !(logger.isLoggable(level)) ) {
8107             return;
8108         }
8109         String discardedStr = (discarded == true ? "DISCARDED":"DISCOVERED");
8110         Object hb = null;
8111         if(handback != null) {
8112             try {
8113                 hb = new MarshalledInstance(handback).get(false);
8114             } catch (ClassNotFoundException e) {
8115                 problemLogger.log(Levels.HANDLED,
8116                                   "ClassNotFoundException when "
8117                                   +"unmarshalling handback",e);
8118             } catch (IOException e) {
8119                 problemLogger.log(Levels.HANDLED,
8120                                   "IOException when unmarshalling "
8121                                   +"handback",e);
8122             }
8123         }//endif
8124         logger.log(level, "\n"+discardedStr+" Event:");
8125         logger.log(level, "  EventID  = "+eventID);
8126         logger.log(level, "  SeqNum   = "+seqNum);
8127         logger.log(level, "  handback = "+hb);
8128         ServiceRegistrar[] regs =
8129                           (ServiceRegistrar[])(groupsMap.keySet()).toArray
8130                                      (new ServiceRegistrar[groupsMap.size()]);
8131         logger.log(level, "  Registrars = ");
8132         writeRegistrarsArray(regs,logger,level);
8133         for(int i=0;i<regs.length;i++){
8134             String[] curGroups = (String[])groupsMap.get(regs[i]);
8135             logger.log(level, "  member groups ["+i+"] = ");
8136             writeGroupArray(curGroups,logger,level);
8137         }//end loop
8138         logger.log(level, "");
8139     }//end logInfoEvents
8140 
8141     /* Logs group state information over all active registrations */
8142     private void logInfoGroups() {
8143         String[] allGroups = discoveryMgr.getGroups();
8144         if( groupsLogger.isLoggable(Level.FINER) ) {
8145             groupsLogger.log(Level.FINER, 
8146                              "Group(s) over all registrations: ");
8147             writeGroupArray(allGroups,groupsLogger,Level.FINER);
8148         }//endif
8149     }//end logInfoGroups
8150 
8151     /* Logs group state information over all active registrations */
8152     private void logInfoGroups(String headerStr) {
8153         if( (headerStr != null) && (groupsLogger.isLoggable(Level.FINER)) ) {
8154             groupsLogger.log(Level.FINER, headerStr);
8155         }//endif
8156         logInfoGroups();
8157     }//end logInfoGroups
8158 
8159     /* Logs locator state information over all active registrations */
8160     private void logInfoLocators() {
8161         LookupLocator[] allLocators = discoveryMgr.getLocators();
8162         if( locatorsLogger.isLoggable(Level.FINER) ) {
8163             locatorsLogger.log(Level.FINER, 
8164                                "Locator(s) over all registrations: ");
8165             writeArrayElements(allLocators,locatorsLogger,Level.FINER);
8166         }//endif
8167     }//end logInfoLocators
8168 
8169     /* Logs information useful to debugging the discard process */
8170     private void logInfoDiscard(String str, Uuid regID) {
8171         if(    (str != null) && (regID != null)
8172             && (discardLogger.isLoggable(Level.FINE)) )
8173         {
8174             discardLogger.log(Level.FINE, str+" registrationID = "+regID);
8175         }//endif
8176     }//end logInfoDiscard
8177 
8178     /* Logs information useful to debugging the discard process */
8179     private void logInfoDiscard(String str) {
8180         if( discardLogger.isLoggable(Level.FINE) ) {
8181             discardLogger.log(Level.FINE, str);
8182         }//endif
8183     }//end logInfoDiscard
8184 
8185     /* Logs information useful to debugging the leasing mechanism */
8186     private void logInfoLease(String str, Uuid regID, Uuid leaseID) {
8187         if(    (str != null) && (regID != null)
8188             && (leaseLogger.isLoggable(Level.FINER)) )
8189         {
8190             leaseLogger.log(Level.FINER, str+" (registrationID,leaseID) = ("
8191                                          +regID+", "+leaseID+")");
8192         }//endif
8193     }//end logInfoLease
8194 
8195     /* Logs information useful to debugging the registration mechanism */
8196     private void logInfoRegistration(String str, Object regInfo) {
8197         if(    (str != null) && (regInfo != null)
8198             && (registrationLogger.isLoggable(Level.FINER)) )
8199         {
8200             registrationLogger.log(Level.FINER, str+" {0}", regInfo);
8201         }//endif
8202     }//end logInfoRegistration
8203 
8204     /* Logs information useful to debugging the logging mechanism */
8205     private static void logInfoPersist(String str) {
8206         if( persistLogger.isLoggable(Level.FINEST) ) {
8207             persistLogger.log(Level.FINEST, str);
8208         }//endif
8209     }//end logInfoPersist
8210 
8211     /* Logs information useful to debugging the addLogRecord method */
8212     private void logInfoAddLogRecord(LogRecord rec) {
8213         if( !(persistLogger.isLoggable(Level.FINEST)) )  return;
8214         String logStr = "Logging a state change: Unknown log record instance";
8215         /* JoinAdmin */
8216         if(rec instanceof LookupAttrsAddedLogObj) {
8217             logStr = "Logging state change: lookup attributes added";
8218         } else if (rec instanceof LookupAttrsModifiedLogObj) {
8219             logStr = "Logging state change: lookup attributes modified";
8220         } else if (rec instanceof LookupGroupsChangedLogObj) {
8221             logStr = "Logging state change: groups to join changed to "
8222                      +writeGroupArrayToString(thisServicesGroups);
8223         } else if (rec instanceof LookupLocatorsChangedLogObj) {
8224             logStr = "Logging state change: locators to join changed to "
8225                      +writeArrayElementsToString(thisServicesLocators);
8226         /* FiddlerAdmin */
8227         } else if (rec instanceof LeaseBoundSetLogObj) {
8228             logStr = "Logging state change: lease duration bound changed";
8229         } else if (rec instanceof SnapshotWeightSetLogObj) {
8230             logStr = "Logging state change: snapshot weight factor changed";
8231         } else if (rec instanceof SnapshotThresholdSetLogObj) {
8232             logStr = "Logging state change: log-to-snapshot threshold changed";
8233         /* LookupDiscoveryService */
8234         } else if (rec instanceof RegistrationGrantedLogObj) {
8235             logStr = "Logging state change: new registration granted";
8236         /* LookupDiscoveryRegistration */
8237         } else if (rec instanceof GroupsAddedToRegistrationLogObj) {
8238             logStr = "Logging state change: added new groups to "
8239                      +"registration's set of groups to discover";
8240         } else if (rec instanceof GroupsSetInRegistrationLogObj) {
8241             logStr = "Logging state change: replaced registration's set of "
8242                      +"groups to discover";
8243         } else if (rec instanceof GroupsRemovedFromRegistrationLogObj) {
8244             logStr = "Logging state change: removed groups from "
8245                      +"registration's set of groups to discover";
8246         } else if (rec instanceof LocsAddedToRegistrationLogObj) {
8247             logStr = "Logging state change: added new locators to "
8248                      +"registration's set of locators to discover";
8249         } else if (rec instanceof LocsSetInRegistrationLogObj) {
8250             logStr = "Logging state change: replaced registration's set of "
8251                      +"locators to discover";
8252         } else if (rec instanceof LocsRemovedFromRegistrationLogObj) {
8253             logStr = "Logging state change: removed locators from "
8254                      +"registration's set of locators to discover";
8255         } else if (rec instanceof LeaseRenewedLogObj) {
8256             logStr = "Logging state change: registration's lease renewed";
8257         } else if (rec instanceof LeasesRenewedLogObj) {
8258             logStr = "Logging state change: set of leases renewed for a "
8259                      +"set of registrations";
8260         } else if (rec instanceof LeaseCancelledLogObj) {
8261             logStr = "Logging state change: registration's lease cancelled";
8262         } else if (rec instanceof LeasesCancelledLogObj) {
8263             logStr = "Logging state change: set of leases cancelled for a "
8264                      +"set of registrations";
8265         }//endif
8266         logInfoPersist(logStr);
8267     }//end logInfoAddLogRecord
8268     /* END Private Logging Facility Methods -------------------------------- */
8269     /* *************** END Private NON-Static Utility Methods ************** */
8270 
8271 }/*end class FiddlerImpl */