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