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 net.jini.lookup;
19  
20  import java.io.IOException;
21  import java.rmi.RemoteException;
22  import java.security.AccessControlContext;
23  import java.security.AccessController;
24  import java.security.PrivilegedAction;
25  import java.security.PrivilegedActionException;
26  import java.security.PrivilegedExceptionAction;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.concurrent.Callable;
32  import java.util.concurrent.ConcurrentLinkedQueue;
33  import java.util.concurrent.CopyOnWriteArrayList;
34  import java.util.concurrent.ExecutorService;
35  import java.util.concurrent.Future;
36  import java.util.concurrent.LinkedBlockingQueue;
37  import java.util.concurrent.RunnableFuture;
38  import java.util.concurrent.ThreadPoolExecutor;
39  import java.util.concurrent.TimeUnit;
40  import java.util.logging.Level;
41  import java.util.logging.Logger;
42  import net.jini.config.Configuration;
43  import net.jini.config.ConfigurationException;
44  import net.jini.config.EmptyConfiguration;
45  import net.jini.config.NoSuchEntryException;
46  import net.jini.core.entry.CloneableEntry;
47  import net.jini.core.entry.Entry;
48  import net.jini.core.lease.Lease;
49  import net.jini.core.lease.UnknownLeaseException;
50  import net.jini.core.lookup.ServiceID;
51  import net.jini.core.lookup.ServiceItem;
52  import net.jini.core.lookup.ServiceRegistrar;
53  import net.jini.core.lookup.ServiceRegistration;
54  import net.jini.discovery.DiscoveryEvent;
55  import net.jini.discovery.DiscoveryListener;
56  import net.jini.discovery.DiscoveryManagement;
57  import net.jini.discovery.LookupDiscoveryManager;
58  import net.jini.lease.LeaseListener;
59  import net.jini.lease.LeaseRenewalEvent;
60  import net.jini.lease.LeaseRenewalManager;
61  import net.jini.security.BasicProxyPreparer;
62  import net.jini.security.ProxyPreparer;
63  import org.apache.river.constants.ThrowableConstants;
64  import org.apache.river.logging.LogUtil;
65  import org.apache.river.lookup.entry.LookupAttributes;
66  import org.apache.river.thread.DependencyLinker;
67  import org.apache.river.thread.ExtensibleExecutorService;
68  import org.apache.river.thread.ExtensibleExecutorService.RunnableFutureFactory;
69  import org.apache.river.thread.FutureObserver;
70  import org.apache.river.thread.NamedThreadFactory;
71  import org.apache.river.thread.wakeup.RetryTask;
72  import org.apache.river.thread.wakeup.WakeupManager;
73  
74  /**
75   * A goal of any well-behaved service is to advertise the facilities and
76   * functions it provides by requesting residency within at least one lookup
77   * service. Making such a request of a lookup service is known as registering
78   * with, or <i>joining</i>, a lookup service. To demonstrate this good
79   * behavior, a service must comply with both the multicast discovery protocol
80   * and the unicast discovery protocol in order to discover the lookup services
81   * it is interested in joining. The service must also comply with the join
82   * protocol to register with the desired lookup services. 
83   * <p>
84   * In order for a service to maintain its residency in the lookup services
85   * it has joined, the service must provide for the coordination, systematic
86   * renewal, and overall management of all leases on that residency. In
87   * addition to handling all discovery and join duties, as well as managing
88   * all leases on lookup service residency, the service must also provide
89   * for the coordination and management of any attribute sets with which
90   * it may have registered with the lookup services in which it resides.
91   * <p>
92   * This class performs all of the functions related to discovery, joining,
93   * service lease renewal, and attribute management which is required of a
94   * well-behaved service. Each of these activities is intimately involved
95   * with the maintenance of a service's residency in one or more lookup
96   * services (the service's join state), thus the name <code>JoinManager</code>.
97   * <p>
98   * This class should be employed by services, not clients. The use of this
99   * class in a wide variety of services can help minimize the work resulting
100  * from having to repeatedly implement this required functionality in each
101  * service. Note that this class is not remote. Services that wish to use
102  * this class will create an instance of this class in the service's address
103  * space to manage the entity's join state locally.
104  *
105  *  <!-- Implementation Specifics -->
106  *
107  * The following implementation-specific items are discussed below:
108  * <ul><li> <a href="#jmConfigEntries">Configuring JoinManager</a>
109  *     <li> <a href="#jmLogging">Logging</a>
110  * </ul>
111  *
112  * <a name="jmConfigEntries">
113  * <b><font size="+1">Configuring JoinManager</font></b>
114  * </a>
115  *
116  * This implementation of <code>JoinManager</code> supports the following
117  * configuration entries; where each configuration entry name is associated
118  * with the component name <code>net.jini.lookup.JoinManager</code>. Note
119  * that the configuration entries specified here are specific to this
120  * implementation of <code>JoinManager</code>. Unless otherwise stated, each
121  * entry is retrieved from the configuration only once per instance of
122  * this utility, where each such retrieval is performed in the constructor.
123  * 
124  * <a name="discoveryManager"></a>
125  * <table summary="Describes the discoveryManager configuration entry" 
126  *                border="0" cellpadding="2">
127  *   <tr valign="top">
128  *     <th scope="col"> <font size="+1">&#X2022;</font>
129  *     <th scope="col" align="left" colspan="2"> <font size="+1">
130  *     <code>discoveryManager</code></font>
131  * 
132  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
133  *     Type: <td> {@link net.jini.discovery.DiscoveryManagement}
134  * 
135  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
136  *     Default: <td> <code> new 
137  *    {@link net.jini.discovery.LookupDiscoveryManager#LookupDiscoveryManager(
138  *       java.lang.String[],
139  *       net.jini.core.discovery.LookupLocator[],
140  *       net.jini.discovery.DiscoveryListener,
141  *       net.jini.config.Configuration) LookupDiscoveryManager}(
142  *                       new java.lang.String[] {""},
143  *                       new {@link net.jini.core.discovery.LookupLocator}[0],
144  *                       null, config)</code>
145  * 
146  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
147  *     Description:
148  *       <td> The object used to manage the discovery processing
149  *            performed by this utility. This entry will be retrieved
150  *            from the configuration only if no discovery manager is
151  *            specified in the constructor. Note that this object should
152  *            not be shared with other components in the application that
153  *            employs this utility.
154  * </table>
155  * 
156  * <a name="leaseManager"></a>
157  * <table summary="Describes the leaseManager configuration entry" 
158  *                border="0" cellpadding="2">
159  *   <tr valign="top">
160  *     <th scope="col" > <font size="+1">&#X2022;</font>
161  *     <th scope="col" align="left" colspan="2"> <font size="+1">
162  *     <code>leaseManager</code></font>
163  * 
164  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
165  *     Type: <td> {@link net.jini.lease.LeaseRenewalManager}
166  * 
167  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
168  *     Default: <td> <code> new 
169  *       {@link net.jini.lease.LeaseRenewalManager#LeaseRenewalManager(
170  *         net.jini.config.Configuration) LeaseRenewalManager}(config)</code>
171  * 
172  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
173  *     Description:
174  *       <td> The object used to manage each service lease returned
175  *            to this utility when the service is registered with the
176  *            the various discovered lookup services. This entry will
177  *            be retrieved from the configuration only if no lease 
178  *            renewal manager is specified in the constructor.
179  * </table>
180  * 
181  * <a name="maxLeaseDuration"></a>
182  * <table summary="Describes the maxLeaseDuration
183  *                configuration entry" border="0" cellpadding="2">
184  *   <tr valign="top">
185  *     <th scope="col" > <font size="+1">&#X2022;</font>
186  *     <th scope="col" align="left" colspan="2"> <font size="+1">
187  *     <code>maxLeaseDuration</code></font>
188  * 
189  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
190  *     Type: <td> <code>long</code>
191  * 
192  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
193  *     Default: <td> <code>Lease.FOREVER</code>
194  * 
195  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
196  *     Description:
197  *       <td> The maximum lease duration (in milliseconds) that is requested
198  *            from each discovered lookup service on behalf of the service;
199  *            both when the lease is initially requested, as well as when 
200  *            renewal of that lease is requested. Note that as this value is
201  *            made smaller, renewal requests will be made more frequently
202  *            while the service is up, and lease expiration will occur sooner
203  *            when the service goes down.
204  * </table>
205  * 
206  * <a name="registrarPreparer"></a>
207  * <table summary="Describes the registrarPreparer configuration entry" 
208  *                border="0" cellpadding="2">
209  *   <tr valign="top">
210  *     <th scope="col" > <font size="+1">&#X2022;</font>
211  *     <th scope="col" align="left" colspan="2"> <font size="+1">
212  *     <code>registrarPreparer</code></font>
213  * 
214  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
215  *       Type: <td> {@link net.jini.security.ProxyPreparer}
216  * 
217  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
218  *       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
219  *                     </code>
220  * 
221  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
222  *   Description:
223  *     <td> Preparer for the proxies to the lookup services that are
224  *          discovered and used by this utility. 
225  *          <p>
226  *          The following methods of the proxy returned by this preparer are
227  *          invoked by this utility:
228  *       <ul>
229  *         <li>{@link net.jini.core.lookup.ServiceRegistrar#register register}
230  *       </ul>
231  * </table>
232  * 
233  * <a name="registrationPreparer"></a>
234  * <table summary="Describes the registrationPreparer configuration entry" 
235  *                border="0" cellpadding="2">
236  *   <tr valign="top">
237  *     <th scope="col" > <font size="+1">&#X2022;</font>
238  *     <th scope="col" align="left" colspan="2"> <font size="+1">
239  *     <code>registrationPreparer</code></font>
240  * 
241  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
242  *       Type: <td> {@link net.jini.security.ProxyPreparer}
243  * 
244  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
245  *       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
246  *                     </code>
247  * 
248  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
249  *   Description:
250  *     <td> Preparer for the proxies to the registrations returned to
251  *          this utility upon registering the service with each discovered
252  *          lookup service.
253  *          <p>
254  *          The following methods of the proxy returned by this preparer are
255  *          invoked by this utility:
256  *       <ul>
257  *         <li>{@link net.jini.core.lookup.ServiceRegistration#getServiceID
258  *                                                           getServiceID}
259  *         <li>{@link net.jini.core.lookup.ServiceRegistration#getLease
260  *                                                           getLease}
261  *         <li>{@link net.jini.core.lookup.ServiceRegistration#addAttributes
262  *                                                           addAttributes}
263  *         <li>{@link net.jini.core.lookup.ServiceRegistration#modifyAttributes
264  *                                                           modifyAttributes}
265  *         <li>{@link net.jini.core.lookup.ServiceRegistration#setAttributes
266  *                                                           setAttributes}
267  *       </ul>
268  * </table>
269  * 
270  * <a name="serviceLeasePreparer"></a>
271  * <table summary="Describes the serviceLeasePreparer configuration entry" 
272  *                border="0" cellpadding="2">
273  *   <tr valign="top">
274  *     <th scope="col" > <font size="+1">&#X2022;</font>
275  *     <th scope="col" align="left" colspan="2"> <font size="+1">
276  *     <code>serviceLeasePreparer</code></font>
277  * 
278  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
279  *       Type: <td> {@link net.jini.security.ProxyPreparer}
280  * 
281  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
282  *       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
283  *                     </code>
284  * 
285  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
286  *   Description:
287  *     <td> Preparer for the leases returned to this utility through
288  *          the registrations with each discovered lookup service with
289  *          which this utility has registered the service.
290  *          <p>
291  *          Currently, none of the methods on the service lease returned
292  *          by this preparer are invoked by this implementation of the utility.
293  * </table>
294  * 
295  * <a name="executorService"></a>
296  * <table summary="Describes the executorService configuration entry" 
297  *                border="0" cellpadding="2">
298  *   <tr valign="top">
299  *     <th scope="col" > <font size="+1">&#X2022;</font>
300  *     <th scope="col" align="left" colspan="2"> <font size="+1">
301  *     <code>executorService</code></font>
302  * 
303  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
304  *     Type: <td> {@link java.util.concurrent.ExecutorService ExecutorService}
305  * 
306  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
307  *     Default: <td> <code>new 
308  *             {@link java.util.concurrent.ThreadPoolExecutor ThreadPoolExecutor}(
309  *                   15,
310  *                   15,
311  *                   15,
312  *                   TimeUnit.SECONDS,
313  *                   new {@link java.util.concurrent.LinkedBlockingQueue LinkedBlockingQueue}(),
314  *                   new {@link NamedThreadFactory NamedThreadFactory}("JoinManager executor thread", false))</code>
315  * 
316  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
317  *     Description:
318  *       <td> The object that pools and manages the various threads
319  *            executed by this utility. This object
320  *            should not be shared with other components in the
321  *            application that employs this utility.
322  * </table>
323  *
324  * <a name="wakeupManager"></a>
325  * <table summary="Describes the wakeupManager configuration entry" 
326  *                border="0" cellpadding="2">
327  *   <tr valign="top">
328  *     <th scope="col" > <font size="+1">&#X2022;</font>
329  *     <th scope="col" align="left" colspan="2"> <font size="+1">
330  *     <code>wakeupManager</code></font>
331  * 
332  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
333  *     Type: <td> {@link org.apache.river.thread.wakeup.WakeupManager}
334  * 
335  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
336  *     Default: <td> <code>new 
337  *     {@link org.apache.river.thread.wakeup.WakeupManager#WakeupManager(
338  *          org.apache.river.thread.wakeup.WakeupManager.ThreadDesc)
339  *     WakeupManager}(new 
340  *     {@link org.apache.river.thread.wakeup.WakeupManager.ThreadDesc}(null,true))</code>
341  * 
342  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
343  *     Description:
344  *       <td> Object that pools and manages the various tasks that are
345  *            initially executed by the object corresponding to the
346  *            <a href="#executorService"><code>executorService</code></a> entry
347  *            of this component, but which fail during that initial execution.
348  *            This object schedules the re-execution of such a failed task -
349  *            in the <a href="#executorService"><code>executorService</code></a>
350  *            object - at various times in the future, until either the
351  *            task succeeds or the task has been executed the maximum
352  *            number of allowable times, corresponding to the 
353  *            <a href="#wakeupRetries"><code>wakeupRetries</code></a>
354  *            entry of this component. This object should not be shared
355  *            with other components in the application that employs this
356  *            utility.
357  * </table>
358  * 
359  * <a name="wakeupRetries"></a>
360  * <table summary="Describes the wakeupRetries
361  *                configuration entry" border="0" cellpadding="2">
362  *   <tr valign="top">
363  *     <th scope="col" > <font size="+1">&#X2022;</font>
364  *     <th scope="col" align="left" colspan="2"> <font size="+1">
365  *     <code>wakeupRetries</code></font>
366  * 
367  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
368  *     Type: <td> <code>int</code>
369  * 
370  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
371  *     Default: <td> <code>6</code>
372  * 
373  *   <tr valign="top"> <td> &nbsp; <th scope="row" align="right">
374  *     Description:
375  *       <td> The maximum number of times a failed task is allowed to
376  *            be executed by the object corresponding to the 
377  *            <a href="#wakeupManager"><code>wakeupManager</code></a>
378  *            entry of this component.
379  * </table>
380  * 
381  * <a name="jmLogging">
382  * 
383  * <b><font size="+1">Logging</font></b>
384  * 
385  * </a>
386  *
387  * This implementation of <code>JoinManager</code> uses the
388  * {@link Logger} named <code>net.jini.lookup.JoinManager</code>
389  * to log information at the following logging levels: <p>
390  *
391  * <table border="1" cellpadding="5"
392  *         summary="Describes the information logged by JoinManager,
393  *                 and the levels at which that information is logged">
394  *
395  * <caption>
396  *   <b><code>net.jini.lookup.JoinManager</code></b>
397  * </caption>
398  *
399  * <tr> <th scope="col"> Level</th>
400  *      <th scope="col"> Description</th>
401  * </tr>
402  *
403  * <tr>
404  *   <td>{@link java.util.logging.Level#INFO INFO}</td>
405  *   <td>when a task is stopped because of a definite exception</td>
406  * </tr>
407  * <tr>
408  *   <td>{@link java.util.logging.Level#INFO INFO}</td>
409  *   <td>
410  *     when a task is stopped because it has exceeded the maximum number of
411  *     times the task is allowed to be tried/re-tried
412  *   </td>
413  * </tr>
414  * <tr>
415  *   <td>{@link java.util.logging.Level#INFO INFO}</td>
416  *   <td>when any exception occurs while attempting to prepare a proxy</td>
417  * </tr>
418  * <tr>
419  *   <td>{@link java.util.logging.Level#FINER FINER}</td>
420  *   <td>
421  *     when any exception (other than the more serious exceptions logged
422  *     at higher levels) occurs in a task
423  *   </td>
424  * </tr>
425  * <tr>
426  *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
427  *   <td>
428  *     when an <code>IllegalStateException</code> occurs upon attempting to
429  *     discard a lookup service
430  *   </td>
431  * </tr>
432  *  <tr>
433  *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
434  *   <td>whenever any task is started</td>
435  * </tr>
436  * 
437  * <tr>
438  *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
439  *   <td>whenever any task completes successfully</td>
440  * </tr>
441  *
442  * <tr>
443  *   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
444  *   <td>whenever a proxy is prepared</td>
445  * </tr>
446  * </table>
447  * <p>
448  *
449  * @author Sun Microsystems, Inc.
450  *
451  * @see net.jini.discovery.DiscoveryManagement
452  * @see net.jini.lease.LeaseRenewalManager
453  * @see java.util.logging.Level
454  * @see java.util.logging.Logger
455  */
456 public class JoinManager {
457     
458     /** Implementation Note:
459      *
460      *  This class executes a number of tasks asynchronously. Each task is
461      *  initially queued in a <code>java.util.concurrent.ExecutorService</code>
462      *  instance, which executes each task in a separate thread. In this
463      *  way, an upper bound is placed on the number of threads executing
464      *  concurrently at any one time; that is, the number of concurrent
465      *  threads is "throttled".
466      *
467      *  In addition to throttling the number of concurrent threads, the
468      *  use of an ExecutorService, in conjunction with the task configuration,
469      *  provides a level of resiliency with respect to down/unresponsive
470      *  lookup services.
471      *
472      *  Recall from the specification that the primary function of a join
473      *  manager is to maintain and update the state (registrations,
474      *  attribute augmentations/replacements/changes, etc.) of the join
475      *  manager's single associated service with all of the desired lookup
476      *  services. Because updating a service's state in a lookup service
477      *  involves remote communication, such update operations are performed
478      *  asynchronously, in separate tasks. If those operations are not
479      *  performed in separate tasks, then a communication problem with
480      *  one of the lookup services while performing one operation could
481      *  prevent (or significantly slow) the execution of all future
482      *  state update operations; causing all processing in the join manager
483      *  to degrade or even hang indefinitely.
484      *
485      *  Although performing each update operation in a separate task thread
486      *  prevents a down/unresponsive lookup service from blocking other
487      *  processing in the join manager, it does not guarantee consistency
488      *  of the service's state in each "up" lookup service, with the state
489      *  expected by the client that requested the state changes.
490      *
491      *  For example, suppose the client first requests that the service's
492      *  attributes be replaced with a new set, and then immediately after
493      *  the attribute replacement request, the client requests that the
494      *  service's attributes be augmented by yet another set of attributes.
495      *  Unless control is imposed on the order of execution of the tasks
496      *  that perform the attribute replacement and augmentation, there is
497      *  a possibility that the service's state in one or more of the lookup
498      *  service's will have experienced the attribute augmentation prior to
499      *  the attribute replacement, rather than the replacement followed by
500      *  the augmentation, as the client would have expected. Thus, not only
501      *  can the service's state in one or more of the lookup services be
502      *  inconsistent with what the client expects, but it can also be
503      *  inconsistent with one or more of the other lookup services with
504      *  which the service is registered.
505      *  
506      *  To prevent the possibility of inconsistencies such as those just
507      *  described, the state update operations are grouped according to
508      *  the particular lookup service with which they are associated. Each
509      *  such grouping is implemented as a task, executed by the task
510      *  manager employed by this implementation of <code>JoinManager</code>,
511      *  containing a queue of sub-tasks (the actual state update operations)
512      *  that are executed by the main task in the same order in which they
513      *  were requested by the client.
514      *  
515      *  Each main task executed by this join manager's ExecutorService is a
516      *  sub-class of the abstract class <code>RetryTask</code>, defined in
517      *  the package <code>org.apache.river.thread</code>, which implements
518      *  the <code>org.apache.river.thread.TaskManager.Task</code> interface.
519      *  The association of each such task with a particular lookup service,
520      *  and with a unique sequence number is reflected in the fields of this
521      *  class. The unique sequence number associated with each main task
522      *  is exploited by the <code>runAfter</code> method defined in 
523      *  <code>RetryTask</code> to guarantee that the service managed by
524      *  this <code>JoinManager</code> is assigned only one service ID.
525      *   
526      *  Because of the relationship between the main tasks and 
527      *  <code>RetryTask</code>, those concrete main tasks created in this
528      *  implementation of <code>JoinManager</code> can be processed by either
529      *  an ExecutorService or a wakeup manager; and this fact is exploited to
530      *  provide a failure recovery mechanism that can tolerate task "storms".
531      *  Task storms can occur in systems that contain numerous services,
532      *  where each service employs its own join manager to handle its join
533      *  state with the desired lookup services. A "registration request storm"
534      *  is an example of one type of task storm.
535      *
536      *  A registration request storm can occur when a system is initially
537      *  started, or when it recovers from a system-wide failure, when each
538      *  service's join manager attempts to register with each discovered
539      *  lookup service; resulting in a flood - or "storm" - of registration
540      *  requests at each lookup service. Because the accept queue under some
541      *  operating systems is - by default - very small, it is possible that,
542      *  in the face of such a large number of concurrent requests, one or
543      *  more of the lookup services will not be able to process the requests
544      *  as fast as they are arriving in the queue. When this happens, the
545      *  associated task in the join manager typically encounters a failure
546      *  such as a <code>java.rmi.ConnectException</code>.
547      *
548      *  To deal with situations such as that described above, this
549      *  <code>JoinManager</code> implementation employs a wakeup manager
550      *  in addition to an ExecutorService. When a main task is created, it is
551      *  initially executed by an ExecutorService. If a failure is encountered,
552      *  and if the nature of that failure indicates that retrying the task
553      *  will <i>definitely</i> fail again, then the associated lookup service
554      *  is discarded so that the task can be retried when the lookup service
555      *  is rediscovered. But if it is not clear that retrying the task will
556      *  again fail, (that is, the failure is <i>indefinite</i>), then the
557      *  task is queued for re-execution at a later time in a wakeup manager.
558      *  This process is repeated - employing a "backoff" strategy - until one
559      *  of the following events occurs:
560      *
561      *  <p><ul>
562      *     <li> the task succeeds 
563      *     <li> a definite failure is encountered
564      *     <li> the task has been executed the maximum number times allowed 
565      *  </ul><p>
566      *  
567      *  The maximum number of times a task is allowed to be retried before
568      *  giving up and discarding the associated lookup service can be changed
569      *  from its default value by setting the <code>wakeupRetries</code>
570      *  configuration entry for this component.
571      *
572      *  @see org.apache.river.thread.TaskManager
573      *  @see org.apache.river.thread.wakeup.WakeupManager
574      *  @see org.apache.river.thread.TaskManager.Task
575      *  @see org.apache.river.thread.RetryTask
576      *  @see org.apache.river.constants.ThrowableConstants
577      */
578 
579     /** Abstract base class from which all of the task classes are derived. */
580     private class ProxyRegTask extends RetryTask implements Comparable<ProxyRegTask> {
581         private final long[] sleepTime = { 5*1000, 10*1000, 15*1000,
582                                           20*1000, 25*1000, 30*1000 };
583         // volatile fields only mutated while synchronized on proxyReg.taskList
584         private volatile int tryIndx  = 0;
585         private volatile int nRetries = 0;
586         private final ProxyReg proxyReg;
587         private final int seqN;
588 
589         /** Basic constructor; simply stores the input parameters */
590         ProxyRegTask(ProxyReg proxyReg, int seqN) {
591             super(JoinManager.this.executor,JoinManager.this.wakeupMgr);
592             this.proxyReg = proxyReg;
593             this.seqN = seqN;
594         }//end constructor
595 
596         /** Executes the current instance of this task once, queuing it
597          *  for retry at a later time and returning <code>false</code>
598          *  upon failure. This method attempts to execute all of the tasks
599          *  associated with the lookup service referenced in this task's 
600          *  <code>proxyReg</code> field. Order of execution is important,
601          *  and this method executes the tasks in the <code>proxyReg</code>'s
602          *  <code>taskList</code> in a FIFO order.
603          *
604          *  Note that tasks may be added to the <code>taskList</code> of
605          *  the <code>proxyReg</code> during the execution of this method.
606          *
607          *  Upon successfully executing all of the tasks in the 
608          *  <code>taskList</code>, this method returns <code>true</code>
609          *  and the current instance of this task is not executed again.
610          *  For each unsuccessful execution of a task in the 
611          *  <code>taskList</code>, this method returns <code>false</code>,
612          *  which causes the task to be scheduled by the
613          *  <code>WakeupManager</code> to be executed again at a later
614          *  time, as indicated by the value returned by <code>retryTime</code>.
615          */
616         @Override
617         public boolean tryOnce() {
618             while(true) {
619                 JoinTask t;
620                 synchronized(proxyReg.taskList) {
621                     if(proxyReg.taskList.isEmpty()) {
622                         proxyReg.proxyRegTask = null;
623                         return true;
624                     }//endif
625                     t = proxyReg.taskList.get(0);
626                 }//end sync
627                 try {
628                     t.runTask();
629                     synchronized(proxyReg.taskList) {
630                         if( !proxyReg.taskList.isEmpty() ) {
631                             JoinTask task = proxyReg.taskList.get(0);
632                             if (task == t) proxyReg.taskList.remove(0);
633                         }//endif
634                         /* reset the retry info for the next task in the list */
635                         tryIndx  = 0;
636                         nRetries = 0;
637                     }//end sync
638                     
639                 } catch (Exception e) {
640                     return stopTrying(e);
641                 }
642             }//end loop
643 	}//end tryOnce
644 
645         /** Returns the next absolute time (in milliseconds) at which another
646          *  execution of this task should be made (after the previous
647          *  attempt has failed).
648          */
649         @Override
650         public long retryTime() {
651 	    long nextTryTime = System.currentTimeMillis();
652             synchronized (proxyReg.taskList){
653 		nextTryTime += sleepTime[tryIndx];
654                 if(tryIndx < sleepTime.length-1)  tryIndx++;//don't go past end
655                     nRetries++;
656             }
657             return nextTryTime;
658         }//end retryTime
659 
660         /** Returns true if the current instance of this task must be run
661          *  after any task already in the ExecutorService queue.
662          *  
663          *  It is important that when the join manager is constructed with
664          *  a <code>null</code> service ID (where it is desired that 
665          *  a unique service ID be generated on the service's behalf),
666          *  that only the first task in the ExecutorService's queue be run; no
667          *  other tasks in the queue should be run while that first task
668          *  is running. This is because the first sub-task executed by
669          *  the first main task in the ExecutorService's queue will always be
670          *  a <code>RegisterTask</code>. And during the execution of that
671          *  first sub-task (if the service ID has not yet been set), the
672          *  service ID generated by the associated lookup service is retrieved
673          *  and stored for use in all future lookup service registration
674          *  tasks, Once the service ID is set by that first registration
675          *  sub-task, all future main tasks (and their associated registration
676          *  sub-tasks) can be run in parallel; each using the same service ID.
677          *  If this is not done, then the registration sub-tasks would be
678          *  run in parallel, each assigning a different ID to the service.
679          *
680          *  This method guarantees that until the service's ID is set,
681          *  only one registration sub-task is run; that is, one task
682          *  doesn't start until the currently running task has completed,
683          *  and a non-<code>null</code> service ID is assigned to the service.
684          *  
685          *  Executing the main tasks sequentially until the service ID is 
686          *  retrieved and stored must also be guaranteed because the currently
687          *  running registration task may fail to register the service
688          *  (because of a <code>RemoteException</code>), and thus may fail
689          *  to obtain an ID for the service. This method guarantees then
690          *  that each main task (and thus, each registration sub-task) will
691          *  run in sequence until one of those tasks completes successfully;
692          *  and from that point on, this method guarantees that all other
693          *  queued tasks will run in parallel.
694          *  
695          *  @param tasks the tasks with which to compare the current task
696          *  @param size  elements with index less than size are considered
697          */
698         public boolean dependsOn(ProxyRegTask t) {
699             return seqN > t.getSeqN();
700         }
701         
702         /* If the service's ID has already been set, then it's okay
703          * to run all ProxyRegTask's in parallel, otherwise, the
704          * ProxyRegTask with the lowest sequence number should be run.
705          */
706         public boolean hasDeps(){
707             return serviceItem.serviceID == null;
708         }
709 
710         /** Accessor method that returns the instance of <code>ProxyReg</code>
711          *  (the lookup service) associated with the task represented by
712          *  the current instance of this class.
713          */
714         public ProxyReg getProxyReg() {
715             return proxyReg;
716         }//end getProxy
717 
718         /** Accessor method that returns the unique sequence number associated
719          *  with the task represented by the current instance of this class.
720          */
721         public int getSeqN() {
722             return seqN;
723         }//end getSeqN
724 
725         /** Convenience method called by the child tasks when they encounter
726          *  an exception. If the given exception indicates that retrying the
727          *  task would definitely fail, or if the maximum allowable number
728          *  of retries of the task has been exceeded, then this method will
729          *  do the following:
730          *    - remove all pending tasks that are to be run after this task
731          *    - cancel this task
732          *    - discard the look service associated with this task
733          *    - return <code>true</code> (which stops the wakeup manager
734          *      from retrying this task
735          *  otherwise, this method returns <code>false</code>, which indicates
736          *  that the wakeup manager should not stop trying to successfully
737          *  execute the task.
738          */
739         protected boolean stopTrying(Exception e) {
740             int exCat = ThrowableConstants.retryable(e);
741             if(    (exCat != ThrowableConstants.INDEFINITE)
742                 || (nRetries >= maxNRetries) )
743             {
744                 removeTasks(proxyReg);//cancel and clear all related tasks
745                 proxyReg.fail(e);
746                 return true;//don't try again
747             }//endif
748             logger.log(Level.FINER,
749                        "JoinManager - failure, will retry later", e);
750             return false;//try this task again later
751         }//end stopTrying
752 
753         @Override
754         public int compareTo(ProxyRegTask o) {
755             if (seqN < o.seqN) return -1;
756             if (seqN > o.seqN) return 1;
757             return 0;
758         }
759 	
760 	@Override
761 	public boolean equals(Object o){
762 	    if (!(o instanceof ProxyRegTask)) return false;
763 	    ProxyRegTask that = (ProxyRegTask) o;
764 	    if (this.seqN != that.seqN) return false;
765 	    return this.proxyReg.equals(that.proxyReg);
766 	}
767 
768 	@Override
769 	public int hashCode() {
770 	    int hash = 5;
771 	    hash = 79 * hash + (this.proxyReg != null ? this.proxyReg.hashCode() : 0);
772 	    hash = 79 * hash + this.seqN;
773 	    return hash;
774 	}
775     }//end class ProxyRegTask
776     
777         private static final class ProxyRegTaskQueue implements FutureObserver {
778         // CacheTasks pending completion.
779         private final ConcurrentLinkedQueue<ProxyRegTask> pending;
780         private final ExecutorService executor;
781         
782         private ProxyRegTaskQueue(ExecutorService e){
783             this.pending = new ConcurrentLinkedQueue<ProxyRegTask>();
784             executor = e;
785         }
786         
787         private Future submit(ProxyRegTask t){
788             pending.offer(t);
789             t.addObserver(this);
790             if (t.hasDeps()) {
791                 List<ObservableFuture> deps = new LinkedList<ObservableFuture>();
792                 Iterator<ProxyRegTask> it = pending.iterator();
793                 while (it.hasNext()){
794                     ProxyRegTask c = it.next();
795                     if (t.dependsOn(c)) {
796                         deps.add(c);
797                     }
798                 }
799                 if (deps.isEmpty()){
800                     executor.submit(t);
801                 } else {
802                     DependencyLinker linker = new DependencyLinker(executor, deps, t);
803                     linker.register();
804                 }
805             } else {
806                 executor.submit(t);
807             }
808             return t;
809         }
810 
811         @Override
812         public void futureCompleted(Future e) {
813             pending.remove(e);
814         }
815     }
816 
817     /** Abstract base class from which all the sub-task classes are derived. */
818     private static abstract class JoinTask {
819 
820         /** Data structure referencing the task's associated lookup service */
821         protected final ProxyReg proxyReg;
822 	private final AccessControlContext context;
823 
824         /** Basic constructor; simply stores the input parameters */
825         JoinTask(ProxyReg proxyReg, AccessControlContext context) {
826             this.proxyReg = proxyReg;
827 	    this.context = context;
828         }//end constructor
829 	
830 	void runTask() throws Exception {
831 	    try {
832 		AccessController.doPrivileged(
833 			new PrivilegedExceptionAction(){
834 			    
835 			    public Object run() throws Exception {
836 				JoinTask.this.run();
837 				return null;
838 			    }
839 			}, context
840 		);
841 	    } catch (PrivilegedActionException ex) {
842 		throw ex.getException();
843 	    }
844 	}
845 
846         /** Method executed in a separate thread created by the ExecutorService */
847 	public abstract void run() throws Exception;
848 
849     }//end class JoinTask
850 
851     /** Task that asynchronously registers the service associated with this
852      *  join manager with the lookup service referenced by the current
853      *  instance of this class.
854      */
855     private static class RegisterTask extends JoinTask {
856         /** Attributes with which to register the service. These attributes
857          *  must not change during the registration process performed in
858          *  this this task.
859          */
860         final Entry[] regAttrs;
861 
862         /** Constructor that associates this task with the lookup service
863          *  referenced in the given <code>ProxyReg</code> parameter.
864          *
865          *  @param proxyReg  data structure that references the lookup service
866          *                   with which the service is to be registered
867          *  @param regAttrs  array of Entry objects whose elements are the
868          *                   attributes with which to register the service.
869          *                   The caller of this constructor should take steps
870          *                   to guarantee that the contents of this parameter
871          *                   do not change during the registration process
872          *                   performed in this task.
873          */
874         RegisterTask(ProxyReg proxyReg, Entry[] regAttrs, AccessControlContext context) {
875             super(proxyReg, context);
876             this.regAttrs = regAttrs;
877 	}//end constructor
878 
879         /** Attempts to register this join manager's service with the lookup
880          *  service referenced in this task's proxyReg field.
881          */
882         @Override
883         public void run() throws Exception {
884             logger.finest("JoinManager - RegisterTask started");
885             proxyReg.register(regAttrs);
886             logger.finest("JoinManager - RegisterTask completed");
887         }//end run
888 
889     }//end class RegisterTask
890 
891     /** Task that asynchronously re-registers the service associated with this
892      *  join manager with the lookup service referenced by the current
893      *  instance of this class.
894      *  
895      *  This task is typically executed when the service's lease with the
896      *  referenced lookup service has expired.
897      */
898     private class LeaseExpireNotifyTask extends JoinTask {
899         /** Attributes with which to re-register the service. These attributes
900          *  must not change during the registration process performed in
901          *  this this task.
902          */
903         Entry[] regAttrs;
904         /** Constructor that associates this task with the lookup service
905          *  referenced in the given <code>ProxyReg</code> parameter.
906          *
907          *  @param proxyReg  data structure that references the lookup service
908          *                   with which the service is to be re-registered
909          *  @param regAttrs  array of Entry objects whose elements are the
910          *                   attributes with which to re-register the service.
911          *                   The caller of this constructor should take steps
912          *                   to guarantee that the contents of this parameter
913          *                   do not change during the registration process
914          *                   performed in this task.
915          */
916         LeaseExpireNotifyTask(ProxyReg proxyReg, Entry[] regAttrs) {
917             super(proxyReg, context);
918             this.regAttrs = regAttrs;
919 	}//end constructor
920 
921         /** Attempts to re-register this join manager's service with the
922          *  lookup service referenced by the current instance of this class.
923          */
924         @Override
925         public void run() throws Exception {
926             logger.finest("JoinManager - LeaseExpireNotifyTask started");
927             if(joinSet.contains(proxyReg)) proxyReg.register(regAttrs);
928             logger.finest("JoinManager - LeaseExpireNotifyTask completed");
929 	}//end run
930 
931     }//end class LeaseExpireNotifyTask
932 
933     /** Task that asynchronously requests the cancellation of the lease
934      *  on the <code>ServiceRegistration</code> referenced by the given
935      *  <code>ProxyReg</code> object. The lease to be cancelled was granted
936      *  by the lookup service whose proxy is also referenced by the given
937      *  <code>ProxyReg</code> object. This task is executed whenever that
938      *  lookup service is discarded.
939      *
940      *  Note that although this task is executed upon receipt of any type
941      *  of lookup service discard event, there is one type of discard
942      *  that this task is actually intended to address: the so-called
943      *  "lost-interest" discard. A discard corresponding to a loss of
944      *  interest is an indication that the discarded lookup service is
945      *  still up and available, but the discovery manager employed by
946      *  this join manager (not the join manager or the service itself)
947      *  discards the lookup service because the service is no longer
948      *  interested in being registered with that lookup service. This
949      *  loss of interest is caused by a change in either the lookup
950      *  service's member groups or the service's groups-to-discover.
951      *  Such a change in groups would typically be made administratively,
952      *  through either the lookup service's administrative interface or
953      *  through the service's administrative interface (or both). 
954      *
955      *  When the lookup service is discarded because of a loss of
956      *  interest, there is a time period in which the service is still
957      *  registered with the lookup service, even though the service no
958      *  longer wishes to be registered with that lookup service. To 
959      *  address this, the lease is cancelled so that the service is
960      *  removed from the lookup service in a much more timely fashion
961      *  than simply allowing the lease to expire.
962      *
963      *  As stated above, this task is also executed upon receipt of
964      *  the other types of discard event. But because those other
965      *  discard types occur when the lookup service is no longer
966      *  available, the lease cancellation that is attempted here has
967      *  no significant effect.
968      *
969      *  @see DiscMgrListener#discarded
970      */
971     private class DiscardProxyTask extends JoinTask {
972         /** Constructor that provides this task with access (through the given
973          *  <code>ProxyReg</code> parameter) to the lease to be cancelled.
974          *
975          *  @param proxyReg  data structure that references the 
976          *                   <code>ServiceRegistration</code> and associated
977          *                   lease whose cancellation is to be requested
978          */
979         DiscardProxyTask(ProxyReg proxyReg) {
980             super(proxyReg, context);
981 	}//end constructor
982 
983         /** Requests the cancellation of the lease on the 
984          *  <code>ServiceRegistration</code> that is referenced in the
985          *  <code>proxyReg</code> data structure.
986          */
987         @Override
988         public void run() {
989             logger.finest("JoinManager --> DiscardProxyTask started");
990             Lease svcLease = proxyReg != null ? proxyReg.serviceLease : null;
991             if( svcLease != null ) {
992                 try {
993                     svcLease.cancel();
994                 } catch (Exception e) { /*ignore*/ }
995             }//endif
996             logger.finest("JoinManager - DiscardProxyTask completed");
997 	}//end run
998 
999     }//end class DiscardProxyTask
1000 
1001     /** Task that asynchronously augments the attributes associated with this
1002      *  join manager's service in the lookup service referenced by the
1003      *  current instance of this class.
1004      */
1005     private static class AddAttributesTask extends JoinTask {
1006         /** The new attribute values with which the service's current
1007          *  attributes will be augmented, replaced, or changed.
1008          */
1009 	protected final Entry[] attrSets;
1010 
1011         /** Constructor that associates this task with the lookup service
1012          *  referenced in the given <code>ProxyReg</code> parameter.
1013          *
1014          *  @param proxyReg  data structure that references the lookup service
1015          *                   in which the service's attributes should be
1016          *                   augmented 
1017          *  @param newAttrs  the attributes with which to augment the 
1018          *                   service's current set of attributes 
1019          */
1020         AddAttributesTask(ProxyReg proxyReg, Entry[] newAttrs, AccessControlContext context) {
1021             super(proxyReg, context);
1022 	    this.attrSets = (Entry[])newAttrs.clone();
1023 	}//end constructor
1024 
1025         /** Performs the actual attribute augmentation, replacement, or
1026          *  modification work. This method is typically overridden by
1027          *  sub-classes of this class.
1028          */
1029         protected void doAttributes(ProxyReg proxyReg) throws Exception {
1030             logger.finest("JoinManager - AddAttributesTask started");
1031 	    proxyReg.addAttributes(attrSets);
1032             logger.finest("JoinManager - AddAttributesTask completed");
1033         }//end AddAttributesTask.doAttributes
1034 
1035         /** Attempts to either augment, replace, or modify the attributes
1036          *  of this join manager's service in the lookup service referenced
1037          *  by the current instance of this class. Which action is taken --
1038          *  augmentation, replacement, or modification -- is dependent on the
1039          *  definition of the <code>doAttributes/code> method.
1040          */
1041         @Override
1042         public void run() throws Exception {
1043 		    doAttributes(proxyReg);
1044 	}//end run
1045 
1046     }//end class AddAttributesTask
1047 
1048     /** Task that asynchronously replaces the attributes associated with this
1049      *  join manager's service in the lookup service referenced by the
1050      *  current instance of this class.
1051      */
1052     private static final class SetAttributesTask extends AddAttributesTask {
1053         /** Constructor that associates this task with the lookup service
1054          *  referenced in the given <code>ProxyReg</code> parameter.
1055          *
1056          *  @param proxyReg         data structure that references the lookup
1057          *                          service in which the service's attributes
1058          *                          should be replaced 
1059          *  @param replacementAttrs the attributes with which to replace the 
1060          *                          service's current set of attributes 
1061          */
1062         SetAttributesTask(ProxyReg proxyReg, Entry[] replacementAttrs, AccessControlContext context){
1063             super(proxyReg, replacementAttrs, context);
1064 	}//end constructor
1065 
1066         /** Performs the actual attribute replacement work. */
1067         @Override
1068         protected void doAttributes(ProxyReg proxyReg) throws Exception {
1069             logger.finest("JoinManager - SetAttributesTask started");
1070 	    proxyReg.setAttributes(attrSets);
1071             logger.finest("JoinManager - SetAttributesTask completed");
1072 	}//end SetAttributesTask.doAttributes
1073 
1074     }//end class SetAttributesTask
1075 
1076     /** Task that asynchronously modifies the attributes associated with this
1077      *  join manager's service in the lookup service referenced by the
1078      *  current instance of this class.
1079      */
1080     private static final class ModifyAttributesTask extends AddAttributesTask {
1081 	private final Entry[] attrSetTemplates;
1082         /** Constructor that associates this task with the lookup service
1083          *  referenced in the given <code>ProxyReg</code> parameter.
1084          *
1085          *  @param proxyReg         data structure that references the lookup
1086          *                          service in which the service's attributes
1087          *                          should be changed 
1088          *  @param attrSetTemplates the attribute templates that are used to
1089          *                          select (through attribute matching) the
1090          *                          attributes to change in the service's
1091          *                          current set of attributes 
1092          *  @param attrChanges      the attributes containing the changes to
1093          *                          make to the attributes in the service's
1094          *                          current set that are selected through
1095          *                          attribute matching with the
1096          *                          attrSetTemplates parameter
1097          */
1098         ModifyAttributesTask(   ProxyReg proxyReg,
1099 				Entry[] attrSetTemplates,
1100 				Entry[] attrChanges,
1101 				AccessControlContext context)
1102         {
1103             super(proxyReg, attrChanges, context);
1104 	    this.attrSetTemplates = (Entry[])attrSetTemplates.clone();
1105 	}//end constructor
1106 
1107         /** Performs the actual attribute modification work. */
1108         @Override
1109         protected void doAttributes(ProxyReg proxyReg) throws Exception {
1110             logger.finest("JoinManager - ModifyAttributesTask started");
1111 	    proxyReg.modifyAttributes(attrSetTemplates, attrSets);
1112             logger.finest("JoinManager - ModifyAttributesTask completed");
1113 	}//end ModifyAttributesTask.doAttributes
1114 
1115     }//end class ModifyAttributesTask
1116 
1117     /** Wrapper class in which each instance corresponds to a lookup
1118      *  service to discover, and with which this join manager's service
1119      *  should be registered.
1120      */
1121     private class ProxyReg implements FutureObserver{
1122 
1123         /** Class that is registered as a listener with this join manager's
1124          *  lease renewal manager. That lease renewal manager manages the
1125          *  lease granted to this join manager's associated service by the
1126          *  lookup service referenced in the proxy object associated with
1127          *  this class (<code>ProxyReg</code>).
1128          *
1129          *  If the lease expires in the lookup service before the lease 
1130          *  renewal manager requests renewal of the lease, then upon sending
1131          *  that renewal request, the lease renewal manager will receive an
1132          *  <code>UnknownLeaseException</code> from the lookup service.
1133          *  As a result, the lease renewal manager removes the expired lease
1134          *  so that no further renewal attempts are made for that lease, 
1135          *  and then sends to this listener a <code>LeaseRenewalEvent</code>,
1136          *  containing an <code>UnknownLeaseException</code>.
1137          *
1138          *  Alternatively, suppose that at the time the lease renewal manager
1139          *  is about to request the renewal of the lease, the lease renewal
1140          *  manager determines that the expiration time of the lease has
1141          *  actually passed. In this case, since there is no reason to
1142          *  send the renewal request, the lease renewal manager instead
1143          *  removes the expired lease (again, so that no further renewal
1144          *  attempts are made for that lease), and then sends a
1145          *  <code>LeaseRenewalEvent</code> to this listener to indicate
1146          *  that the lease has expired. The difference between this case,
1147          *  and the case described previously is that in this case the
1148          *  <code>LeaseRenewalEvent</code> contains no exception (that is,
1149          *  <code>LeaseRenewalEvent.getException</code> returns 
1150          *  <code>null</code>).
1151          *
1152          *  Both situations described above indicate that the lease
1153          *  referenced by the event received by this listener has expired.
1154          *  Thus, the normal course of action should be to attempt to
1155          *  re-register the service with the lookup service that originally
1156          *  granted the lease that has expired.
1157          *
1158          *  Prior to re-registering the service, the joinSet is examined to
1159          *  determine if it contains a ProxyReg object that is equivalent to
1160          *  the ProxyReg object referencing the current instance of this
1161          *  listener class. That is, using <code>equals</code>, it is
1162          *  determined whether the joinSet contains either ProxyReg.this,
1163          *  or a new instance of ProxyReg that is equal to ProxyReg.this.
1164          *  If the joinSet does not contain such a ProxyReg, then the lookup
1165          *  service must have been discarded and not yet re-discovered; in
1166          *  which case, there is no need to attempt to re-register with that
1167          *  lookup service, since it is unavailable.
1168          *
1169          *  If it is determined that the joinSet does contain either
1170          *  ProxyReg.this or a new ProxyReg equivalent to ProxyReg.this,
1171          *  then re-registration should be attempted, but only if the lease
1172          *  associated with the ProxyReg in the joinSet is equal to the
1173          *  expired lease referenced in the event received by this listener.
1174          *  Equality of those leases is an indication that the lease on the
1175          *  service's current registration has expired; thus, an attempt to
1176          *  re-register the service should be made.
1177          *
1178          *  If the lease associated with the ProxyReg from the joinSet
1179          *  does not equal the expired lease from the event, then
1180          *  re-registration should not be attempted. This is because
1181          *  the inequality of the leases is an indication that the lease
1182          *  renewal event received by this listener was the result of an
1183          *  <code>UnknownLeaseException</code> that occurs when the
1184          *  ProxyReg in the joinSet is a new ProxyReg, different from
1185          *  ProxyReg.this, and the lease renewal manager requests the
1186          *  renewal of the (now invalid) lease associated with that old
1187          *  ProxyReg.this; not the valid lease associated with the new
1188          *  ProxyReg.
1189          *
1190          *  A scenario such as that just described can occur when the
1191          *  lookup service is discarded, rediscovered, and the service is
1192          *  re-registered, resulting in a new ProxyReg (with new lease)
1193          *  being placed in the joinSet, replacing the previous ProxyReg
1194          *  (ProxyReg.this). But before the old, expired lease is removed
1195          *  from the lease renewal manager, an attempt to renew the old
1196          *  lease is made. Such an attempt can occur because the lease
1197          *  renewal manager may be in the process of requesting the renewal
1198          *  of that lease (or may have queued such a request) just prior to,
1199          *  or at the same time as, when the lease removal request is made
1200          *  during discard processing. When the request is made to renew
1201          *  the expired lease, an <code>UnknownLeaseException</code> occurs
1202          *  and a lease renewal event is sent to this listener.
1203          *
1204          *  If, upon receiving an event such as that just described, the
1205          *  service were to be re-registered, the current valid service
1206          *  registration would be replaced, a new lease would be granted,
1207          *  and the corresponding ProxyReg currently contained in the joinSet
1208          *  would be replaced with a new ProxyReg. Additionally, the now
1209          *  invalid lease corresponding to the ProxyReg that was just
1210          *  replaced would remain in the lease renewal manager. This means
1211          *  that an attempt to renew that lease will eventually be made and
1212          *  will fail, and the cycle just described will repeat indefinitely.
1213          *
1214          *  Thus, for the reasons stated above, re-registration is attempted
1215          *  only if the lease associated with the ProxyReg contained in the
1216          *  joinSet is equal to the expired lease referenced in the lease
1217          *  renewal event received by this listener.
1218          */
1219 	private class DiscLeaseListener implements LeaseListener {
1220               @Override
1221   	    public void notify(LeaseRenewalEvent e) {
1222                 Throwable ex = e.getException();
1223 		if ( (ex == null) || (ex instanceof UnknownLeaseException) ) {
1224                     removeTasks(ProxyReg.this);
1225                     Lease expiredLease = e.getLease();
1226                     // Maybe re-register
1227                     Iterator<ProxyReg> it = joinSet.iterator();
1228                     while (it.hasNext()){
1229                         ProxyReg next = it.next();
1230                         if (!ProxyReg.this.equals(next)) continue;
1231                         if(expiredLease.equals(next.serviceLease)) {
1232                             // Okay to re-register
1233                             addTask(
1234                                 new LeaseExpireNotifyTask (ProxyReg.this,
1235                                              (Entry[])lookupAttr.clone()));
1236                         }//endif
1237                     }
1238 		} else {
1239 		    fail(ex);
1240                 }//endif
1241 	    }//end notify
1242 	}//end class DiscLeaseListener
1243 
1244         /** The <code>ProxyRegTask</code> that instantiated this
1245          *  <code>ProxyReg</code>.
1246          */
1247         volatile ProxyRegTask proxyRegTask;// writes sync on taskList
1248         /** The <i>prepared</i> proxy to the lookup service referenced by
1249          *  this class, and with which this join manager's service will be
1250          *  registered.
1251          */
1252 	final ServiceRegistrar proxy;
1253         /** The <i>prepared</i> registration proxy returned by this class'
1254          *  associated lookup service when this join manager registers its
1255          *  associated service.
1256          * 
1257          * Writes to reference synchronized on JoinManager.this, but not referent
1258          * as it has foreign remote methods.
1259          */
1260 	volatile ServiceRegistration srvcRegistration = null;
1261         /* The <i>prepared</i> proxy to the lease on the registration of this
1262          * join manager's service with the this class' associated lookup
1263          * service.
1264          */
1265 	volatile Lease serviceLease = null;
1266         /** The set of sub-tasks that are to be executed in order for the
1267          *  lookup service associated with the current instance of this class.
1268          */
1269         final List<JoinTask> taskList = new ArrayList<JoinTask>();
1270         /** The instance of <code>DiscLeaseListener</code> that is registered
1271          *  with the lease renewal manager that handles the lease of this join
1272          *  manger's service.
1273          */
1274         final List<Future> runningTasks = new ArrayList<Future>();
1275         
1276 	private final DiscLeaseListener dListener = new DiscLeaseListener();
1277 
1278         /** Constructor that associates this class with the lookup service
1279          *  referenced in the given <code>ProxyReg</code> parameter.
1280          *
1281 	 *  @param proxy data structure that references the lookup service on
1282 	 *               which the sub-tasks referenced in this class will be
1283 	 *               executed in order
1284          */
1285 	public ProxyReg(ServiceRegistrar proxy) {
1286 	    if(proxy == null)  throw new IllegalArgumentException
1287                                                       ("proxy can't be null");
1288 	    this.proxy = proxy;
1289 	}//end constructor	
1290         
1291         @Override
1292         public void futureCompleted(Future e) {
1293             synchronized (runningTasks){
1294                 runningTasks.remove(e);
1295             }
1296         }
1297         
1298         public void terminate(){
1299             synchronized (runningTasks){
1300                 Iterator<Future> it = runningTasks.iterator();
1301                 while (it.hasNext()){
1302                     Future f = it.next();
1303 		    if (f != null) f.cancel(false);
1304                 }
1305                 runningTasks.clear();
1306             }
1307         }
1308 
1309         /** Convenience method that adds new sub-tasks to this class' 
1310          *  task queue.
1311          *
1312          *  @param task the task to add to the task queue
1313          */
1314         public void addTask(JoinTask task) {
1315             if(bTerminated) return;
1316             Future future = null;
1317             synchronized(taskList) {
1318                 taskList.add(task);
1319                 if(this.proxyRegTask == null) {
1320                     this.proxyRegTask = new ProxyRegTask(this,taskSeqN++);
1321                     this.proxyRegTask.addObserver(this);
1322                     future = proxyRegTaskQueue.submit(this.proxyRegTask);
1323                 }//endif
1324             }//end sync(taskList)
1325             synchronized (runningTasks){
1326                 runningTasks.add(future);
1327             }
1328         }//end addTask
1329 
1330         /** Registers the service associated with this join manager with the
1331          *  the lookup service corresponding to this class. Additionally,
1332          *  this method retrieves the lease granted by the lookup service
1333          *  on the service's registration, and passes that lease to the
1334          *  lease renewal manager. If a <code>ServiceIDListener</code> 
1335          *  has been registered with this join manager, this method will
1336          *  send to that listener a notification containing the service's ID.
1337          */
1338         public void register(Entry[] srvcAttrs) throws Exception {
1339             if(proxy == null) throw new RuntimeException("proxy is null");
1340             /* The lookup service proxy was already prepared at discovery */
1341             ServiceItem tmpSrvcItem;
1342             ServiceItem item;
1343             srvcRegistration = null;
1344             //accessing serviceItem.serviceID
1345             item = serviceItem;
1346             tmpSrvcItem = new ServiceItem(item.serviceID,
1347                                               item.service,
1348                                               srvcAttrs);
1349             /* Retrieve and prepare the proxy to the service registration */
1350             ServiceRegistration tmpSrvcRegistration 
1351                                 = proxy.register(tmpSrvcItem, renewalDuration);
1352             try {
1353                 tmpSrvcRegistration = 
1354                    (ServiceRegistration)registrationPreparer.prepareProxy
1355                                                        ( tmpSrvcRegistration );
1356                 logger.finest
1357                           ("JoinManager - ServiceRegistration proxy prepared");
1358             } catch(Exception e) {
1359 		LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
1360 		    	"register", "JoinManager - failure during " +
1361 		    	"preparation of ServiceRegistration proxy: {0}",
1362 		    	new Object[] { tmpSrvcRegistration }, e);
1363                 throw e; //rethrow the exception since proxy may be unusable
1364             }
1365             /* Retrieve and prepare the proxy to the service lease */
1366             Lease svcLease = tmpSrvcRegistration.getLease();
1367             try {
1368                 this.serviceLease = 
1369                        (Lease)serviceLeasePreparer.prepareProxy(svcLease);
1370                 logger.finest("JoinManager - service lease proxy prepared");
1371             } catch(Exception e) {
1372 		LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
1373 		    	"register", "JoinManager - failure during " +
1374 		    	"preparation of service lease proxy: {0}",
1375 		    	new Object[] { svcLease }, e);
1376                 throw e; //rethrow the exception since proxy may be unusable
1377             }
1378             leaseRenewalMgr.renewUntil(svcLease, Lease.FOREVER,
1379                                        renewalDuration, dListener);
1380             ServiceID tmpID = null;
1381             srvcRegistration = tmpSrvcRegistration;
1382             ServiceID id = srvcRegistration.getServiceID();
1383             synchronized (JoinManager.this){
1384                 item = serviceItem;
1385                 if(item.serviceID == null) {
1386                     serviceItem = new ServiceItem(id, item.service, item.attributeSets);
1387                     tmpID = id;
1388                 }//endif
1389             }
1390             if( (tmpID != null) && (callback != null) )  {
1391                 callback.serviceIDNotify(tmpID);
1392             }//endif
1393         }//end ProxyReg.register
1394 
1395         /** With respect to the lookup service referenced in this class
1396          *  and with which this join manager's service is registered, this
1397          *  method associates with that service a new set of attributes -- in
1398          *  addition to that service's current set of attributes.
1399          */
1400         public void addAttributes(Entry[] attSet) throws Exception {
1401             ServiceRegistration sr = srvcRegistration;
1402             if (sr != null) sr.addAttributes(attSet);
1403 	}//end ProxyReg.addAttributes
1404 
1405         /** With respect to the lookup service referenced in this class
1406          *  and with which this join manager's service is registered, this
1407          *  method changes that service's current attributes by selecting
1408          *  the attributes to change using the given first parameter;
1409          *  and identifying the desired changes to make through the
1410          *  contents of the second parameter.
1411          */
1412         public void modifyAttributes(Entry[] templ, Entry[] attSet)
1413                                                              throws Exception
1414         {
1415             ServiceRegistration sr = srvcRegistration;
1416             if (sr != null) sr.modifyAttributes(templ, attSet);
1417 	}//end ProxyReg.modifyAttributes		    
1418 
1419         /** With respect to the lookup service referenced in this class
1420          *  and with which this join manager's service is registered, this
1421          *  method replaces that service's current attributes with a new
1422          *  set of attributes.
1423          */
1424         public void setAttributes(Entry[] attSet) throws Exception {
1425             ServiceRegistration sr = srvcRegistration;
1426             if (sr != null) sr.setAttributes(attSet);
1427 	}//end ProxyReg.setAttributes
1428 
1429         /** Convenience method that encapsulates appropriate behavior when
1430          *  failure is encountered related to the current instance of this
1431          *  class. This method discards the lookup service proxy associated
1432          *  with this object, and logs the stack trace of the given
1433          *  <code>Throwable</code> according to the logging levels specified
1434          *  for this utility.
1435          *  
1436          *  Note that if the discovery manager employed by this join manager
1437          *  has been terminated, then the attempt to discard the lookup 
1438          *  service proxy will result in an <code>IllegalStateException</code>.
1439          *  Since this method is called only within the tasks run by
1440          *  this join manager, and since propagating an
1441          *  <code>IllegalStateException</code> out into the
1442          *  <code>ThreadGroup</code> of those tasks is undesirable, this
1443          *  method will not propagate the <code>IllegalStateException</code>
1444          *  that occurs as a result of an attempt to discard a lookup
1445          *  service proxy from the discovery manager employed by this
1446          *  join manager.
1447          * 
1448          * For more information, refer to Bug 4490355.
1449          */
1450 	public void fail(Throwable e) {
1451 		if(bTerminated) return;
1452                 LogUtil.logThrow(logger, Level.INFO, ProxyReg.class, "fail",
1453                     "JoinManager - failure for lookup service proxy: {0}",
1454                     new Object[] { proxy }, e);
1455                 try {
1456                     discMgr.discard(proxy);
1457                 } catch(IllegalStateException e1) {
1458                    logger.log(Level.FINEST,
1459                               "JoinManager - cannot discard lookup, "
1460                               +"discovery manager already terminated",
1461                               e1);
1462                 }
1463 	}//end ProxyReg.fail
1464 
1465 	/** Returns true if the both objects' associated proxies are equal. */
1466         @Override
1467 	public boolean equals(Object obj) {
1468 	    if (obj instanceof ProxyReg) {
1469 		return proxy.equals( ((ProxyReg)obj).proxy );
1470 	    } else {
1471                 return false;
1472             }//endif
1473 	}//end ProxyReg.equals
1474 
1475 	/** Returns the hash code of the proxy referenced in this class. */
1476         @Override
1477 	public int hashCode() {
1478 	    return proxy.hashCode();
1479 	}//end hashCode
1480 
1481     }//end class ProxyReg
1482 
1483     /* Listener class for discovery/discard notification of lookup services. */
1484     private class DiscMgrListener implements DiscoveryListener {
1485 	/* Invoked when new or previously discarded lookup is discovered. */
1486         @Override
1487 	public void discovered(DiscoveryEvent e) {
1488 		ServiceRegistrar[] proxys
1489 				       = (ServiceRegistrar[])e.getRegistrars();
1490                 int l = proxys.length;
1491 		for(int i=0;i<l;i++) {
1492 		    /* Prepare the proxy to the discovered lookup service
1493 					 * before interacting with it.
1494 					 */
1495 		    try {
1496 			proxys[i]
1497 			  = (ServiceRegistrar)registrarPreparer.prepareProxy
1498 								   (proxys[i]);
1499 			logger.log(Level.FINEST, "JoinManager - discovered "
1500 				   +"lookup service proxy prepared: {0}",
1501 				   proxys[i]);
1502 		    } catch(Exception e1) {
1503 			LogUtil.logThrow(logger, Level.INFO,
1504 			    DiscMgrListener.class, "discovered", "failure "
1505 			    + "preparing discovered ServiceRegistrar proxy: "
1506 			    + "{0}", new Object[] { proxys[i] }, e1);
1507 			discMgr.discard(proxys[i]);
1508 			continue;
1509 		    }
1510 		    /* If the serviceItem is a lookup service, don't need to
1511                      * register it with itself since a well-defined lookup
1512                      * service will always register with itself.
1513                      */
1514 		    if( !proxys[i].equals(serviceItem.service) ) {
1515 			ProxyReg proxyReg = new ProxyReg(proxys[i]);
1516 			if( !joinSet.contains(proxyReg) ) {
1517 			    joinSet.add(proxyReg);
1518 			    proxyReg.addTask(new RegisterTask
1519 						(proxyReg,
1520 						 (Entry[])lookupAttr.clone(), context));
1521 			}//endif
1522 		    }//endif
1523 		}//end loop
1524 	}//end discovered
1525 
1526 	/* Invoked when previously discovered lookup is discarded. */
1527         @Override
1528 	public void discarded(DiscoveryEvent e) {
1529             ServiceRegistrar[] proxys
1530                                   = (ServiceRegistrar[])e.getRegistrars();
1531             int l = proxys.length;
1532             for(int i=0;i<l;i++) {
1533                 ProxyReg proxyReg = findReg(proxys[i]);
1534                 if(proxyReg != null) {
1535                     removeTasks(proxyReg);
1536                     joinSet.remove(proxyReg);
1537                     try {
1538                         leaseRenewalMgr.remove( proxyReg.serviceLease );
1539                     } catch(UnknownLeaseException ex) { /*ignore*/ }
1540                     proxyReg.addTask(new DiscardProxyTask(proxyReg));
1541                 }//endif
1542             }//end loop
1543 	}//end discarded
1544     }//end class DiscMgrListener
1545 
1546     /* Name of this component; used in config entry retrieval and the logger.*/
1547     private static final String COMPONENT_NAME = "net.jini.lookup.JoinManager";
1548     /* Logger used by this utility. */
1549     private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
1550     /** Maximum number of concurrent tasks that can be run in any default
1551      * ThreadPoolExecutor created by this class.
1552      */
1553     private static final int MAX_N_TASKS = 15;
1554     /** Whenever a task is created in this join manager, it is assigned a
1555      *  unique sequence number so that the task is not run prior to the
1556      *  execution, and completion of, any other tasks with which that task
1557      *  is associated (tasks are grouped by the <code>ProxyReg</code> with
1558      *  which each task is associated). This field contains the value of
1559      *  the sequence number assigned to the most recently created task.
1560      */
1561     private int taskSeqN = 0; // access sync on taskList
1562     /** Task manager for the various tasks executed by this join manager.
1563      *  On the first attempt to execute any task is managed by this
1564      *  <code>ExecutorService</code> so that the number of concurrent threads
1565      *  can be bounded. If one or more of those attempts fails, a
1566      *  <code>WakeupManager</code> is used (through the use of a
1567      *  <code>RetryTask</code>) to schedule - at a later time (employing a
1568      *  "backoff strategy") - the re-execution of each failed task in this
1569      *  <code>ExecutorService</code>.
1570      */
1571     private final ExecutorService executor;
1572     private final ProxyRegTaskQueue proxyRegTaskQueue;
1573     /** Maximum number of times a failed task is allowed to be re-executed. */
1574     private final int maxNRetries;
1575     /** Wakeup manager for the various tasks executed by this join manager.
1576      *  After an initial failure of any task executed by this join manager,
1577      *  the failed task is managed by this <code>WakeupManager</code>; which
1578      *  schedules the re-execution of the failed task - in the ExecutorService -
1579      *  at various times in the future until either the task succeeds or the
1580      *  task has been executed the maximum number of allowable times. This
1581      *  wakeup manager is supplied to the <code>RetryTask</code>) that
1582      *  performs the actual task execution so that when termination of this
1583      *  join manager is requested, all tasks scheduled for retry by this
1584      *  wakeup manager can be cancelled.
1585      */
1586     private final WakeupManager wakeupMgr;
1587     /** Contains the reference to the service that is to be registered with
1588      *  all of the desired lookup services referenced by <code>discMgr</code>.
1589      */
1590     private volatile ServiceItem serviceItem = null; // writes sync on JoinManager.this
1591     /** Contains the attributes with which to associate the service in each
1592      *  of the lookup services with which this join manager registers the
1593      *  service.
1594      */
1595     private volatile Entry[] lookupAttr = null; // writes sync on JoinManager.this
1596     /** Contains the listener -- instantiated by the entity that constructs
1597      *  this join manager -- that will receive an event containing the
1598      *  service ID assigned to this join manager's service by one of the
1599      *  lookup services with which that service is registered.
1600      */
1601     private final ServiceIDListener callback;
1602     /** Contains elements of type <code>ProxyReg</code> where each element
1603      *  references a proxy to one of the lookup services with which this
1604      *  join manager's service is registered.
1605      */
1606     private final List<ProxyReg> joinSet = new CopyOnWriteArrayList<ProxyReg>();
1607     /** Contains the discovery manager that discovers the lookup services
1608      *  with which this join manager will register its associated service.
1609      */
1610     private final DiscoveryManagement discMgr;
1611     /** Contains the discovery listener registered by this join manager with
1612      *  the discovery manager so that this join manager is notified whenever
1613      *  one of the desired lookup services is discovered or discarded.
1614      */
1615     private final DiscMgrListener discMgrListener ;
1616     /** Flag that indicate whether the discovery manager employed by this
1617      *  join manager was created by this join manager itself, or by the
1618      *  entity that constructed this join manager.
1619      */
1620     private final boolean bCreateDiscMgr;
1621     /** Contains the lease renewal manager that renews all of the leases
1622      *  this join manager's service holds with each lookup service with which
1623      *  it has been registered.
1624      */
1625     private final LeaseRenewalManager leaseRenewalMgr;
1626     /** The value to use as the <code>renewDuration</code> parameter
1627      *  when invoking the lease renewal manager's <code>renewUntil</code>
1628      *  method to add a service lease to manage. This value represents,
1629      *  effectively, the time interval (in milliseconds) over which each
1630      *  managed lease must be renewed. As this value is made smaller,
1631      *  renewal requests will be made more frequently while the service
1632      *  is up, and lease expirations will occur sooner when the service
1633      *  goes down.
1634      */
1635     private final long renewalDuration;
1636     /** Flag that indicates if this join manager has been terminated. */
1637     private volatile boolean bTerminated = false; // write access sync on this.
1638     /* Preparer for the proxies to the lookup services that are discovered
1639      * and used by this utility.
1640      */
1641     private final ProxyPreparer registrarPreparer;
1642     /* Preparer for the proxies to the registrations returned to this utility
1643      * upon registering the service with each discovered lookup service.
1644      */
1645     private final ProxyPreparer registrationPreparer;
1646     /* Preparer for the proxies to the leases returned to this utility through
1647      * the registrations with each discovered lookup service with which this
1648      * utility has registered the service.
1649      */
1650     private final ProxyPreparer serviceLeasePreparer;
1651     
1652     /* Context used for call backs.
1653      *
1654      */
1655     private final AccessControlContext context;
1656 
1657     /** 
1658      * Constructs an instance of this class that will register the given
1659      * service reference with all discovered lookup services and, through
1660      * an event sent to the given <code>ServiceIDListener</code> object,
1661      * communicate the service ID assigned by the first lookup service
1662      * with which the service is registered. This constructor is typically
1663      * used by services which have not yet been assigned a service ID.
1664      * <p>
1665      * The value input to the <code>serviceProxy</code> parameter represents
1666      * the service reference (proxy) to register with each discovered lookup
1667      * service. If the <code>Object</code> input to that parameter is not
1668      * <code>Serializable</code>, an <code>IllegalArgumentException</code>
1669      * is thrown. If <code>null</code> is input to that parameter, a
1670      * <code>NullPointerException</code> is thrown.
1671      * <p>
1672      * The value input to the <code>attrSets</code> parameter is an array
1673      * of <code>Entry</code> objects, none of whose elements may be
1674      * <code>null</code>, that represents the new set of attributes to
1675      * associate with the new service reference to be registered. Passing
1676      * <code>null</code> as the value of the <code>attrSets</code> parameter
1677      * is equivalent to passing an empty array. If any of the elements
1678      * of the <code>attrSets</code> array are <code>null</code>, a
1679      * <code>NullPointerException</code> is thrown. The set of attributes
1680      * passed in this parameter will be associated with the service in all
1681      * future join processing until those attributes are changed through
1682      * an invocation of a method on this class such as,
1683      * <code>addAttributes</code>, <code>setAttributes</code>, 
1684      * <code>modifyAttributes</code>, or <code>replaceRegistration</code>.
1685      * <p>
1686      * When constructing this utility, the service supplies an object through
1687      * which notifications that indicate a lookup service has been discovered
1688      * or discarded will be received. At a minimum, the object supplied
1689      * (through the <code>discoveryMgr</code> parameter) must satisfy the
1690      * contract defined in the <code>DiscoveryManagement</code> interface.
1691      * That is, the object supplied must provide this utility with the ability
1692      * to set discovery listeners and to discard previously discovered
1693      * lookup services when they are found to be unavailable. A value of
1694      * <code>null</code> may be input to the <code>discoveryMgr</code>
1695      * parameter. When <code>null</code> is input to that parameter, an
1696      * instance of <code>LookupDiscoveryManager</code> is used to listen
1697      * for events announcing the discovery of only those lookup services
1698      * that are members of the public group.
1699      * <p>
1700      * The object input to the <code>leaseMgr</code> parameter provides for
1701      * the coordination, systematic renewal, and overall management of all
1702      * leases on the given service reference's residency in the lookup 
1703      * services that have been joined. As with the <code>discoveryMgr</code>
1704      * parameter, a value of <code>null</code> may be input to this
1705      * parameter. When <code>null</code> is input to this parameter,
1706      * an instance of <code>LeaseRenewalManager</code>, initially managing
1707      * no <code>Lease</code> objects will be used. This feature allows a
1708      * service to either use a single entity to manage all of its leases,
1709      * or to use separate entities: one to manage the leases unrelated to
1710      * the join process, and one to manage the leases that result from the
1711      * join process, that are accessible only within the current instance
1712      * of the <code>JoinManager</code>.
1713      * 
1714      * @param serviceProxy the service reference (proxy) to register with all
1715      *                     discovered lookup services
1716      * @param attrSets     array of <code>Entry</code> consisting of the
1717      *                     attribute sets with which to register the service
1718      * @param callback     reference to the object that should receive the
1719      *                     event containing the service ID, assigned to the
1720      *                     service by the first lookup service with which the
1721      *                     service reference is registered
1722      * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1723      *                     object this class should use to manage lookup
1724      *                     service discovery on behalf of the given service
1725      * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
1726      *                     object this class should use to manage the leases
1727      *                     on the given service's residency in the lookup 
1728      *                     services that have been joined
1729      *
1730      * @throws java.lang.IllegalArgumentException if the object input to the
1731      *         <code>serviceProxy</code> parameter is not serializable
1732      * @throws java.lang.NullPointerException if either <code>null</code> is
1733      *         input to the <code>serviceProxy</code> parameter, or at least
1734      *         one of the elements of the <code>attrSets</code> parameter is
1735      *         <code>null</code>
1736      * @throws java.io.IOException if initiation of discovery process results
1737      *         in <code>IOException</code> when socket allocation occurs
1738      *
1739      * @throws java.lang.IllegalStateException if this method is called on 
1740      *         a terminated <code>JoinManager</code> instance. Note that this 
1741      *         exception is implementation-specific.
1742      *
1743      * @see net.jini.lookup.ServiceIDListener
1744      * @see net.jini.discovery.DiscoveryManagement
1745      * @see net.jini.discovery.LookupDiscoveryManager
1746      * @see net.jini.lease.LeaseRenewalManager
1747      */
1748      public JoinManager(Object serviceProxy,
1749                         Entry[] attrSets,
1750 			ServiceIDListener callback,
1751 			DiscoveryManagement discoveryMgr,
1752 			LeaseRenewalManager leaseMgr)    throws IOException
1753     {
1754            this(serviceProxy, attrSets, null, callback, 
1755                  getConf(EmptyConfiguration.INSTANCE, leaseMgr, discoveryMgr, serviceProxy));
1756     }//end constructor
1757 
1758     /** 
1759      * Constructs an instance of this class, configured using the items
1760      * retrieved through the given <code>Configuration</code> object,
1761      * that will register the given service reference with all discovered
1762      * lookup services and, through an event sent to the given
1763      * <code>ServiceIDListener</code> object, communicate the service ID
1764      * assigned by the first lookup service with which the service is
1765      * registered. This constructor is typically used by services which
1766      * have not yet been assigned a service ID, and which wish to allow
1767      * for deployment-time configuration of the service's join processing.
1768      * <p>
1769      * The items used to configure the current instance of this class
1770      * are obtained through the object input to the <code>config</code>
1771      * parameter. If <code>null</code> is input to that parameter, a
1772      * <code>NullPointerException</code> is thrown.
1773      * <p>
1774      * The object this utility will use to manage lookup service discovery on
1775      * behalf of the given service can be supplied through either the
1776      * <code>discoveryMgr</code> parameter or through an entry contained
1777      * in the given <code>Configuration</code>. If <code>null</code> is input
1778      * to the <code>discoveryMgr</code> parameter, an attempt will first be
1779      * made to retrieve from the given <code>Configuration</code>, an entry
1780      * named "discoveryManager" (described above). If such an object is
1781      * successfully retrieved from the given <code>Configuration</code>, that 
1782      * object will be used to perform the lookup service discovery management
1783      * required by this utility.
1784      * <p>
1785      * If <code>null</code> is input to the <code>discoveryMgr</code>
1786      * parameter, and no entry named "discoveryManager" is specified in the
1787      * given <code>Configuration</code>, then an instance of the utility class
1788      * <code>LookupDiscoveryManager</code> will be used to listen for events
1789      * announcing the discovery of only those lookup services that are
1790      * members of the public group.
1791      * <p>
1792      * As with the <code>discoveryMgr</code> parameter, the object this 
1793      * utility will use to perform lease management on behalf of the given
1794      * service can be supplied through either the <code>leaseMgr</code>
1795      * parameter or through an entry contained in the given
1796      * <code>Configuration</code>. If <code>null</code> is input to the
1797      * <code>leaseMgr</code> parameter, an attempt will first be made to
1798      * retrieve from the given <code>Configuration</code>, an entry named
1799      * "leaseManager" (described above). If such an object is successfully
1800      * retrieved from the given <code>Configuration</code>, that object
1801      * will be used to perform the lease management required by this utility.
1802      * <p>
1803      * If <code>null</code> is input to the <code>leaseMgr</code>
1804      * parameter, and no entry named "leaseManager" is specified in the
1805      * given <code>Configuration</code>, then an instance of the utility
1806      * class <code>LeaseRenewalManager</code> that takes the given
1807      * <code>Configuration</code> will be created (initially managing no
1808      * leases) and used to perform all required lease renewal management
1809      * on behalf of the given service.
1810      * <p>
1811      * Except for the <code>config</code> parameter and the additional 
1812      * semantics imposed by that parameter (as noted above), all other
1813      * parameters of this form of the constructor, along with their
1814      * associated semantics, are identical to that of the five-argument
1815      * constructor that takes a <code>ServiceIDListener</code>.
1816      * 
1817      * @param serviceProxy the service reference (proxy) to register with all
1818      *                     discovered lookup services
1819      * @param attrSets     array of <code>Entry</code> consisting of the
1820      *                     attribute sets with which to register the service
1821      * @param callback     reference to the <code>ServiceIDListener</code>
1822      *                     object that should receive the event containing the
1823      *                     service ID assigned to the service by the first
1824      *                     lookup service with which the service reference
1825      *                     is registered
1826      * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1827      *                     object this class should use to manage lookup
1828      *                     service discovery on behalf of the given service
1829      * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
1830      *                     object this class should use to manage the leases
1831      *                     on the given service's residency in the lookup 
1832      *                     services that have been joined
1833      * @param config       instance of <code>Configuration</code> through
1834      *                     which the items used to configure the current
1835      *                     instance of this class are obtained
1836      *
1837      * @throws java.lang.IllegalArgumentException if the object input to the
1838      *         <code>serviceProxy</code> parameter is not serializable
1839      * @throws java.lang.NullPointerException if <code>null</code> is input
1840      *         to the <code>serviceProxy</code> parameter or the
1841      *         <code>config</code> parameter, or if at least one of the
1842      *         elements of the <code>attrSets</code> parameter is
1843      *         <code>null</code>
1844      * @throws java.io.IOException if initiation of discovery process results
1845      *         in <code>IOException</code> when socket allocation occurs
1846      * @throws net.jini.config.ConfigurationException if an exception
1847      *         occurs while retrieving an item from the given
1848      *         <code>Configuration</code> object
1849      *
1850      * @throws java.lang.IllegalStateException if this method is called on 
1851      *         a terminated <code>JoinManager</code> instance. Note that this 
1852      *         exception is implementation-specific.
1853      *
1854      * @see net.jini.lookup.ServiceIDListener
1855      * @see net.jini.discovery.DiscoveryManagement
1856      * @see net.jini.discovery.LookupDiscoveryManager
1857      * @see net.jini.lease.LeaseRenewalManager
1858      * @see net.jini.config.Configuration
1859      * @see net.jini.config.ConfigurationException
1860      */
1861      public JoinManager(Object serviceProxy,
1862                         Entry[] attrSets,
1863 			ServiceIDListener callback,
1864 			DiscoveryManagement discoveryMgr,
1865 			LeaseRenewalManager leaseMgr,
1866                         Configuration config)
1867                                     throws IOException, ConfigurationException
1868     {
1869         
1870         this(serviceProxy, attrSets, null, callback, 
1871             getConfig(config, leaseMgr, discoveryMgr, serviceProxy)
1872         );
1873     }//end constructor
1874 
1875     /** 
1876      * Constructs an instance of this class that will register the
1877      * service with all discovered lookup services, using the supplied 
1878      * <code>ServiceID</code>. This constructor is typically used by
1879      * services which have already been assigned a service ID (possibly
1880      * by the service provider itself or as a result of a prior registration
1881      * with some lookup service), and which do not wish to allow for
1882      * deployment-time configuration of the service's join processing.
1883      * <p>
1884      * Except that the desired <code>ServiceID</code> is supplied through the
1885      * <code>serviceID</code> parameter rather than through a notification
1886      * sent to a <code>ServiceIDListener</code>, all other parameters
1887      * of this form of the constructor, along with their associated semantics,
1888      * are identical to that of the five-argument constructor that takes
1889      * a <code>ServiceIDListener</code>.
1890      *
1891      * @param serviceProxy a reference to the service requesting the services
1892      *                     of this class
1893      * @param attrSets     array of <code>Entry</code> consisting of the
1894      *                     attribute sets with which to register the service
1895      * @param serviceID    an instance of <code>ServiceID</code> with which to
1896      *                     register the service with all desired lookup
1897      *                     services
1898      * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1899      *                     object this class should use to manage the given
1900      *                     service's lookup service discovery duties
1901      * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
1902      *                     object this class should use to manage the leases
1903      *                     on the given service's residency in the lookup 
1904      *                     services that have been joined
1905      *
1906      * @throws java.lang.IllegalArgumentException if the object input to the
1907      *         <code>serviceProxy</code> parameter is not serializable
1908      * @throws java.lang.NullPointerException if either <code>null</code> is
1909      *         input to the <code>serviceProxy</code> parameter, or at least
1910      *         one of the elements of the <code>attrSets</code> parameter is
1911      *         <code>null</code>
1912      * @throws java.io.IOException if initiation of discovery process results
1913      *         in <code>IOException</code> when socket allocation occurs
1914      *
1915      * @throws java.lang.IllegalStateException if this method is called on 
1916      *         a terminated <code>JoinManager</code> instance. Note that this 
1917      *         exception is implementation-specific.
1918      *
1919      * @see net.jini.core.lookup.ServiceID
1920      * @see net.jini.discovery.DiscoveryManagement
1921      * @see net.jini.discovery.LookupDiscoveryManager
1922      * @see net.jini.lease.LeaseRenewalManager
1923      */
1924      public JoinManager(Object serviceProxy,
1925                         Entry[] attrSets,
1926 			ServiceID serviceID,
1927 			DiscoveryManagement discoveryMgr,
1928 			LeaseRenewalManager leaseMgr)    throws IOException
1929     {
1930        this(serviceProxy, attrSets, serviceID, null, 
1931              getConf(EmptyConfiguration.INSTANCE, leaseMgr, discoveryMgr, serviceProxy)
1932        );
1933     }//end constructor
1934 
1935     /** 
1936      * Constructs an instance of this class, configured using the items
1937      * retrieved through the given <code>Configuration</code>, that will
1938      * register the service with all discovered lookup services, using the
1939      * supplied <code>ServiceID</code>. This constructor is typically used by
1940      * services which have already been assigned a service ID (possibly
1941      * by the service provider itself or as a result of a prior registration
1942      * with some lookup service), and which wish to allow for deployment-time
1943      * configuration of the service's join processing.
1944      * <p>
1945      * The items used to configure the current instance of this class
1946      * are obtained through the object input to the <code>config</code>
1947      * parameter. If <code>null</code> is input to that parameter, a
1948      * <code>NullPointerException</code> is thrown.
1949      * <p>
1950      * Except that the desired <code>ServiceID</code> is supplied through the
1951      * <code>serviceID</code> parameter rather than through a notification
1952      * sent to a <code>ServiceIDListener</code>, all other parameters
1953      * of this form of the constructor, along with their associated semantics,
1954      * are identical to that of the six-argument constructor that takes
1955      * a <code>ServiceIDListener</code>.
1956      *
1957      * @param serviceProxy a reference to the service requesting the services
1958      *                     of this class
1959      * @param attrSets     array of <code>Entry</code> consisting of the
1960      *                     attribute sets with which to register the service.
1961      * @param serviceID    an instance of <code>ServiceID</code> with which to
1962      *                     register the service with all desired lookup
1963      *                     services
1964      * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
1965      *                     object this class should use to manage lookup
1966      *                     service discovery on behalf of the given service
1967      * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
1968      *                     object this class should use to manage the leases
1969      *                     on the given service's residency in the lookup 
1970      *                     services that have been joined
1971      * @param config       instance of <code>Configuration</code> through
1972      *                     which the items used to configure the current
1973      *                     instance of this class are obtained
1974      *
1975      * @throws java.lang.IllegalArgumentException if the object input to the
1976      *         <code>serviceProxy</code> parameter is not serializable
1977      * @throws java.lang.NullPointerException if <code>null</code> is input
1978      *         to the <code>serviceProxy</code> parameter or the
1979      *         <code>config</code> parameter, or if at least one of the
1980      *         elements of the <code>attrSets</code> parameter is
1981      *         <code>null</code>
1982      * @throws java.io.IOException if initiation of discovery process results
1983      *         in <code>IOException</code> when socket allocation occurs
1984      * @throws net.jini.config.ConfigurationException if an exception
1985      *         occurs while retrieving an item from the given
1986      *         <code>Configuration</code> object
1987      *
1988      * @throws java.lang.IllegalStateException if this method is called on 
1989      *         a terminated <code>JoinManager</code> instance. Note that this 
1990      *         exception is implementation-specific.
1991      *
1992      * @see net.jini.core.lookup.ServiceID
1993      * @see net.jini.discovery.DiscoveryManagement
1994      * @see net.jini.discovery.LookupDiscoveryManager
1995      * @see net.jini.lease.LeaseRenewalManager
1996      * @see net.jini.config.Configuration
1997      * @see net.jini.config.ConfigurationException
1998      */
1999      public JoinManager(Object serviceProxy,
2000                         Entry[] attrSets,
2001 			ServiceID serviceID,
2002 			DiscoveryManagement discoveryMgr,
2003 			LeaseRenewalManager leaseMgr,
2004                         Configuration config)
2005                                     throws IOException, ConfigurationException
2006     {
2007         this(serviceProxy, attrSets, serviceID, null, 
2008              getConfig(config, leaseMgr, discoveryMgr, serviceProxy)
2009        );
2010     }//end constructor
2011 
2012     /** 
2013      * Returns the instance of <code>DiscoveryManagement</code> that was
2014      * either passed into the constructor, or that was created as a result
2015      * of <code>null</code> being input to that parameter.
2016      * <p>
2017      * The object returned by this method encapsulates the mechanism by which
2018      * either the <code>JoinManager</code> or the entity itself can set
2019      * discovery listeners and discard previously discovered lookup services
2020      * when they are found to be unavailable.
2021      *
2022      * @return the instance of the <code>DiscoveryManagement</code> interface
2023      *         that was either passed into the constructor, or that was
2024      *         created as a result of <code>null</code> being input to that
2025      *         parameter.
2026      * 
2027      * @see net.jini.discovery.DiscoveryManagement
2028      * @see net.jini.discovery.LookupDiscoveryManager
2029      */
2030     public DiscoveryManagement getDiscoveryManager(){
2031         if(bTerminated) 
2032             throw new IllegalStateException("join manager was terminated");
2033 	return discMgr; 
2034     }//end getDiscoveryManager
2035 
2036     /** 
2037      * Returns the instance of the <code>LeaseRenewalManager</code> class
2038      * that was either passed into the constructor, or that was created
2039      * as a result of <code>null</code> being input to that parameter.
2040      * <p>
2041      * The object returned by this method manages the leases requested and
2042      * held by the <code>JoinManager</code>. Although it may also manage
2043      * leases unrelated to the join process that are requested and held by
2044      * the service itself, the leases with which the <code>JoinManager</code>
2045      * is concerned are the leases that correspond to the service registration
2046      * requests made with each lookup service the service wishes to join.
2047      *
2048      * @return the instance of the <code>LeaseRenewalManager</code> class
2049      *         that was either passed into the constructor, or that was
2050      *         created as a result of <code>null</code> being input to that
2051      *         parameter.
2052      * 
2053      * @see net.jini.discovery.DiscoveryManagement
2054      * @see net.jini.lease.LeaseRenewalManager
2055      */
2056     public LeaseRenewalManager getLeaseRenewalManager(){
2057         if(bTerminated) 
2058             throw new IllegalStateException("join manager was terminated");
2059 	return leaseRenewalMgr;
2060     }//end getLeaseRenewalManager
2061 
2062     /** 
2063      * Returns an array of <code>ServiceRegistrar</code> objects, each
2064      * corresponding to a lookup service with which the service is currently
2065      * registered (joined). If there are no lookup services with which the
2066      * service is currently registered, this method returns the empty array.
2067      * This method returns a new array upon each invocation.
2068      *
2069      * @return array of instances of <code>ServiceRegistrar</code>, each
2070      *         corresponding to a lookup service with which the service is
2071      *         currently registered 
2072      * 
2073      * @see net.jini.core.lookup.ServiceRegistrar
2074      */
2075     public ServiceRegistrar[] getJoinSet() {
2076         if(bTerminated) throw new IllegalStateException("join manager was terminated");
2077         List<ServiceRegistrar> retList = new LinkedList<ServiceRegistrar>();
2078         for (Iterator<ProxyReg> iter = joinSet.iterator(); iter.hasNext(); ) {
2079             ProxyReg proxyReg = iter.next();
2080             if(proxyReg.srvcRegistration != null) {//test registration flag
2081                 retList.add(proxyReg.proxy);
2082             }//endif
2083         }//end loop
2084         return ( (retList.toArray(new ServiceRegistrar[retList.size()]) ) );
2085     }//end getJoinSet
2086 
2087     /** 
2088      * Returns an array containing the set of attributes currently associated
2089      * with the service. If the service is not currently associated with an
2090      * attribute set, this method returns the empty array. This method returns
2091      * a new array upon each invocation.
2092      *
2093      * @return array of instances of <code>Entry</code> consisting of the
2094      *         set of attributes with which the service is registered in
2095      *         each lookup service that it has joined
2096      * 
2097      * @see net.jini.core.entry.Entry
2098      * @see #setAttributes
2099      */
2100     public Entry[] getAttributes() {
2101         if(bTerminated) throw new IllegalStateException("join manager was terminated");
2102         Entry[] result = lookupAttr.clone();
2103 	for (int i = 0, l = result.length; i < l; i++){
2104 	    if (result[i] instanceof CloneableEntry){
2105 		result[i] = ((CloneableEntry)result[i]).clone();
2106 	    }
2107 	}
2108 	return result;
2109     }//end getAttributes
2110 
2111     /** 
2112      * Associates a new set of attributes with the service, in addition to
2113      * the service's current set of attributes. The association of this new
2114      * set of attributes with the service will be propagated to each lookup
2115      * service with which the service is registered. Note that this
2116      * propagation is performed asynchronously, thus there is no guarantee
2117      * that the propagation of the attributes to all lookup services with
2118      * which the service is registered will have completed upon return from
2119      * this method.
2120      * <p>
2121      * An invocation of this method with duplicate elements in the 
2122      * <code>attrSets</code> parameter (where duplication means attribute
2123      * equality as defined by calling the <code>MarshalledObject.equals</code>
2124      * method on field values) is equivalent to performing the invocation
2125      * with the duplicates removed from that parameter.
2126      * <p>
2127      * Note that because there is no guarantee that attribute propagation
2128      * will have completed upon return from this method, services that 
2129      * invoke this method must take care not to modify the contents of the 
2130      * <code>attrSets</code> parameter. Doing so could cause the service's
2131      * attribute state to be corrupted or inconsistent on a subset of the
2132      * lookup services with which the service is registered as compared with
2133      * the state reflected on the remaining lookup services. It is for this
2134      * reason that the effects of modifying the contents of the
2135      * <code>attrSets</code> parameter, after this method is invoked, are
2136      * undefined.
2137      *
2138      * @param attrSets array of <code>Entry</code> consisting of the
2139      *                 attribute sets with which to augment the service's
2140      *                 current set of attributes
2141      *
2142      * @throws java.lang.NullPointerException if either <code>null</code> is
2143      *         input to the <code>attrSets</code> parameter, or one or more
2144      *         of the elements of the <code>attrSets</code> parameter is
2145      *         <code>null</code>
2146      *
2147      * @see net.jini.core.entry.Entry
2148      */
2149     public void addAttributes(Entry[] attrSets) {
2150 	addAttributes(attrSets, false);
2151     }//end addAttributes
2152 
2153     /** 
2154      * Associates a new set of attributes with the service, in addition to
2155      * the service's current set of attributes. The association of this new
2156      * set of attributes with the service will be propagated to each lookup
2157      * service with which the service is registered. Note that this
2158      * propagation is performed asynchronously, thus there is no guarantee
2159      * that the propagation of the attributes to all lookup services with
2160      * which the service is registered will have completed upon return from
2161      * this method.
2162      * <p>
2163      * An invocation of this method with duplicate elements in the 
2164      * <code>attrSets</code> parameter (where duplication means attribute
2165      * equality as defined by calling the <code>MarshalledObject.equals</code>
2166      * method on field values) is equivalent to performing the invocation
2167      * with the duplicates removed from that parameter.
2168      * <p>
2169      * Note that because there is no guarantee that attribute propagation
2170      * will have completed upon return from this method, services that 
2171      * invoke this method must take care not to modify the contents of the 
2172      * <code>attrSets</code> parameter. Doing so could cause the service's
2173      * attribute state to be corrupted or inconsistent on a subset of the
2174      * lookup services with which the service is registered as compared with
2175      * the state reflected on the remaining lookup services. It is for this
2176      * reason that the effects of modifying the contents of the
2177      * <code>attrSets</code> parameter, after this method is invoked, are
2178      * undefined.
2179      * <p>
2180      * A service typically employs this version of <code>addAttributes</code> 
2181      * to prevent clients or other services from attempting to add what are
2182      * referred to as "service controlled attributes" to the service's set.
2183      * A service controlled attribute is an attribute that implements the
2184      * <code>ServiceControlled</code> marker interface.
2185      * <p>
2186      * Consider a printer service. With printers, there are often times error
2187      * conditions, that only the printer can detect (for example, a paper
2188      * jam or a toner low condition). To report conditions such as these to
2189      * interested parties, the printer typically adds an attribute to its
2190      * attribute set, resulting in an event being sent that notifies clients
2191      * that have registered interest in such events. When the condition is
2192      * corrected, the printer would then remove the attribute from its set
2193      * by invoking the <code>modifyAttributes</code> method in the appropriate
2194      * manner.
2195      * <p>
2196      * Attributes representing conditions that only the service can know about
2197      * or control are good candidates for being defined as service controlled
2198      * attributes. That is, the service provider (the developer of the printer
2199      * service for example) would define the attributes that represent
2200      * conditions such as those just described to implement the
2201      * <code>ServiceControlled</code> marker interface. Thus, when other
2202      * entities attempt to add new attributes, services that wish to employ
2203      * such service controlled attributes should ultimately invoke only this
2204      * version of <code>addAttributes</code> (with the <code>checkSC</code>
2205      * parameter set to <code>true</code>), resulting in a
2206      * <code>SecurityException</code> if any of the attributes being added
2207      * happen to be service controlled attributes. In this way, only the
2208      * printer itself would be able to set a "paper jammed" or "toner low"
2209      * attribute, not some arbitrary client.
2210      *
2211      * @param attrSets array of <code>Entry</code> consisting of the
2212      *                 attribute sets with which to augment the service's
2213      *                 current set of attributes
2214      * @param checkSC  <code>boolean</code> flag indicating whether the
2215      *                 elements of the set of attributes to add should be
2216      *                 checked to determine if they are service controlled
2217      *                 attributes
2218      *
2219      * @throws java.lang.NullPointerException if either <code>null</code> is
2220      *         input to the <code>attrSets</code> parameter, or one or more
2221      *         of the elements of the <code>attrSets</code> parameter is
2222      *         <code>null</code>
2223      *
2224      * @throws java.lang.SecurityException if the <code>checkSC</code>
2225      *         parameter is <code>true</code>, and at least one of the
2226      *         attributes to be added is an instance of the
2227      *         <code>ServiceControlled</code> marker interface
2228      *
2229      * @see net.jini.core.entry.Entry
2230      * @see net.jini.lookup.entry.ServiceControlled
2231      */
2232     public void addAttributes(Entry[] attrSets, boolean checkSC) {
2233         if(bTerminated) throw new IllegalStateException("join manager was terminated");
2234 	synchronized(this) {
2235 	    lookupAttr = LookupAttributes.add(lookupAttr, attrSets, checkSC);
2236             serviceItem = new ServiceItem(serviceItem.serviceID, serviceItem.service, lookupAttr);
2237         }
2238         Iterator<ProxyReg> it = joinSet.iterator();
2239         while (it.hasNext()){
2240             ProxyReg proxyReg = it.next();
2241             proxyReg.addTask(new AddAttributesTask(proxyReg,attrSets, context));
2242         }//end loop
2243     }//end addAttributes
2244 
2245     /** 
2246      * Replaces the service's current set of attributes with a new set of
2247      * attributes. The association of this new set of attributes with the
2248      * service will be propagated to each lookup service with which the
2249      * service is registered. Note that this propagation is performed
2250      * asynchronously, thus there is no guarantee that the propagation of
2251      * the attributes to all lookup services with which the service is
2252      * registered will have completed upon return from this method.
2253      * <p>
2254      * An invocation of this method with duplicate elements in the 
2255      * <code>attrSets</code> parameter (where duplication means attribute
2256      * equality as defined by calling the <code>MarshalledObject.equals</code>
2257      * method on field values) is equivalent to performing the invocation
2258      * with the duplicates removed from that parameter.
2259      * <p>
2260      * Note that because there is no guarantee that attribute propagation
2261      * will have completed upon return from this method, services that 
2262      * invoke this method must take care not to modify the contents of the 
2263      * <code>attrSets</code> parameter. Doing so could cause the service's
2264      * attribute state to be corrupted or inconsistent on a subset of the
2265      * lookup services with which the service is registered as compared with
2266      * the state reflected on the remaining lookup services. It is for this
2267      * reason that the effects of modifying the contents of the
2268      * <code>attrSets</code> parameter, after this method is invoked, are
2269      * undefined.
2270      *
2271      * @param attrSets array of <code>Entry</code> consisting of the
2272      *                 attribute sets with which to replace the service's
2273      *                 current set of attributes
2274      *
2275      * @throws java.lang.NullPointerException if either <code>null</code> is
2276      *         input to the <code>attrSets</code> parameter, or one or more
2277      *         of the elements of the <code>attrSets</code> parameter is
2278      *         <code>null</code>.
2279      *
2280      * @see net.jini.core.entry.Entry
2281      * @see #getAttributes
2282      */
2283     public void setAttributes(Entry[] attrSets) {
2284         if(bTerminated) 
2285             throw new IllegalStateException("join manager was terminated");
2286         testForNullElement(attrSets);
2287 	synchronized(this) {
2288             lookupAttr = (Entry[]) attrSets.clone();
2289             serviceItem = new ServiceItem(serviceItem.serviceID, 
2290                                           serviceItem.service, lookupAttr);
2291         }
2292         Iterator<ProxyReg> it = joinSet.iterator();
2293         while (it.hasNext()){
2294             ProxyReg proxyReg = it.next();
2295             proxyReg.addTask(new SetAttributesTask(proxyReg,attrSets, context));
2296         }
2297     }//end setAttributes
2298 
2299     /** 
2300      * Changes the service's current set of attributes using the same
2301      * semantics as the <code>modifyAttributes</code> method of the
2302      * <code>ServiceRegistration</code> class.
2303      * <p>
2304      * The association of the new set of attributes with the service will
2305      * be propagated to each lookup service with which the service is
2306      * registered. Note that this propagation is performed asynchronously,
2307      * thus there is no guarantee that the propagation of the attributes to
2308      * all lookup services with which the service is registered will have
2309      * completed upon return from this method.
2310      * <p>
2311      * Note that if the length of the array containing the templates does
2312      * not equal the length of the array containing the modifications, an
2313      * <code>IllegalArgumentException</code> will be thrown and propagated
2314      * through this method.
2315      * <p>
2316      * Note also that because there is no guarantee that attribute propagation
2317      * will have completed upon return from this method, services that 
2318      * invoke this method must take care not to modify the contents of the 
2319      * <code>attrSets</code> parameter. Doing so could cause the service's
2320      * attribute state to be corrupted or inconsistent on a subset of the
2321      * lookup services with which the service is registered as compared with
2322      * the state reflected on the remaining lookup services. It is for this
2323      * reason that the effects of modifying the contents of the
2324      * <code>attrSets</code> parameter, after this method is invoked, are
2325      * undefined.
2326      *
2327      * @param attrSetTemplates array of <code>Entry</code> used to identify
2328      *                         which elements to modify from the service's
2329      *                         current set of attributes
2330      * @param attrSets         array of <code>Entry</code> containing the
2331      *                         actual modifications to make in the matching
2332      *                         sets found using the 
2333      *                         <code>attrSetTemplates</code> parameter
2334      *
2335      * @throws java.lang.IllegalArgumentException if the array containing the
2336      *         templates does not equal the length of the array containing the
2337      *         modifications
2338      *
2339      * @see net.jini.core.entry.Entry
2340      * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
2341      */
2342     public void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets) {
2343 	modifyAttributes(attrSetTemplates, attrSets, false);
2344     }//end modifyAttributes
2345 
2346     /** 
2347      * Changes the service's current set of attributes using the same
2348      * semantics as the <code>modifyAttributes</code> method of the
2349      * <code>ServiceRegistration</code> class.
2350      * <p>
2351      * The association of the new set of attributes with the service will
2352      * be propagated to each lookup service with which the service is
2353      * registered. Note that this propagation is performed asynchronously,
2354      * thus there is no guarantee that the propagation of the attributes to
2355      * all lookup services with which the service is registered will have
2356      * completed upon return from this method.
2357      * <p>
2358      * Note that if the length of the array containing the templates does
2359      * not equal the length of the array containing the modifications, an
2360      * <code>IllegalArgumentException</code> will be thrown and propagated
2361      * through this method.
2362      * <p>
2363      * Note also that because there is no guarantee that attribute propagation
2364      * will have completed upon return from this method, services that 
2365      * invoke this method must take care not to modify the contents of the 
2366      * <code>attrSets</code> parameter. Doing so could cause the service's
2367      * attribute state to be corrupted or inconsistent on a subset of the
2368      * lookup services with which the service is registered as compared with
2369      * the state reflected on the remaining lookup services. It is for this
2370      * reason that the effects of modifying the contents of the
2371      * <code>attrSets</code> parameter, after this method is invoked, are
2372      * undefined.
2373      * <p>
2374      * A service typically employs this version of 
2375      * <code>modifyAttributes</code> to prevent clients or other services
2376      * from attempting to modify what are referred to as "service controlled
2377      * attributes" in the service's set. A service controlled attribute is an
2378      * attribute that implements the <code>ServiceControlled</code> marker
2379      * interface.
2380      * <p>
2381      * Attributes representing conditions that only the service can know about
2382      * or control are good candidates for being defined as service controlled
2383      * attributes. When other entities attempt to modify a service's
2384      * attributes, if the service wishes to employ such service controlled
2385      * attributes, the service should ultimately invoke only this version 
2386      * of <code>modifyAttributes</code> (with the <code>checkSC</code>
2387      * parameter set to <code>true</code>), resulting in a
2388      * <code>SecurityException</code> if any of the attributes being modified
2389      * happen to be service controlled attributes.
2390      *
2391      * @param attrSetTemplates array of <code>Entry</code> used to identify
2392      *                         which elements to modify from the service's
2393      *                         current set of attributes
2394      * @param attrSets         array of <code>Entry</code> containing the
2395      *                         actual modifications to make in the matching
2396      *                         sets found using the 
2397      *                         <code>attrSetTemplates</code> parameter
2398      * @param checkSC          <code>boolean</code> flag indicating whether the
2399      *                         elements of the set of attributes to modify
2400      *                         should be checked to determine if they are
2401      *                         service controlled attributes
2402      *
2403      * @throws java.lang.IllegalArgumentException if the array containing the
2404      *         templates does not equal the length of the array containing
2405      *         the modifications
2406      *
2407      * @throws java.lang.SecurityException if the <code>checkSC</code>
2408      *         parameter is <code>true</code>, and at least one of the
2409      *         attributes to be modified is an instance of the
2410      *         <code>ServiceControlled</code> marker interface
2411      *
2412      * @see net.jini.core.entry.Entry
2413      * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
2414      * @see net.jini.lookup.entry.ServiceControlled
2415      */
2416     public void modifyAttributes(Entry[] attrSetTemplates,
2417                                  Entry[] attrSets,
2418                                  boolean checkSC)
2419     {
2420         if(bTerminated) 
2421             throw new IllegalStateException("join manager was terminated");
2422 	synchronized(this) {
2423 	    lookupAttr = LookupAttributes.modify(lookupAttr, attrSetTemplates,
2424                                                  attrSets, checkSC);
2425             serviceItem = new ServiceItem(serviceItem.serviceID,
2426                                           serviceItem.service, lookupAttr);
2427         }//end sync
2428         Iterator<ProxyReg> it = joinSet.iterator();
2429         while (it.hasNext()){
2430             ProxyReg proxyReg = it.next();
2431             proxyReg.addTask(new ModifyAttributesTask(proxyReg,
2432                                                       attrSetTemplates,
2433                                                       attrSets, context));
2434         }//end loop
2435     }//end modifyAttributes
2436 
2437     /**
2438      * Performs cleanup duties related to the termination of the lookup
2439      * service discovery event mechanism, as well as the lease and 
2440      * thread management performed by the <code>JoinManager</code>. This
2441      * method will cancel all of the service's managed leases that were
2442      * granted by the lookup services with which the service is registered,
2443      * and will terminate all threads that have been created.
2444      * <p>
2445      * Note that if the discovery manager employed by the instance of this
2446      * class that is being terminated was created by the instance itself,
2447      * this method will terminate all discovery processing being performed by
2448      * that manager object on behalf of the service; otherwise, the discovery
2449      * manager supplied by the service is still valid.
2450      * <p>
2451      * Whether an instance of the <code>LeaseRenewalManager</code> class was
2452      * supplied by the service or created by the <code>JoinManager</code>
2453      * itself, any reference to that object obtained by the service prior to
2454      * termination will still be valid after termination.
2455      * Note also this class makes certain concurrency guarantees with respect
2456      * to an invocation of the terminate method while other method invocations
2457      * are in progress. The termination process will not begin until
2458      * completion of all invocations of the methods defined in the public
2459      * interface of this class. Furthermore, once the termination process has
2460      * begun, no further remote method invocations will be made by this class,
2461      * and all other method invocations made on this class will not return
2462      * until the termination process has completed.
2463      * <p>
2464      * Upon completion of the termination process, the semantics of all
2465      * current and future method invocations on the instance of this class
2466      * that was just terminated are undefined; although the reference to the
2467      * <code>LeaseRenewalManager</code> object employed by that instance
2468      * of <code>JoinManager</code> is still valid.
2469      */
2470     public void terminate() {
2471         synchronized(this) {
2472             if(bTerminated) return;//allow for multiple terminations
2473             bTerminated = true;
2474         }//end sync(this)
2475         /* Terminate discovery and task management */
2476         discMgr.removeDiscoveryListener(discMgrListener);
2477         if(bCreateDiscMgr)  discMgr.terminate();
2478         terminateTaskMgr();
2479         /* Clear the joinSet and cancel all leases held by the service */
2480         Iterator<ProxyReg> iter = joinSet.iterator();
2481         while (iter.hasNext()) {
2482             try {
2483                 leaseRenewalMgr.cancel(iter.next().serviceLease );
2484             } catch (UnknownLeaseException e){
2485             } catch (RemoteException e) {}
2486         }//end loop
2487         leaseRenewalMgr.close();
2488         joinSet.clear();
2489     }//end terminate
2490 
2491     /** 
2492      * Registers a new reference to the service with all current and future
2493      * discovered lookup services. The new service reference will replace
2494      * the reference that was previously registered as a result of either
2495      * constructing this utility, or a prior invocation of one of the forms
2496      * of this method. The new service reference will be registered using
2497      * the same <code>ServiceID</code> with which previous registrations
2498      * were made through this utility.
2499      * <p>
2500      * The value input to the <code>serviceProxy</code> parameter represents
2501      * the new service reference (proxy) to register with each discovered
2502      * lookup service. If the <code>Object</code> input to that parameter is
2503      * not <code>Serializable</code>, an <code>IllegalArgumentException</code>
2504      * is thrown. If <code>null</code> is input to that parameter, a
2505      * <code>NullPointerException</code> is thrown.
2506      * <p>
2507      * The attribute sets that this method associates with the new service
2508      * reference are the same attribute sets as those associated with the
2509      * old registration.
2510      *
2511      * @param serviceProxy the new service reference (proxy) to register with
2512      *                     all current and future discovered lookup services
2513      *
2514      * @throws java.lang.IllegalArgumentException if the object input to the
2515      *         <code>serviceProxy</code> parameter is not serializable
2516      * @throws java.lang.NullPointerException if <code>null</code> is input
2517      *         to the <code>serviceProxy</code> parameter
2518      *
2519      * @throws java.lang.IllegalStateException if this method is called on 
2520      *         a terminated <code>JoinManager</code> instance. Note that this 
2521      *         exception is implementation-specific.
2522      */
2523     public void replaceRegistration(Object serviceProxy) {
2524         replaceRegistrationDo(serviceProxy, null, false);
2525     }//end replaceRegistration
2526 
2527     /** 
2528      * Registers a new reference to the service with all current and future
2529      * discovered lookup services, applying semantics identical to the 
2530      * one-argument form of this method, except with respect to the
2531      * registration of the given attribute sets.
2532      * <p>
2533      * This form of <code>replaceRegistration</code> takes as its
2534      * second parameter, an array of <code>Entry</code> objects
2535      * (<code>attrSets</code>), none of whose elements may be
2536      * <code>null</code>, that represents the new set of attributes to
2537      * associate with the new service reference to be registered. As with
2538      * the constructor to this utility, passing <code>null</code> as the
2539      * value of the <code>attrSets</code> parameter is equivalent to passing
2540      * an empty array. If any of the elements of <code>attrSets</code> are 
2541      * <code>null</code>, a <code>NullPointerException</code> is thrown.
2542      * This new set of attributes will be associated with the service in
2543      * all future join processing.
2544      *
2545      * @param serviceProxy the new service reference (proxy) to register with
2546      *                     all current and future discovered lookup services
2547      * @param attrSets     array of <code>Entry</code> consisting of the
2548      *                     attribute sets with which to register the new
2549      *                     service reference. Passing <code>null</code> as
2550      *                     the value of this parameter is equivalent to
2551      *                     passing an empty <code>Entry</code> array
2552      *
2553      * @throws java.lang.IllegalArgumentException if the object input to the
2554      *         <code>serviceProxy</code> parameter is not serializable
2555      * @throws java.lang.NullPointerException if either <code>null</code> is
2556      *         input to the <code>serviceProxy</code> parameter, or at least
2557      *         one of the elements of the <code>attrSets</code> parameter is
2558      *         <code>null</code>
2559      *
2560      * @throws java.lang.IllegalStateException if this method is called on 
2561      *         a terminated <code>JoinManager</code> instance. Note that this 
2562      *         exception is implementation-specific.
2563      */
2564     public void replaceRegistration(Object serviceProxy, Entry[] attrSets) {
2565         replaceRegistrationDo(serviceProxy, attrSets, true);
2566     }//end replaceRegistration
2567 
2568     private static class Conf{
2569         ProxyPreparer registrarPreparer;
2570         ProxyPreparer registrationPreparer;
2571         ProxyPreparer serviceLeasePreparer;
2572         ExecutorService executorService;
2573         WakeupManager wakeupManager;
2574         Integer maxNretrys;
2575         LeaseRenewalManager leaseRenewalManager;
2576         Long renewalDuration;
2577         DiscoveryManagement discoveryMgr;
2578         boolean bcreateDisco;
2579         
2580         Conf (  ProxyPreparer registrarPreparer,
2581                 ProxyPreparer registrationPreparer,
2582                 ProxyPreparer serviceLeasePreparer,
2583                 ExecutorService taskManager,
2584                 WakeupManager wakeupManager,
2585                 Integer maxNretrys,
2586                 LeaseRenewalManager leaseRenewalManager,
2587                 Long renewalDuration,
2588                 DiscoveryManagement discoveryMgr,
2589                 boolean bcreateDisco)
2590         {
2591             this.registrarPreparer = registrarPreparer;
2592             this.registrationPreparer = registrationPreparer;
2593             this.serviceLeasePreparer = serviceLeasePreparer;
2594             this.executorService = taskManager;
2595             this.wakeupManager = wakeupManager;
2596             this.maxNretrys = maxNretrys;
2597             this.leaseRenewalManager = leaseRenewalManager;
2598             this.renewalDuration = renewalDuration;
2599             this.discoveryMgr = discoveryMgr;
2600             this.bcreateDisco = bcreateDisco;
2601         }
2602     }
2603     
2604     /**
2605      * This method is for constructors that use an empty configuration.
2606      * 
2607      * @param config
2608      * @param leaseMgr
2609      * @param discoveryMgr
2610      * @param serviceProxy
2611      * @return Conf
2612      * @throws IOException
2613      * @throws NullPointerException
2614      * @throws IllegalArgumentException 
2615      */
2616     private static Conf getConf(    Configuration config,
2617                                    LeaseRenewalManager leaseMgr,
2618                                     DiscoveryManagement discoveryMgr,
2619                                     Object serviceProxy) 
2620             throws IOException, NullPointerException, IllegalArgumentException {
2621         try {
2622             return getConfig(config, leaseMgr, discoveryMgr, serviceProxy);
2623         } catch (ConfigurationException e){
2624             throw new IOException("Configuration problem during construction", e);
2625         }
2626     }
2627     
2628     /**
2629      * Gets the configuration and throws any exceptions.
2630      * 
2631      * This static method guards against finalizer attacks and allows fields
2632      * to be final.
2633      * 
2634      * @param config
2635      * @param leaseMgr
2636      * @param discoveryMgr
2637      * @param serviceProxy
2638      * @return Conf
2639      * @throws IOException
2640      * @throws ConfigurationException
2641      * @throws NullPointerException
2642      * @throws IllegalArgumentException 
2643      */
2644     private static Conf getConfig(  Configuration config,
2645                                     LeaseRenewalManager leaseMgr, 
2646                                     DiscoveryManagement discoveryMgr,
2647                                     Object serviceProxy) 
2648             throws IOException, ConfigurationException, NullPointerException,
2649             IllegalArgumentException 
2650     {
2651 	if(!(serviceProxy instanceof java.io.Serializable)) {
2652             throw new IllegalArgumentException
2653                                        ("serviceProxy must be Serializable");
2654 	}//endif
2655         /* Retrieve configuration items if applicable */
2656         if(config == null)  throw new NullPointerException("config is null");
2657         /* Proxy preparers */
2658         ProxyPreparer registrarPreparer = config.getEntry
2659                                                    (COMPONENT_NAME,
2660                                                     "registrarPreparer",
2661                                                     ProxyPreparer.class,
2662                                                     new BasicProxyPreparer());
2663         ProxyPreparer registrationPreparer = config.getEntry
2664                                                    (COMPONENT_NAME,
2665                                                     "registrationPreparer",
2666                                                     ProxyPreparer.class,
2667                                                     new BasicProxyPreparer());
2668         ProxyPreparer serviceLeasePreparer = config.getEntry
2669                                                    (COMPONENT_NAME,
2670                                                     "serviceLeasePreparer",
2671                                                     ProxyPreparer.class,
2672                                                     new BasicProxyPreparer());
2673         /* Task manager */
2674         ExecutorService taskMgr;
2675         try {
2676             taskMgr = config.getEntry(COMPONENT_NAME,
2677                                        "executorService",
2678                                        ExecutorService.class);
2679         } catch(NoSuchEntryException e) { /* use default */
2680             taskMgr = new ThreadPoolExecutor(
2681                 MAX_N_TASKS, 
2682                 MAX_N_TASKS, /* Ignored */
2683                 15,
2684                 TimeUnit.SECONDS,
2685                 new LinkedBlockingQueue(), /* Unbounded Queue */
2686                 new NamedThreadFactory("JoinManager executor thread", false)
2687             );
2688         }
2689         /* Wakeup manager */
2690         WakeupManager wakeupMgr;
2691         try {
2692             wakeupMgr = config.getEntry(COMPONENT_NAME,
2693                                                        "wakeupManager",
2694                                                        WakeupManager.class);
2695         } catch(NoSuchEntryException e) { /* use default */
2696             wakeupMgr = new WakeupManager
2697                                     (new WakeupManager.ThreadDesc(null,true));
2698         }
2699         /* Max number of times to re-schedule tasks in thru wakeup manager */
2700         int maxNRetries = (config.getEntry
2701                                         (COMPONENT_NAME,
2702                                          "wakeupRetries",
2703                                          int.class,
2704                                          Integer.valueOf(6))).intValue();
2705         /* Lease renewal manager */
2706 	if(leaseMgr == null) {
2707             try {
2708                 leaseMgr = config.getEntry
2709                                                   (COMPONENT_NAME,
2710                                                    "leaseManager",
2711                                                    LeaseRenewalManager.class);
2712             } catch(NoSuchEntryException e) { /* use default */
2713                 leaseMgr = new LeaseRenewalManager(config);
2714             }
2715         }//endif
2716         long renewalDuration = (config.getEntry
2717                                       (COMPONENT_NAME,
2718                                        "maxLeaseDuration",
2719                                        long.class,
2720                                        Long.valueOf(Lease.FOREVER))).longValue();
2721         if( (renewalDuration == 0) || (renewalDuration < Lease.ANY) ) {
2722             throw new ConfigurationException("invalid configuration entry: "
2723                                              +"renewalDuration ("
2724                                              +renewalDuration+") must be "
2725                                              +"positive or Lease.ANY");
2726         }//endif
2727         /* Discovery manager */
2728         boolean bCreateDiscMgr = false;
2729 	if(discoveryMgr == null) {
2730 	    bCreateDiscMgr = true;
2731             try {
2732                 discoveryMgr = config.getEntry
2733                                                  (COMPONENT_NAME,
2734                                                   "discoveryManager",
2735                                                   DiscoveryManagement.class);
2736             } catch(NoSuchEntryException e) { /* use default */
2737                 discoveryMgr = new LookupDiscoveryManager
2738                                      (new String[] {""}, null, null, config);
2739             }
2740 	}//endif
2741         return new Conf(registrarPreparer, registrationPreparer, serviceLeasePreparer,
2742                 taskMgr, wakeupMgr, maxNRetries, leaseMgr, renewalDuration,
2743                 discoveryMgr, bCreateDiscMgr);
2744     }
2745     
2746     /** Convenience method invoked by the constructors of this class that
2747      *  uses the given <code>Configuration</code> to initialize the current
2748      *  instance of this utility, and initiates all join processing for
2749      *  the given parameters. This method handles the various configurations
2750      *  allowed by the different constructors.
2751      */
2752     private JoinManager(Object serviceProxy,
2753                                    Entry[] attrSets, ServiceID serviceID, 
2754                                    ServiceIDListener callback, Conf conf)
2755     {
2756 	context = AccessController.getContext();
2757 	registrarPreparer = conf.registrarPreparer;
2758         registrationPreparer = conf.registrationPreparer;
2759         serviceLeasePreparer = conf.serviceLeasePreparer;
2760         executor = new ExtensibleExecutorService(conf.executorService, 
2761                 new RunnableFutureFactory(){
2762 
2763             @Override
2764             public <T> RunnableFuture<T> newTaskFor(Runnable r, T value) {
2765                 if (r instanceof ProxyRegTask) return (RunnableFuture<T>) r;
2766                 throw new IllegalStateException("Runnable not instance of ProxyRegTask");
2767             }
2768 
2769             @Override
2770             public <T> RunnableFuture<T> newTaskFor(Callable<T> c) {
2771                 if (c instanceof ProxyRegTask) return (RunnableFuture<T>) c;
2772                 throw new IllegalStateException("Callable not instance of ProxyRegTask");
2773             }
2774             
2775         });
2776         proxyRegTaskQueue = new ProxyRegTaskQueue(executor);
2777         wakeupMgr = conf.wakeupManager;
2778         maxNRetries = conf.maxNretrys;
2779         leaseRenewalMgr = conf.leaseRenewalManager;
2780         renewalDuration = conf.renewalDuration;
2781         bCreateDiscMgr = conf.bcreateDisco;
2782         DiscMgrListener discMgrListen = new DiscMgrListener();
2783         if(attrSets == null) {
2784             lookupAttr = new Entry[0];
2785         } else {
2786             attrSets = attrSets.clone();
2787             LookupAttributes.check(attrSets,false);//null elements NOT ok
2788             lookupAttr = attrSets;
2789         }//endif
2790 	serviceItem = new ServiceItem(serviceID, serviceProxy, lookupAttr);
2791 	this.callback = callback;
2792 	conf.discoveryMgr.addDiscoveryListener(discMgrListen);
2793         discMgr = conf.discoveryMgr;
2794         discMgrListener = discMgrListen;
2795     }//end createJoinManager
2796 
2797     /** For the given lookup service proxy, searches the <code>joinSet</code>
2798      *  for the corresponding <code>ProxyReg</code> element, and upon finding
2799      *  such an element, returns that element; otherwise returns
2800      *  <code>null</code>.
2801      */
2802     private ProxyReg findReg(ServiceRegistrar proxy) {
2803 	for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
2804 	    ProxyReg reg =(ProxyReg)iter.next();
2805 	    if(reg.proxy.equals(proxy))  return reg;
2806 	}//end loop
2807 	return null;
2808     }//end findReg
2809 
2810     /** Removes (from the ExecutorService) and cancels (in the wakeup manager)
2811      *  all tasks associated with the given instance of <code>ProxyReg</code>.
2812      */
2813     private void removeTasks(ProxyReg proxyReg) {
2814         if(proxyReg == null) return;
2815         if(executor == null) return;
2816         synchronized(proxyReg.taskList) {
2817             if(proxyReg.proxyRegTask != null) {
2818                 proxyReg.proxyRegTask.cancel(false);                
2819                 proxyReg.proxyRegTask = null;  //don't reuse because of seq#
2820             }//endif
2821             proxyReg.taskList.clear();
2822         }//end sync(proxyReg.taskList)
2823         proxyReg.terminate();
2824     }//end removeTasks
2825 
2826     /** Removes from the ExecutorService, all pending tasks regardless of the
2827      *  the instance of <code>ProxyReg</code> with which the task is
2828      *  associated, and then terminates the ExecutorService, and makes it
2829      *  a candidate for garbage collection.
2830      */
2831     private void terminateTaskMgr() {
2832         synchronized(wakeupMgr) {
2833             /* Cancel all tasks scheduled for future retry by the wakeup mgr */
2834             wakeupMgr.cancelAll();//cancel all tickets
2835             wakeupMgr.stop();//stop execution of the wakeup manager
2836         }
2837         /* Interrupt all active tasks, prepare taskMgr for GC. */
2838         executor.shutdownNow();
2839     }//end terminateTaskMgr
2840 
2841     /** Examines the elements of the input set and, upon finding at least one
2842      *  <code>null</code> element, throws a <code>NullPointerException</code>.
2843      */
2844     private void testForNullElement(Object[] a) {
2845         if(a == null) return;
2846         int l = a.length;
2847         for(int i=0;i<l;i++) {
2848             if(a[i] == null) {
2849                 throw new NullPointerException
2850                           ("input array contains at least one null element");
2851             }//endif
2852         }//end loop
2853     }//end testForNullElement
2854 
2855     /** Convenience method invoked by either form of the method
2856      *  <code>replaceRegistration</code>. This method registers the
2857      *  given <code>serviceProxy</code> with all discovered lookup
2858      *  services, replacing all current registrations. If the value
2859      *  of the <code>doAttrs</code> parameter is <code>true</code>,
2860      *  this method will associate the given <code>attrSets</code>
2861      *  with the new service registration; otherwise, it will use
2862      *  the attribute sets currently associated with the old registration.
2863      */
2864     private void replaceRegistrationDo(Object serviceProxy,
2865                                        Entry[] attrSets,
2866                                        boolean doAttrs)
2867     {
2868         if(bTerminated) 
2869             throw new IllegalStateException("join manager was terminated");
2870 	if(!(serviceProxy instanceof java.io.Serializable)) {
2871             throw new IllegalArgumentException
2872                                         ("serviceProxy must be Serializable");
2873 	}//endif
2874 	synchronized(this) {
2875             if(doAttrs) {
2876                 if(attrSets == null) {
2877                     lookupAttr = new Entry[0];
2878                 } else {
2879                     attrSets = (Entry[])attrSets.clone();
2880                     LookupAttributes.check(attrSets,false);//no null elements
2881                     lookupAttr = attrSets;
2882                 }//endif
2883             }//endif
2884             serviceItem = new ServiceItem(serviceItem.serviceID, serviceProxy, lookupAttr);
2885         }
2886         Iterator<ProxyReg> it = joinSet.iterator();
2887         while (it.hasNext()){
2888             ProxyReg proxyReg = it.next();
2889             removeTasks(proxyReg);
2890             try {
2891                 leaseRenewalMgr.remove( proxyReg.serviceLease );
2892             } catch (UnknownLeaseException e) { }
2893             proxyReg.addTask(new RegisterTask(proxyReg,
2894                                                  (Entry[])lookupAttr.clone(), context));
2895         }//end loop
2896     }//end replaceRegistrationDo
2897 
2898 }//end class JoinManager
2899