Package net.jini.lookup

Source Code of net.jini.lookup.JoinManager

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jini.lookup;

import com.sun.jini.constants.ThrowableConstants;
import com.sun.jini.lookup.entry.LookupAttributes;
import com.sun.jini.thread.RetryTask;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.thread.WakeupManager;
import com.sun.jini.logging.LogUtil;

import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.config.NoSuchEntryException;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryManagement;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import net.jini.lease.LeaseRenewalManager;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;

import net.jini.core.entry.Entry;
import net.jini.core.lease.Lease;
import net.jini.core.lease.UnknownLeaseException;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* A goal of any well-behaved service is to advertise the facilities and
* functions it provides by requesting residency within at least one lookup
* service. Making such a request of a lookup service is known as registering
* with, or <i>joining</i>, a lookup service. To demonstrate this good
* behavior, a service must comply with both the multicast discovery protocol
* and the unicast discovery protocol in order to discover the lookup services
* it is interested in joining. The service must also comply with the join
* protocol to register with the desired lookup services.
* <p>
* In order for a service to maintain its residency in the lookup services
* it has joined, the service must provide for the coordination, systematic
* renewal, and overall management of all leases on that residency. In
* addition to handling all discovery and join duties, as well as managing
* all leases on lookup service residency, the service must also provide
* for the coordination and management of any attribute sets with which
* it may have registered with the lookup services in which it resides.
* <p>
* This class performs all of the functions related to discovery, joining,
* service lease renewal, and attribute management which is required of a
* well-behaved service. Each of these activities is intimately involved
* with the maintenance of a service's residency in one or more lookup
* services (the service's join state), thus the name <code>JoinManager</code>.
* <p>
* This class should be employed by services, not clients. The use of this
* class in a wide variety of services can help minimize the work resulting
* from having to repeatedly implement this required functionality in each
* service. Note that this class is not remote. Services that wish to use
* this class will create an instance of this class in the service's address
* space to manage the entity's join state locally.
*
* @com.sun.jini.impl <!-- Implementation Specifics -->
*
* The following implementation-specific items are discussed below:
* <ul><li> <a href="#jmConfigEntries">Configuring JoinManager</a>
*     <li> <a href="#jmLogging">Logging</a>
* </ul>
*
* <a name="jmConfigEntries">
* <p>
* <b><font size="+1">Configuring JoinManager</font></b>
* <p>
* </a>
*
* This implementation of <code>JoinManager</code> supports the following
* configuration entries; where each configuration entry name is associated
* with the component name <code>net.jini.lookup.JoinManager</code>. Note
* that the configuration entries specified here are specific to this
* implementation of <code>JoinManager</code>. Unless otherwise stated, each
* entry is retrieved from the configuration only once per instance of
* this utility, where each such retrieval is performed in the constructor.
*
* <a name="discoveryManager">
* <table summary="Describes the discoveryManager configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>discoveryManager</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link net.jini.discovery.DiscoveryManagement}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code> new
*    {@link net.jini.discovery.LookupDiscoveryManager#LookupDiscoveryManager(
*       java.lang.String[],
*       net.jini.core.discovery.LookupLocator[],
*       net.jini.discovery.DiscoveryListener,
*       net.jini.config.Configuration) LookupDiscoveryManager}(
*                       new java.lang.String[] {""},
*                       new {@link net.jini.core.discovery.LookupLocator}[0],
*                       null, config)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The object used to manage the discovery processing
*            performed by this utility. This entry will be retrieved
*            from the configuration only if no discovery manager is
*            specified in the constructor. Note that this object should
*            not be shared with other components in the application that
*            employs this utility.
* </table>
*
* <a name="leaseManager">
* <table summary="Describes the leaseManager configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>leaseManager</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link net.jini.lease.LeaseRenewalManager}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code> new
*       {@link net.jini.lease.LeaseRenewalManager#LeaseRenewalManager(
*         net.jini.config.Configuration) LeaseRenewalManager}(config)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The object used to manage each service lease returned
*            to this utility when the service is registered with the
*            the various discovered lookup services. This entry will
*            be retrieved from the configuration only if no lease
*            renewal manager is specified in the constructor.
* </table>
*
* <a name="maxLeaseDuration">
* <table summary="Describes the maxLeaseDuration
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>maxLeaseDuration</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>long</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>Lease.FOREVER</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The maximum lease duration (in milliseconds) that is requested
*            from each discovered lookup service on behalf of the service;
*            both when the lease is initially requested, as well as when
*            renewal of that lease is requested. Note that as this value is
*            made smaller, renewal requests will be made more frequently
*            while the service is up, and lease expiration will occur sooner
*            when the service goes down.
* </table>
*
* <a name="registrarPreparer">
* <table summary="Describes the registrarPreparer configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>registrarPreparer</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Type: <td> {@link net.jini.security.ProxyPreparer}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
*                     </code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*   Description:
*     <td> Preparer for the proxies to the lookup services that are
*          discovered and used by this utility.
*          <p>
*          The following methods of the proxy returned by this preparer are
*          invoked by this utility:
*       <ul>
*         <li>{@link net.jini.core.lookup.ServiceRegistrar#register register}
*       </ul>
* </table>
*
* <a name="registrationPreparer">
* <table summary="Describes the registrationPreparer configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>registrationPreparer</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Type: <td> {@link net.jini.security.ProxyPreparer}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
*                     </code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*   Description:
*     <td> Preparer for the proxies to the registrations returned to
*          this utility upon registering the service with each discovered
*          lookup service.
*          <p>
*          The following methods of the proxy returned by this preparer are
*          invoked by this utility:
*       <ul>
*         <li>{@link net.jini.core.lookup.ServiceRegistration#getServiceID
*                                                           getServiceID}
*         <li>{@link net.jini.core.lookup.ServiceRegistration#getLease
*                                                           getLease}
*         <li>{@link net.jini.core.lookup.ServiceRegistration#addAttributes
*                                                           addAttributes}
*         <li>{@link net.jini.core.lookup.ServiceRegistration#modifyAttributes
*                                                           modifyAttributes}
*         <li>{@link net.jini.core.lookup.ServiceRegistration#setAttributes
*                                                           setAttributes}
*       </ul>
* </table>
*
* <a name="serviceLeasePreparer">
* <table summary="Describes the serviceLeasePreparer configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>serviceLeasePreparer</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Type: <td> {@link net.jini.security.ProxyPreparer}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*       Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
*                     </code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*   Description:
*     <td> Preparer for the leases returned to this utility through
*          the registrations with each discovered lookup service with
*          which this utility has registered the service.
*          <p>
*          Currently, none of the methods on the service lease returned
*          by this preparer are invoked by this implementation of the utility.
* </table>
*
* <a name="taskManager">
* <table summary="Describes the taskManager configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>taskManager</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link com.sun.jini.thread.TaskManager}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>new
*             {@link com.sun.jini.thread.TaskManager#TaskManager()
*                                   TaskManager}(15, (15*1000), 1.0f)</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The object that pools and manages the various threads
*            executed by this utility. The default manager creates a
*            maximum of 15 threads, waits 15 seconds before removing
*            idle threads, and uses a load factor of 1.0 when
*            determining whether to create a new thread. This object
*            should not be shared with other components in the
*            application that employs this utility.
* </table>
*
* <a name="wakeupManager">
* <table summary="Describes the wakeupManager configuration entry"
*                border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>wakeupManager</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> {@link com.sun.jini.thread.WakeupManager}
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>new
*     {@link com.sun.jini.thread.WakeupManager#WakeupManager(
*          com.sun.jini.thread.WakeupManager.ThreadDesc)
*     WakeupManager}(new
*     {@link com.sun.jini.thread.WakeupManager.ThreadDesc}(null,true))</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> Object that pools and manages the various tasks that are
*            initially executed by the object corresponding to the
*            <a href="#taskManager"><code>taskManager</code></a> entry
*            of this component, but which fail during that initial execution.
*            This object schedules the re-execution of such a failed task -
*            in the <a href="#taskManager"><code>taskManager</code></a>
*            object - at various times in the future, until either the
*            task succeeds or the task has been executed the maximum
*            number of allowable times, corresponding to the
*            <a href="#wakeupRetries"><code>wakeupRetries</code></a>
*            entry of this component. This object should not be shared
*            with other components in the application that employs this
*            utility.
* </table>
*
* <a name="wakeupRetries">
* <table summary="Describes the wakeupRetries
*                configuration entry" border="0" cellpadding="2">
*   <tr valign="top">
*     <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
*     <th scope="col" align="left" colspan="2"> <font size="+1">
*     <code>wakeupRetries</code></font>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Type: <td> <code>int</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Default: <td> <code>6</code>
*
*   <tr valign="top"> <td> &nbsp <th scope="row" align="right">
*     Description:
*       <td> The maximum number of times a failed task is allowed to
*            be executed by the object corresponding to the
*            <a href="#wakeupManager"><code>wakeupManager</code></a>
*            entry of this component.
* </table>
*
* <a name="jmLogging">
* <p>
* <b><font size="+1">Logging</font></b>
* <p>
* </a>
*
* This implementation of <code>JoinManager</code> uses the
* {@link Logger} named <code>net.jini.lookup.JoinManager</code>
* to log information at the following logging levels: <p>
*
* <table border="1" cellpadding="5"
*         summary="Describes the information logged by JoinManager,
*                 and the levels at which that information is logged">
*
* <caption halign="center" valign="top">
*   <b><code>net.jini.lookup.JoinManager</code></b>
* </caption>
*
* <tr> <th scope="col"> Level</th>
*      <th scope="col"> Description</th>
* </tr>
*
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>when a task is stopped because of a definite exception</td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>
*     when a task is stopped because it has exceeded the maximum number of
*     times the task is allowed to be tried/re-tried
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#INFO INFO}</td>
*   <td>when any exception occurs while attempting to prepare a proxy</td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINER FINER}</td>
*   <td>
*     when any exception (other than the more serious exceptions logged
*     at higher levels) occurs in a task
*   </td>
* </tr>
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>
*     when an <code>IllegalStateException</code> occurs upon attempting to
*     discard a lookup service
*   </td>
* </tr>
<tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>whenever any task is started</td>
* </tr>
*
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>whenever any task completes successfully</td>
* </tr>
*
* <tr>
*   <td>{@link java.util.logging.Level#FINEST FINEST}</td>
*   <td>whenever a proxy is prepared</td>
* </tr>
* </table>
* <p>
*
* @author Sun Microsystems, Inc.
*
* @see net.jini.discovery.DiscoveryManagement
* @see net.jini.lease.LeaseRenewalManager
* @see java.util.logging.Level
* @see java.util.logging.Logger
*/
public class JoinManager {

    /** Implementation Note:
     *
     *  This class executes a number of tasks asynchronously. Each task is
     *  initially queued in a <code>com.sun.jini.thread.TaskManager</code>
     *  instance, which executes each task in a separate thread. In this
     *  way, an upper bound is placed on the number of threads executing
     *  concurrently at any one time; that is, the number of concurrent
     *  threads is "throttled".
     *
     *  In addition to throttling the number of concurrent threads, the
     *  use of a task manager, in conjunction with the task configuration,
     *  provides a level of resiliency with respect to down/unresponsive
     *  lookup services.
     *
     *  Recall from the specification that the primary function of a join
     *  manager is to maintain and update the state (registrations,
     *  attribute augmentations/replacements/changes, etc.) of the join
     *  manager's single associated service with all of the desired lookup
     *  services. Because updating a service's state in a lookup service
     *  involves remote communication, such update operations are performed
     *  asynchronously, in separate tasks. If those operations are not
     *  performed in separate tasks, then a communication problem with
     *  one of the lookup services while performing one operation could
     *  prevent (or significantly slow) the execution of all future
     *  state update operations; causing all processing in the join manager
     *  to degrade or even hang indefinitely.
     *
     *  Although performing each update operation in a separate task thread
     *  prevents a down/unresponsive lookup service from blocking other
     *  processing in the join manager, it does not guarantee consistency
     *  of the service's state in each "up" lookup service, with the state
     *  expected by the client that requested the state changes.
     *
     *  For example, suppose the client first requests that the service's
     *  attributes be replaced with a new set, and then immediately after
     *  the attribute replacement request, the client requests that the
     *  service's attributes be augmented by yet another set of attributes.
     *  Unless control is imposed on the order of execution of the tasks
     *  that perform the attribute replacement and augmentation, there is
     *  a possibility that the service's state in one or more of the lookup
     *  service's will have experienced the attribute augmentation prior to
     *  the attribute replacement, rather than the replacement followed by
     *  the augmentation, as the client would have expected. Thus, not only
     *  can the service's state in one or more of the lookup services be
     *  inconsistent with what the client expects, but it can also be
     *  inconsistent with one or more of the other lookup services with
     *  which the service is registered.
     * 
     *  To prevent the possibility of inconsistencies such as those just
     *  described, the state update operations are grouped according to
     *  the particular lookup service with which they are associated. Each
     *  such grouping is implemented as a task, executed by the task
     *  manager employed by this implementation of <code>JoinManager</code>,
     *  containing a queue of sub-tasks (the actual state update operations)
     *  that are executed by the main task in the same order in which they
     *  were requested by the client.
     * 
     *  Each main task executed by this join manager's task manager is a
     *  sub-class of the abstract class <code>RetryTask</code>, defined in
     *  the package <code>com.sun.jini.thread</code>, which implements
     *  the <code>com.sun.jini.thread.TaskManager.Task</code> interface.
     *  The association of each such task with a particular lookup service,
     *  and with a unique sequence number is reflected in the fields of this
     *  class. The unique sequence number associated with each main task
     *  is exploited by the <code>runAfter</code> method defined in
     *  <code>RetryTask</code> to guarantee that the service managed by
     *  this <code>JoinManager</code> is assigned only one service ID.
     *  
     *  Because of the relationship between the main tasks and
     *  <code>RetryTask</code>, those concrete main tasks created in this
     *  implementation of <code>JoinManager</code> can be processed by either
     *  a task manager or a wakeup manager; and this fact is exploited to
     *  provide a failure recovery mechanism that can tolerate task "storms".
     *  Task storms can occur in systems that contain numerous services,
     *  where each service employs its own join manager to handle its join
     *  state with the desired lookup services. A "registration request storm"
     *  is an example of one type of task storm.
     *
     *  A registration request storm can occur when a system is initially
     *  started, or when it recovers from a system-wide failure, when each
     *  service's join manager attempts to register with each discovered
     *  lookup service; resulting in a flood - or "storm" - of registration
     *  requests at each lookup service. Because the accept queue under some
     *  operating systems is - by default - very small, it is possible that,
     *  in the face of such a large number of concurrent requests, one or
     *  more of the lookup services will not be able to process the requests
     *  as fast as they are arriving in the queue. When this happens, the
     *  associated task in the join manager typically encounters a failure
     *  such as a <code>java.rmi.ConnectException</code>.
     *
     *  To deal with situations such as that described above, this
     *  <code>JoinManager</code> implementation employs a wakeup manager
     *  in addition to a task manager. When a main task is created, it is
     *  initially executed by a task manager. If a failure is encountered,
     *  and if the nature of that failure indicates that retrying the task
     *  will <i>definitely</i> fail again, then the associated lookup service
     *  is discarded so that the task can be retried when the lookup service
     *  is rediscovered. But if it is not clear that retrying the task will
     *  again fail, (that is, the failure is <i>indefinite</i>), then the
     *  task is queued for re-execution at a later time in a wakeup manager.
     *  This process is repeated - employing a "backoff" strategy - until one
     *  of the following events occurs:
     *
     *  <p><ul>
     *     <li> the task succeeds
     *     <li> a definite failure is encountered
     *     <li> the task has been executed the maximum number times allowed
     *  </ul><p>
    
     *  The maximum number of times a task is allowed to be retried before
     *  giving up and discarding the associated lookup service can be changed
     *  from its default value by setting the <code>wakeupRetries</code>
     *  configuration entry for this component.
     *
     *  @see com.sun.jini.thread.TaskManager
     *  @see com.sun.jini.thread.WakeupManager
     *  @see com.sun.jini.thread.TaskManager.Task
     *  @see com.sun.jini.thread.RetryTask
     *  @see com.sun.jini.constants.ThrowableConstants
     */

    /** Abstract base class from which all of the task classes are derived. */
    private class ProxyRegTask extends RetryTask {
        private final long[] sleepTime = { 5*1000, 10*1000, 15*1000,
                                          20*1000, 25*1000, 30*1000 };
        protected int tryIndx  = 0;
        protected int nRetries = 0;
        protected ProxyReg proxyReg;
        protected int seqN;

        /** Basic constructor; simply stores the input parameters */
        ProxyRegTask(ProxyReg proxyReg, int seqN) {
            super(JoinManager.this.taskMgr,JoinManager.this.wakeupMgr);
            this.proxyReg = proxyReg;
            this.seqN = seqN;
        }//end constructor

        /** Executes the current instance of this task once, queuing it
         *  for retry at a later time and returning <code>false</code>
         *  upon failure. This method attempts to execute all of the tasks
         *  associated with the lookup service referenced in this task's
         *  <code>proxyReg</code> field. Order of execution is important,
         *  and this method executes the tasks in the <code>proxyReg</code>'s
         *  <code>taskList</code> in a FIFO order.
         *
         *  Note that tasks may be added to the <code>taskList</code> of
         *  the <code>proxyReg</code> during the execution of this method.
         *
         *  Upon successfully executing all of the tasks in the
         *  <code>taskList</code>, this method returns <code>true</code>
         *  and the current instance of this task is not executed again.
         *  For each unsuccessful execution of a task in the
         *  <code>taskList</code>, this method returns <code>false</code>,
         *  which causes the task to be scheduled by the
         *  <code>WakeupManager</code> to be executed again at a later
         *  time, as indicated by the value returned by <code>retryTime</code>.
         */
        public boolean tryOnce() {
            while(true) {
                JoinTask t = null;
                synchronized(proxyReg.taskList) {
                    if(proxyReg.taskList.isEmpty()) {
                        proxyReg.proxyRegTask = null;
                        return true;
                    }//endif
                    t = (JoinTask)proxyReg.taskList.get(0);
                }//end sync
                try {
                    t.run();
                    synchronized(proxyReg.taskList) {
                        if( !proxyReg.taskList.isEmpty() ) {
                            proxyReg.taskList.remove(0);
                        }//endif
                    }//end sync
                    /* reset the retry info for the next task in the list */
                    tryIndx  = 0;
                    nRetries = 0;
                } catch (Exception e) {
                    return stopTrying(e);
                }
            }//end loop
  }//end tryOnce

        /** Returns the next absolute time (in milliseconds) at which another
         *  execution of this task should be made (after the previous
         *  attempt has failed).
         */
        public long retryTime() {
      long nextTryTime = System.currentTimeMillis() + sleepTime[tryIndx];
      if(tryIndx < sleepTime.length-1tryIndx++;//don't go past end
            nRetries++;
            return nextTryTime;
        }//end retryTime

        /** Returns true if the current instance of this task must be run
         *  after any task already in the task manager queue.
         * 
         *  It is important that when the join manager is constructed with
         *  a <code>null</code> service ID (where it is desired that
         *  a unique service ID be generated on the service's behalf),
         *  that only the first task in the task manager's queue be run; no
         *  other tasks in the queue should be run while that first task
         *  is running. This is because the first sub-task executed by
         *  the first main task in the task manager's queue will always be
         *  a <code>RegisterTask</code>. And during the execution of that
         *  first sub-task (if the service ID has not yet been set), the
         *  service ID generated by the associated lookup service is retrieved
         *  and stored for use in all future lookup service registration
         *  tasks, Once the service ID is set by that first registration
         *  sub-task, all future main tasks (and their associated registration
         *  sub-tasks) can be run in parallel; each using the same service ID.
         *  If this is not done, then the registration sub-tasks would be
         *  run in parallel, each assigning a different ID to the service.
         *
         *  This method guarantees that until the service's ID is set,
         *  only one registration sub-task is run; that is, one task
         *  doesn't start until the currently running task has completed,
         *  and a non-<code>null</code> service ID is assigned to the service.
         * 
         *  Executing the main tasks sequentially until the service ID is
         *  retrieved and stored must also be guaranteed because the currently
         *  running registration task may fail to register the service
         *  (because of a <code>RemoteException</code>), and thus may fail
         *  to obtain an ID for the service. This method guarantees then
         *  that each main task (and thus, each registration sub-task) will
         *  run in sequence until one of those tasks completes successfully;
         *  and from that point on, this method guarantees that all other
         *  queued tasks will run in parallel.
         * 
         *  @param tasks the tasks with which to compare the current task
         *  @param size  elements with index less than size are considered
         */
        public boolean runAfter(List tasks, int size) {
            /* If the service's ID has already been set, then it's okay
             * to run all ProxyRegTask's in parallel, otherwise, the
             * ProxyRegTask with the lowest sequence number should be run.
             */
            synchronized(serviceItem) {//accessing serviceItem.serviceID
                if(serviceItem.serviceID != nullreturn false;
                /* For task with lowest seq #, run it now; else run it later */
                for(int i=0; i<size; i++) {
                    TaskManager.Task t = (TaskManager.Task)tasks.get(i);
                    int nextTaskSeqN = ((ProxyRegTask)t).getSeqN();
                    if( seqN > nextTaskSeqN return true;
                }//end loop
                return false;
      }//end sync(serviceItem)
        }//end runAfter

        /** Accessor method that returns the instance of <code>ProxyReg</code>
         *  (the lookup service) associated with the task represented by
         *  the current instance of this class.
         */
        public ProxyReg getProxyReg() {
            return proxyReg;
        }//end getProxy

        /** Accessor method that returns the unique sequence number associated
         *  with the task represented by the current instance of this class.
         */
        public int getSeqN() {
            return seqN;
        }//end getSeqN

        /** Convenience method called by the child tasks when they encounter
         *  an exception. If the given exception indicates that retrying the
         *  task would definitely fail, or if the maximum allowable number
         *  of retries of the task has been exceeded, then this method will
         *  do the following:
         *    - remove all pending tasks that are to be run after this task
         *    - cancel this task
         *    - discard the look service associated with this task
         *    - return <code>true</code> (which stops the wakeup manager
         *      from retrying this task
         *  otherwise, this method returns <code>false</code>, which indicates
         *  that the wakeup manager should not stop trying to successfully
         *  execute the task.
         */
        protected boolean stopTrying(Exception e) {
            int exCat = ThrowableConstants.retryable(e);
            if(    (exCat != ThrowableConstants.INDEFINITE)
                || (nRetries >= maxNRetries) )
            {
                synchronized(joinSet) {
                    removeTasks(proxyReg);//cancel and clear all related tasks
                }//end sync(joinSet)
                proxyReg.fail(e);
                return true;//don't try again
            }//endif
            logger.log(Level.FINER,
                       "JoinManager - failure, will retry later", e);
            return false;//try this task again later
        }//end stopTrying
    }//end class ProxyRegTask

    /** Abstract base class from which all the sub-task classes are derived. */
    private abstract class JoinTask {

        /** Data structure referencing the task's associated lookup service */
        protected ProxyReg proxyReg;

        /** Basic constructor; simply stores the input parameters */
        JoinTask(ProxyReg proxyReg) {
            this.proxyReg = proxyReg;
        }//end constructor

        /** Method executed in a separate thread created by the task manager */
  public abstract void run() throws Exception;

    }//end class JoinTask

    /** Task that asynchronously registers the service associated with this
     *  join manager with the lookup service referenced by the current
     *  instance of this class.
     */
    private class RegisterTask extends JoinTask {
        /** Attributes with which to register the service. These attributes
         *  must not change during the registration process performed in
         *  this this task.
         */
        Entry[] regAttrs;

        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the lookup service
         *                   with which the service is to be registered
         *  @param regAttrs  array of Entry objects whose elements are the
         *                   attributes with which to register the service.
         *                   The caller of this constructor should take steps
         *                   to guarantee that the contents of this parameter
         *                   do not change during the registration process
         *                   performed in this task.
         */
        RegisterTask(ProxyReg proxyReg, Entry[] regAttrs) {
            super(proxyReg);
            this.regAttrs = regAttrs;
  }//end constructor

        /** Attempts to register this join manager's service with the lookup
         *  service referenced in this task's proxyReg field.
         */
        public void run() throws Exception {
            logger.finest("JoinManager - RegisterTask started");
            proxyReg.register(regAttrs);
            logger.finest("JoinManager - RegisterTask completed");
        }//end run

    }//end class RegisterTask

    /** Task that asynchronously re-registers the service associated with this
     *  join manager with the lookup service referenced by the current
     *  instance of this class.
     * 
     *  This task is typically executed when the service's lease with the
     *  referenced lookup service has expired.
     */
    private class LeaseExpireNotifyTask extends JoinTask {
        /** Attributes with which to re-register the service. These attributes
         *  must not change during the registration process performed in
         *  this this task.
         */
        Entry[] regAttrs;
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the lookup service
         *                   with which the service is to be re-registered
         *  @param regAttrs  array of Entry objects whose elements are the
         *                   attributes with which to re-register the service.
         *                   The caller of this constructor should take steps
         *                   to guarantee that the contents of this parameter
         *                   do not change during the registration process
         *                   performed in this task.
         */
        LeaseExpireNotifyTask(ProxyReg proxyReg, Entry[] regAttrs) {
            super(proxyReg);
            this.regAttrs = regAttrs;
  }//end constructor

        /** Attempts to re-register this join manager's service with the
         *  lookup service referenced by the current instance of this class.
         */
        public void run() throws Exception {
            logger.finest("JoinManager - LeaseExpireNotifyTask started");
            boolean tryIt = false;
            synchronized(joinSet) {
                tryIt = joinSet.contains(proxyReg);
            }//end sync(joinSet)
            if(tryItproxyReg.register(regAttrs);
            logger.finest("JoinManager - LeaseExpireNotifyTask completed");
  }//end run

    }//end class LeaseExpireNotifyTask

    /** Task that asynchronously requests the cancellation of the lease
     *  on the <code>ServiceRegistration</code> referenced by the given
     *  <code>ProxyReg</code> object. The lease to be cancelled was granted
     *  by the lookup service whose proxy is also referenced by the given
     *  <code>ProxyReg</code> object. This task is executed whenever that
     *  lookup service is discarded.
     *
     *  Note that although this task is executed upon receipt of any type
     *  of lookup service discard event, there is one type of discard
     *  that this task is actually intended to address: the so-called
     *  "lost-interest" discard. A discard corresponding to a loss of
     *  interest is an indication that the discarded lookup service is
     *  still up and available, but the discovery manager employed by
     *  this join manager (not the join manager or the service itself)
     *  discards the lookup service because the service is no longer
     *  interested in being registered with that lookup service. This
     *  loss of interest is caused by a change in either the lookup
     *  service's member groups or the service's groups-to-discover.
     *  Such a change in groups would typically be made administratively,
     *  through either the lookup service's administrative interface or
     *  through the service's administrative interface (or both).
     *
     *  When the lookup service is discarded because of a loss of
     *  interest, there is a time period in which the service is still
     *  registered with the lookup service, even though the service no
     *  longer wishes to be registered with that lookup service. To
     *  address this, the lease is cancelled so that the service is
     *  removed from the lookup service in a much more timely fashion
     *  than simply allowing the lease to expire.
     *
     *  As stated above, this task is also executed upon receipt of
     *  the other types of discard event. But because those other
     *  discard types occur when the lookup service is no longer
     *  available, the lease cancellation that is attempted here has
     *  no significant effect.
     *
     *  @see DiscMgrListener#discarded
     */
    private class DiscardProxyTask extends JoinTask {
        /** Constructor that provides this task with access (through the given
         *  <code>ProxyReg</code> parameter) to the lease to be cancelled.
         *
         *  @param proxyReg  data structure that references the
         *                   <code>ServiceRegistration</code> and associated
         *                   lease whose cancellation is to be requested
         */
        DiscardProxyTask(ProxyReg proxyReg) {
            super(proxyReg);
  }//end constructor

        /** Requests the cancellation of the lease on the
         *  <code>ServiceRegistration</code> that is referenced in the
         *  <code>proxyReg</code> data structure.
         */
        public void run() {
            logger.finest("JoinManager --> DiscardProxyTask started");
            if( (proxyReg != null) && (proxyReg.serviceLease != null) ) {
                try {
                    proxyReg.serviceLease.cancel();
                } catch (Exception e) { /*ignore*/ }
            }//endif
            logger.finest("JoinManager - DiscardProxyTask completed");
  }//end run

    }//end class DiscardProxyTask

    /** Task that asynchronously augments the attributes associated with this
     *  join manager's service in the lookup service referenced by the
     *  current instance of this class.
     */
    private class AddAttributesTask extends JoinTask {
        /** The new attribute values with which the service's current
         *  attributes will be augmented, replaced, or changed.
         */
  protected Entry[] attrSets;

        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg  data structure that references the lookup service
         *                   in which the service's attributes should be
         *                   augmented
         *  @param newAttrs  the attributes with which to augment the
         *                   service's current set of attributes
         */
        AddAttributesTask(ProxyReg proxyReg, Entry[] newAttrs) {
            super(proxyReg);
      this.attrSets = (Entry[])newAttrs.clone();
  }//end constructor

        /** Performs the actual attribute augmentation, replacement, or
         *  modification work. This method is typically overridden by
         *  sub-classes of this class.
         */
        protected void doAttributes(ProxyReg proxyReg) throws Exception {
            logger.finest("JoinManager - AddAttributesTask started");
      proxyReg.addAttributes(attrSets);
            logger.finest("JoinManager - AddAttributesTask completed");
        }//end AddAttributesTask.doAttributes

        /** Attempts to either augment, replace, or modify the attributes
         *  of this join manager's service in the lookup service referenced
         *  by the current instance of this class. Which action is taken --
         *  augmentation, replacement, or modification -- is dependent on the
         *  definition of the <code>doAttributes/code> method.
         */
        public void run() throws Exception {
            doAttributes(proxyReg);
  }//end run

    }//end class AddAttributesTask

    /** Task that asynchronously replaces the attributes associated with this
     *  join manager's service in the lookup service referenced by the
     *  current instance of this class.
     */
    private final class SetAttributesTask extends AddAttributesTask {
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg         data structure that references the lookup
         *                          service in which the service's attributes
         *                          should be replaced
         *  @param replacementAttrs the attributes with which to replace the
         *                          service's current set of attributes
         */
        SetAttributesTask(ProxyReg proxyReg, Entry[] replacementAttrs){
            super(proxyReg, replacementAttrs);
  }//end constructor

        /** Performs the actual attribute replacement work. */
        protected void doAttributes(ProxyReg proxyReg) throws Exception {
            logger.finest("JoinManager - SetAttributesTask started");
      proxyReg.setAttributes(attrSets);
            logger.finest("JoinManager - SetAttributesTask completed");
  }//end SetAttributesTask.doAttributes

    }//end class SetAttributesTask

    /** Task that asynchronously modifies the attributes associated with this
     *  join manager's service in the lookup service referenced by the
     *  current instance of this class.
     */
    private final class ModifyAttributesTask extends AddAttributesTask {
  private Entry[] attrSetTemplates;
        /** Constructor that associates this task with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
         *  @param proxyReg         data structure that references the lookup
         *                          service in which the service's attributes
         *                          should be changed
         *  @param attrSetTemplates the attribute templates that are used to
         *                          select (through attribute matching) the
         *                          attributes to change in the service's
         *                          current set of attributes
         *  @param attrChanges      the attributes containing the changes to
         *                          make to the attributes in the service's
         *                          current set that are selected through
         *                          attribute matching with the
         *                          attrSetTemplates parameter
         */
        ModifyAttributesTask( ProxyReg proxyReg,
                              Entry[] attrSetTemplates,
                              Entry[] attrChanges )
        {
            super(proxyReg, attrChanges);
      this.attrSetTemplates = (Entry[])attrSetTemplates.clone();
  }//end constructor

        /** Performs the actual attribute modification work. */
        protected void doAttributes(ProxyReg proxyReg) throws Exception {
            logger.finest("JoinManager - ModifyAttributesTask started");
      proxyReg.modifyAttributes(attrSetTemplates, attrSets);
            logger.finest("JoinManager - ModifyAttributesTask completed");
  }//end ModifyAttributesTask.doAttributes

    }//end class ModifyAttributesTask

    /** Wrapper class in which each instance corresponds to a lookup
     *  service to discover, and with which this join manager's service
     *  should be registered.
     */
    private class ProxyReg {
        /** Class that is registered as a listener with this join manager's
         *  lease renewal manager. That lease renewal manager manages the
         *  lease granted to this join manager's associated service by the
         *  lookup service referenced in the proxy object associated with
         *  this class (<code>ProxyReg</code>).
         *
         *  If the lease expires in the lookup service before the lease
         *  renewal manager requests renewal of the lease, then upon sending
         *  that renewal request, the lease renewal manager will receive an
         *  <code>UnknownLeaseException</code> from the lookup service.
         *  As a result, the lease renewal manager removes the expired lease
         *  so that no further renewal attempts are made for that lease,
         *  and then sends to this listener a <code>LeaseRenewalEvent</code>,
         *  containing an <code>UnknownLeaseException</code>.
         *
         *  Alternatively, suppose that at the time the lease renewal manager
         *  is about to request the renewal of the lease, the lease renewal
         *  manager determines that the expiration time of the lease has
         *  actually passed. In this case, since there is no reason to
         *  send the renewal request, the lease renewal manager instead
         *  removes the expired lease (again, so that no further renewal
         *  attempts are made for that lease), and then sends a
         *  <code>LeaseRenewalEvent</code> to this listener to indicate
         *  that the lease has expired. The difference between this case,
         *  and the case described previously is that in this case the
         *  <code>LeaseRenewalEvent</code> contains no exception (that is,
         *  <code>LeaseRenewalEvent.getException</code> returns
         *  <code>null</code>).
         *
         *  Both situations described above indicate that the lease
         *  referenced by the event received by this listener has expired.
         *  Thus, the normal course of action should be to attempt to
         *  re-register the service with the lookup service that originally
         *  granted the lease that has expired.
         *
         *  Prior to re-registering the service, the joinSet is examined to
         *  determine if it contains a ProxyReg object that is equivalent to
         *  the ProxyReg object referencing the current instance of this
         *  listener class. That is, using <code>equals</code>, it is
         *  determined whether the joinSet contains either ProxyReg.this,
         *  or a new instance of ProxyReg that is equal to ProxyReg.this.
         *  If the joinSet does not contain such a ProxyReg, then the lookup
         *  service must have been discarded and not yet re-discovered; in
         *  which case, there is no need to attempt to re-register with that
         *  lookup service, since it is unavailable.
         *
         *  If it is determined that the joinSet does contain either
         *  ProxyReg.this or a new ProxyReg equivalent to ProxyReg.this,
         *  then re-registration should be attempted, but only if the lease
         *  associated with the ProxyReg in the joinSet is equal to the
         *  expired lease referenced in the event received by this listener.
         *  Equality of those leases is an indication that the lease on the
         *  service's current registration has expired; thus, an attempt to
         *  re-register the service should be made.
         *
         *  If the lease associated with the ProxyReg from the joinSet
         *  does not equal the expired lease from the event, then
         *  re-registration should not be attempted. This is because
         *  the inequality of the leases is an indication that the lease
         *  renewal event received by this listener was the result of an
         *  <code>UnknownLeaseException</code> that occurs when the
         *  ProxyReg in the joinSet is a new ProxyReg, different from
         *  ProxyReg.this, and the lease renewal manager requests the
         *  renewal of the (now invalid) lease associated with that old
         *  ProxyReg.this; not the valid lease associated with the new
         *  ProxyReg.
         *
         *  A scenario such as that just described can occur when the
         *  lookup service is discarded, rediscovered, and the service is
         *  re-registered, resulting in a new ProxyReg (with new lease)
         *  being placed in the joinSet, replacing the previous ProxyReg
         *  (ProxyReg.this). But before the old, expired lease is removed
         *  from the lease renewal manager, an attempt to renew the old
         *  lease is made. Such an attempt can occur because the lease
         *  renewal manager may be in the process of requesting the renewal
         *  of that lease (or may have queued such a request) just prior to,
         *  or at the same time as, when the lease removal request is made
         *  during discard processing. When the request is made to renew
         *  the expired lease, an <code>UnknownLeaseException</code> occurs
         *  and a lease renewal event is sent to this listener.
         *
         *  If, upon receiving an event such as that just described, the
         *  service were to be re-registered, the current valid service
         *  registration would be replaced, a new lease would be granted,
         *  and the corresponding ProxyReg currently contained in the joinSet
         *  would be replaced with a new ProxyReg. Additionally, the now
         *  invalid lease corresponding to the ProxyReg that was just
         *  replaced would remain in the lease renewal manager. This means
         *  that an attempt to renew that lease will eventually be made and
         *  will fail, and the cycle just described will repeat indefinitely.
         *
         *  Thus, for the reasons stated above, re-registration is attempted
         *  only if the lease associated with the ProxyReg contained in the
         *  joinSet is equal to the expired lease referenced in the lease
         *  renewal event received by this listener.
         */
  private class DiscLeaseListener implements LeaseListener {
        public void notify(LeaseRenewalEvent e) {
                Throwable ex = e.getException();
    if ( (ex == null) || (ex instanceof UnknownLeaseException) ) {
                    synchronized(joinSet) {
                        removeTasks(ProxyReg.this);
                        Lease expiredLease = e.getLease();
                        // Maybe re-register
                        int indx = joinSet.indexOf(ProxyReg.this);
                        if(indx >= 0) {//new proxyReg/old ProxyReg.this in set
                            ProxyReg curProxyReg = (ProxyReg)joinSet.get(indx);
                            if(expiredLease.equals(curProxyReg.serviceLease)) {
                                // Okay to re-register
                                addTask(new LeaseExpireNotifyTask
                                                (ProxyReg.this,
                                                 (Entry[])lookupAttr.clone()));
                            }//endif
                        }//endif
                    }//end sync(joinSet)
    } else {
        fail(ex);
                }//endif
      }//end notify
  }//end class DiscLeaseListener

        /** The <code>ProxyRegTask</code> that instantiated this
         *  <code>ProxyReg</code>.
         */
        public ProxyRegTask proxyRegTask;
        /** The <i>prepared</i> proxy to the lookup service referenced by
         *  this class, and with which this join manager's service will be
         *  registered.
         */
  public ServiceRegistrar proxy;
        /** The <i>prepared</i> registration proxy returned by this class'
         *  associated lookup service when this join manager registers its
         *  associated service.
         */
  public ServiceRegistration srvcRegistration = null;
        /* The <i>prepared</i> proxy to the lease on the registration of this
         * join manager's service with the this class' associated lookup
         * service.
         */
  public Lease serviceLease = null;
        /** The set of sub-tasks that are to be executed in order for the
         *  lookup service associated with the current instance of this class.
         */
        public List taskList = new ArrayList(1);
        /** The instance of <code>DiscLeaseListener</code> that is registered
         *  with the lease renewal manager that handles the lease of this join
         *  manger's service.
         */
  private DiscLeaseListener dListener = new DiscLeaseListener();

        /** Constructor that associates this class with the lookup service
         *  referenced in the given <code>ProxyReg</code> parameter.
         *
   *  @param proxy data structure that references the lookup service on
   *               which the sub-tasks referenced in this class will be
   *               executed in order
         */
  public ProxyReg(ServiceRegistrar proxy) {
      if(proxy == nullthrow new IllegalArgumentException
                                                      ("proxy can't be null");
      this.proxy = proxy;
  }//end constructor     

        /** Convenience method that adds new sub-tasks to this class'
         *  task queue.
         *
         *  @param task the task to add to the task queue
         */
        public void addTask(JoinTask task) {
            synchronized(JoinManager.this) {
                if(bTerminated) return;
            }//end sync
            synchronized(taskList) {
                taskList.add(task);
                if(this.proxyRegTask == null) {
                    this.proxyRegTask = new ProxyRegTask(this,taskSeqN++);
                    synchronized (taskMgr) {
                        taskMgr.add(this.proxyRegTask);
                    }//end sync(taskMgr)
                }//endif
            }//end sync(taskList)
        }//end addTask

        /** Registers the service associated with this join manager with the
         *  the lookup service corresponding to this class. Additionally,
         *  this method retrieves the lease granted by the lookup service
         *  on the service's registration, and passes that lease to the
         *  lease renewal manager. If a <code>ServiceIDListener</code>
         *  has been registered with this join manager, this method will
         *  send to that listener a notification containing the service's ID.
         */
        public void register(Entry[] srvcAttrs) throws Exception {
            if(proxy == null) throw new RuntimeException("proxy is null");
            /* The lookup service proxy was already prepared at discovery */
            ServiceItem tmpSrvcItem = null;
            synchronized(joinSet) {
                srvcRegistration = null;
                synchronized(serviceItem) {//accessing serviceItem.serviceID
                    tmpSrvcItem = new ServiceItem(serviceItem.serviceID,
                                                  serviceItem.service,
                                                  srvcAttrs);
                }//end sync(serviceItem)
            }//end sync(joinSet)
            /* Retrieve and prepare the proxy to the service registration */
            ServiceRegistration tmpSrvcRegistration
                                = proxy.register(tmpSrvcItem, renewalDuration);
            try {
                tmpSrvcRegistration =
                   (ServiceRegistration)registrationPreparer.prepareProxy
                                                       ( tmpSrvcRegistration );
                logger.finest
                          ("JoinManager - ServiceRegistration proxy prepared");
            } catch(Exception e) {
    LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
          "register", "JoinManager - failure during " +
          "preparation of ServiceRegistration proxy: {0}",
          new Object[] { tmpSrvcRegistration }, e);
                throw e; //rethrow the exception since proxy may be unusable
            }
            /* Retrieve and prepare the proxy to the service lease */
            serviceLease = tmpSrvcRegistration.getLease();
            try {
                serviceLease =
                       (Lease)serviceLeasePreparer.prepareProxy(serviceLease);
                logger.finest("JoinManager - service lease proxy prepared");
            } catch(Exception e) {
    LogUtil.logThrow(logger, Level.WARNING, ProxyReg.class,
          "register", "JoinManager - failure during " +
          "preparation of service lease proxy: {0}",
          new Object[] { serviceLease }, e);
                throw e; //rethrow the exception since proxy may be unusable
            }
            leaseRenewalMgr.renewUntil(serviceLease, Lease.FOREVER,
                                       renewalDuration, dListener);
            ServiceID tmpID = null;
            synchronized(joinSet) {
                srvcRegistration = tmpSrvcRegistration;
                synchronized(serviceItem) {//accessing serviceItem.serviceID
                    if(serviceItem.serviceID == null) {
                        serviceItem.serviceID
                                            = srvcRegistration.getServiceID();
                        tmpID = serviceItem.serviceID;
                    }//endif
                }//end sync(serviceItem)
            }//end sync(joinSet)
            if( (tmpID != null) && (callback != null) )  {
                callback.serviceIDNotify(tmpID);
            }//endif
        }//end ProxyReg.register

        /** With respect to the lookup service referenced in this class
         *  and with which this join manager's service is registered, this
         *  method associates with that service a new set of attributes -- in
         *  addition to that service's current set of attributes.
         */
        public void addAttributes(Entry[] attSet) throws Exception {
            srvcRegistration.addAttributes(attSet);
  }//end ProxyReg.addAttributes

        /** With respect to the lookup service referenced in this class
         *  and with which this join manager's service is registered, this
         *  method changes that service's current attributes by selecting
         *  the attributes to change using the given first parameter;
         *  and identifying the desired changes to make through the
         *  contents of the second parameter.
         */
        public void modifyAttributes(Entry[] templ, Entry[] attSet)
                                                             throws Exception
        {
            srvcRegistration.modifyAttributes(templ, attSet);
  }//end ProxyReg.modifyAttributes       

        /** With respect to the lookup service referenced in this class
         *  and with which this join manager's service is registered, this
         *  method replaces that service's current attributes with a new
         *  set of attributes.
         */
        public void setAttributes(Entry[] attSet) throws Exception {
            srvcRegistration.setAttributes(attSet);
  }//end ProxyReg.setAttributes

        /** Convenience method that encapsulates appropriate behavior when
         *  failure is encountered related to the current instance of this
         *  class. This method discards the lookup service proxy associated
         *  with this object, and logs the stack trace of the given
         *  <code>Throwable</code> according to the logging levels specified
         *  for this utility.
         * 
         *  Note that if the discovery manager employed by this join manager
         *  has been terminated, then the attempt to discard the lookup
         *  service proxy will result in an <code>IllegalStateException</code>.
         *  Since this method is called only within the tasks run by
         *  this join manager, and since propagating an
         *  <code>IllegalStateException</code> out into the
         *  <code>ThreadGroup</code> of those tasks is undesirable, this
         *  method will not propagate the <code>IllegalStateException</code>
         *  that occurs as a result of an attempt to discard a lookup
         *  service proxy from the discovery manager employed by this
         *  join manager.
         *
         * For more information, refer to Bug 4490355.
         */
  public void fail(Throwable e) {
      synchronized(this) {
    if(bTerminated) {
        return;
    } else {
        LogUtil.logThrow(logger, Level.INFO, ProxyReg.class, "fail",
      "JoinManager - failure for lookup service proxy: {0}",
      new Object[] { proxy }, e);
        try {
      discMgr.discard(proxy);
        } catch(IllegalStateException e1) {
           logger.log(Level.FINEST,
          "JoinManager - cannot discard lookup, "
          +"discovery manager already terminated",
          e1);
        }
    }//endif
      }//end sync(this)
  }//end ProxyReg.fail

  /** Returns true if the both objects' associated proxies are equal. */
  public boolean equals(Object obj) {
      if (obj instanceof ProxyReg) {
    return proxy.equals( ((ProxyReg)obj).proxy );
      } else {
                return false;
            }//endif
  }//end ProxyReg.equals

  /** Returns the hash code of the proxy referenced in this class. */
  public int hashCode() {
      return proxy.hashCode();
  }//end hashCode

    }//end class ProxyReg

    /* Listener class for discovery/discard notification of lookup services. */
    private class DiscMgrListener implements DiscoveryListener {
  /* Invoked when new or previously discarded lookup is discovered. */
  public void discovered(DiscoveryEvent e) {
      synchronized(joinSet) {
    ServiceRegistrar[] proxys
               = (ServiceRegistrar[])e.getRegistrars();
    for(int i=0;i<proxys.length;i++) {
        /* Prepare the proxy to the discovered lookup service
           * before interacting with it.
           */
        try {
      proxys[i]
        = (ServiceRegistrar)registrarPreparer.prepareProxy
                   (proxys[i]);
      logger.log(Level.FINEST, "JoinManager - discovered "
           +"lookup service proxy prepared: {0}",
           proxys[i]);
        } catch(Exception e1) {
      LogUtil.logThrow(logger, Level.INFO,
          DiscMgrListener.class, "discovered", "failure "
          + "preparing discovered ServiceRegistrar proxy: "
          + "{0}", new Object[] { proxys[i] }, e1);
      discMgr.discard(proxys[i]);
      continue;
        }
        /* If the serviceItem is a lookup service, don't need to
                     * register it with itself since a well-defined lookup
                     * service will always register with itself.
                     */
        if( !proxys[i].equals(serviceItem.service) ) {
      ProxyReg proxyReg = new ProxyReg(proxys[i]);
      if( !joinSet.contains(proxyReg) ) {
          joinSet.add(proxyReg);
          proxyReg.addTask(new RegisterTask
            (proxyReg,
             (Entry[])lookupAttr.clone()));
      }//endif
        }//endif
    }//end loop
      }//end sync(joinSet)
  }//end discovered

  /* Invoked when previously discovered lookup is discarded. */
  public void discarded(DiscoveryEvent e) {
            synchronized(joinSet) {
                ServiceRegistrar[] proxys
                                      = (ServiceRegistrar[])e.getRegistrars();
                for(int i=0;i<proxys.length;i++) {
                    ProxyReg proxyReg = findReg(proxys[i]);
        if(proxyReg != null) {
                        removeTasks(proxyReg);
                        joinSet.remove(proxyReg);
                        try {
                            leaseRenewalMgr.remove( proxyReg.serviceLease );
                        } catch(UnknownLeaseException ex) { /*ignore*/ }
                        proxyReg.addTask(new DiscardProxyTask(proxyReg));
        }//endif
                }//end loop
            }//end sync(joinSet)
  }//end discarded
    }//end class DiscMgrListener

    /* Name of this component; used in config entry retrieval and the logger.*/
    private static final String COMPONENT_NAME = "net.jini.lookup.JoinManager";
    /* Logger used by this utility. */
    private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
    /** Maximum number of concurrent tasks that can be run in any default
     * task manager created by this class.
     */
    private static final int MAX_N_TASKS = 15;
    /** Whenever a task is created in this join manager, it is assigned a
     *  unique sequence number so that the task is not run prior to the
     *  execution, and completion of, any other tasks with which that task
     *  is associated (tasks are grouped by the <code>ProxyReg</code> with
     *  which each task is associated). This field contains the value of
     *  the sequence number assigned to the most recently created task.
     */
    private int taskSeqN = 0;
    /** Task manager for the various tasks executed by this join manager.
     *  On the first attempt to execute any task is managed by this
     *  <code>TaskManager</code> so that the number of concurrent threads
     *  can be bounded. If one or more of those attempts fails, a
     *  <code>WakeupManager</code> is used (through the use of a
     *  <code>RetryTask</code>) to schedule - at a later time (employing a
     *  "backoff strategy") - the re-execution of each failed task in this
     *  <code>TaskManager</code>.
     */
    private TaskManager taskMgr;
    /** Maximum number of times a failed task is allowed to be re-executed. */
    private int maxNRetries = 6;
    /** Wakeup manager for the various tasks executed by this join manager.
     *  After an initial failure of any task executed by this join manager,
     *  the failed task is managed by this <code>WakeupManager</code>; which
     *  schedules the re-execution of the failed task - in the task manager -
     *  at various times in the future until either the task succeeds or the
     *  task has been executed the maximum number of allowable times. This
     *  wakeup manager is supplied to the <code>RetryTask</code>) that
     *  performs the actual task execution so that when termination of this
     *  join manager is requested, all tasks scheduled for retry by this
     *  wakeup manager can be cancelled.
     */
    private WakeupManager wakeupMgr;
    /** Contains the reference to the service that is to be registered with
     *  all of the desired lookup services referenced by <code>discMgr</code>.
     */
    private ServiceItem serviceItem;
    /** Contains the attributes with which to associate the service in each
     *  of the lookup services with which this join manager registers the
     *  service.
     */
    private Entry[] lookupAttr = null;
    /** Contains the listener -- instantiated by the entity that constructs
     *  this join manager -- that will receive an event containing the
     *  service ID assigned to this join manager's service by one of the
     *  lookup services with which that service is registered.
     */
    private ServiceIDListener callback;
    /** Contains elements of type <code>ProxyReg</code> where each element
     *  references a proxy to one of the lookup services with which this
     *  join manager's service is registered.
     */
    private final ArrayList joinSet = new ArrayList(1);
    /** Contains the discovery manager that discovers the lookup services
     *  with which this join manager will register its associated service.
     */
    private DiscoveryManagement discMgr = null;
    /** Contains the discovery listener registered by this join manager with
     *  the discovery manager so that this join manager is notified whenever
     *  one of the desired lookup services is discovered or discarded.
     */
    private DiscMgrListener discMgrListener = new DiscMgrListener();
    /** Flag that indicate whether the discovery manager employed by this
     *  join manager was created by this join manager itself, or by the
     *  entity that constructed this join manager.
     */
    private boolean bCreateDiscMgr = false;
    /** Contains the lease renewal manager that renews all of the leases
     *  this join manager's service holds with each lookup service with which
     *  it has been registered.
     */
    private LeaseRenewalManager leaseRenewalMgr = null;
    /** The value to use as the <code>renewDuration</code> parameter
     *  when invoking the lease renewal manager's <code>renewUntil</code>
     *  method to add a service lease to manage. This value represents,
     *  effectively, the time interval (in milliseconds) over which each
     *  managed lease must be renewed. As this value is made smaller,
     *  renewal requests will be made more frequently while the service
     *  is up, and lease expirations will occur sooner when the service
     *  goes down.
     */
    private long renewalDuration = Lease.FOREVER;
    /** Flag that indicates if this join manager has been terminated. */
    private boolean bTerminated = false;
    /* Preparer for the proxies to the lookup services that are discovered
     * and used by this utility.
     */
    private ProxyPreparer registrarPreparer;
    /* Preparer for the proxies to the registrations returned to this utility
     * upon registering the service with each discovered lookup service.
     */
    private ProxyPreparer registrationPreparer;
    /* Preparer for the proxies to the leases returned to this utility through
     * the registrations with each discovered lookup service with which this
     * utility has registered the service.
     */
    private ProxyPreparer serviceLeasePreparer;

    /**
     * Constructs an instance of this class that will register the given
     * service reference with all discovered lookup services and, through
     * an event sent to the given <code>ServiceIDListener</code> object,
     * communicate the service ID assigned by the first lookup service
     * with which the service is registered. This constructor is typically
     * used by services which have not yet been assigned a service ID.
     * <p>
     * The value input to the <code>serviceProxy</code> parameter represents
     * the service reference (proxy) to register with each discovered lookup
     * service. If the <code>Object</code> input to that parameter is not
     * <code>Serializable</code>, an <code>IllegalArgumentException</code>
     * is thrown. If <code>null</code> is input to that parameter, a
     * <code>NullPointerException</code> is thrown.
     * <p>
     * The value input to the <code>attrSets</code> parameter is an array
     * of <code>Entry</code> objects, none of whose elements may be
     * <code>null</code>, that represents the new set of attributes to
     * associate with the new service reference to be registered. Passing
     * <code>null</code> as the value of the <code>attrSets</code> parameter
     * is equivalent to passing an empty array. If any of the elements
     * of the <code>attrSets</code> array are <code>null</code>, a
     * <code>NullPointerException</code> is thrown. The set of attributes
     * passed in this parameter will be associated with the service in all
     * future join processing until those attributes are changed through
     * an invocation of a method on this class such as,
     * <code>addAttributes</code>, <code>setAttributes</code>,
     * <code>modifyAttributes</code>, or <code>replaceRegistration</code>.
     * <p>
     * When constructing this utility, the service supplies an object through
     * which notifications that indicate a lookup service has been discovered
     * or discarded will be received. At a minimum, the object supplied
     * (through the <code>discoveryMgr</code> parameter) must satisfy the
     * contract defined in the <code>DiscoveryManagement</code> interface.
     * That is, the object supplied must provide this utility with the ability
     * to set discovery listeners and to discard previously discovered
     * lookup services when they are found to be unavailable. A value of
     * <code>null</code> may be input to the <code>discoveryMgr</code>
     * parameter. When <code>null</code> is input to that parameter, an
     * instance of <code>LookupDiscoveryManager</code> is used to listen
     * for events announcing the discovery of only those lookup services
     * that are members of the public group.
     * <p>
     * The object input to the <code>leaseMgr</code> parameter provides for
     * the coordination, systematic renewal, and overall management of all
     * leases on the given service reference's residency in the lookup
     * services that have been joined. As with the <code>discoveryMgr</code>
     * parameter, a value of <code>null</code> may be input to this
     * parameter. When <code>null</code> is input to this parameter,
     * an instance of <code>LeaseRenewalManager</code>, initially managing
     * no <code>Lease</code> objects will be used. This feature allows a
     * service to either use a single entity to manage all of its leases,
     * or to use separate entities: one to manage the leases unrelated to
     * the join process, and one to manage the leases that result from the
     * join process, that are accessible only within the current instance
     * of the <code>JoinManager</code>.
     *
     * @param serviceProxy the service reference (proxy) to register with all
     *                     discovered lookup services
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service
     * @param callback     reference to the object that should receive the
     *                     event containing the service ID, assigned to the
     *                     service by the first lookup service with which the
     *                     service reference is registered
     * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
     *                     object this class should use to manage lookup
     *                     service discovery on behalf of the given service
     * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         <code>serviceProxy</code> parameter is not serializable
     * @throws java.lang.NullPointerException if either <code>null</code> is
     *         input to the <code>serviceProxy</code> parameter, or at least
     *         one of the elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     * @throws java.io.IOException if initiation of discovery process results
     *         in <code>IOException</code> when socket allocation occurs
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated <code>JoinManager</code> instance. Note that this
     *         exception is implementation-specific.
     *
     * @see net.jini.lookup.ServiceIDListener
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     */
     public JoinManager(Object serviceProxy,
                        Entry[] attrSets,
      ServiceIDListener callback,
      DiscoveryManagement discoveryMgr,
      LeaseRenewalManager leaseMgr)    throws IOException
    {
        discMgr = discoveryMgr;
        try {
           createJoinManager(null, serviceProxy, attrSets, callback, leaseMgr,
                             EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /**
     * Constructs an instance of this class, configured using the items
     * retrieved through the given <code>Configuration</code> object,
     * that will register the given service reference with all discovered
     * lookup services and, through an event sent to the given
     * <code>ServiceIDListener</code> object, communicate the service ID
     * assigned by the first lookup service with which the service is
     * registered. This constructor is typically used by services which
     * have not yet been assigned a service ID, and which wish to allow
     * for deployment-time configuration of the service's join processing.
     * <p>
     * The items used to configure the current instance of this class
     * are obtained through the object input to the <code>config</code>
     * parameter. If <code>null</code> is input to that parameter, a
     * <code>NullPointerException</code> is thrown.
     * <p>
     * The object this utility will use to manage lookup service discovery on
     * behalf of the given service can be supplied through either the
     * <code>discoveryMgr</code> parameter or through an entry contained
     * in the given <code>Configuration</code>. If <code>null</code> is input
     * to the <code>discoveryMgr</code> parameter, an attempt will first be
     * made to retrieve from the given <code>Configuration</code>, an entry
     * named "discoveryManager" (described above). If such an object is
     * successfully retrieved from the given <code>Configuration</code>, that
     * object will be used to perform the lookup service discovery management
     * required by this utility.
     * <p>
     * If <code>null</code> is input to the <code>discoveryMgr</code>
     * parameter, and no entry named "discoveryManager" is specified in the
     * given <code>Configuration</code>, then an instance of the utility class
     * <code>LookupDiscoveryManager</code> will be used to listen for events
     * announcing the discovery of only those lookup services that are
     * members of the public group.
     * <p>
     * As with the <code>discoveryMgr</code> parameter, the object this
     * utility will use to perform lease management on behalf of the given
     * service can be supplied through either the <code>leaseMgr</code>
     * parameter or through an entry contained in the given
     * <code>Configuration</code>. If <code>null</code> is input to the
     * <code>leaseMgr</code> parameter, an attempt will first be made to
     * retrieve from the given <code>Configuration</code>, an entry named
     * "leaseManager" (described above). If such an object is successfully
     * retrieved from the given <code>Configuration</code>, that object
     * will be used to perform the lease management required by this utility.
     * <p>
     * If <code>null</code> is input to the <code>leaseMgr</code>
     * parameter, and no entry named "leaseManager" is specified in the
     * given <code>Configuration</code>, then an instance of the utility
     * class <code>LeaseRenewalManager</code> that takes the given
     * <code>Configuration</code> will be created (initially managing no
     * leases) and used to perform all required lease renewal management
     * on behalf of the given service.
     * <p>
     * Except for the <code>config</code> parameter and the additional
     * semantics imposed by that parameter (as noted above), all other
     * parameters of this form of the constructor, along with their
     * associated semantics, are identical to that of the five-argument
     * constructor that takes a <code>ServiceIDListener</code>.
     *
     * @param serviceProxy the service reference (proxy) to register with all
     *                     discovered lookup services
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service
     * @param callback     reference to the <code>ServiceIDListener</code>
     *                     object that should receive the event containing the
     *                     service ID assigned to the service by the first
     *                     lookup service with which the service reference
     *                     is registered
     * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
     *                     object this class should use to manage lookup
     *                     service discovery on behalf of the given service
     * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     * @param config       instance of <code>Configuration</code> through
     *                     which the items used to configure the current
     *                     instance of this class are obtained
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         <code>serviceProxy</code> parameter is not serializable
     * @throws java.lang.NullPointerException if <code>null</code> is input
     *         to the <code>serviceProxy</code> parameter or the
     *         <code>config</code> parameter, or if at least one of the
     *         elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     * @throws java.io.IOException if initiation of discovery process results
     *         in <code>IOException</code> when socket allocation occurs
     * @throws net.jini.config.ConfigurationException if an exception
     *         occurs while retrieving an item from the given
     *         <code>Configuration</code> object
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated <code>JoinManager</code> instance. Note that this
     *         exception is implementation-specific.
     *
     * @see net.jini.lookup.ServiceIDListener
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     * @see net.jini.config.Configuration
     * @see net.jini.config.ConfigurationException
     */
     public JoinManager(Object serviceProxy,
                        Entry[] attrSets,
      ServiceIDListener callback,
      DiscoveryManagement discoveryMgr,
      LeaseRenewalManager leaseMgr,
                        Configuration config)
                                    throws IOException, ConfigurationException
    {
        discMgr = discoveryMgr;
        createJoinManager(null, serviceProxy, attrSets,
                          callback, leaseMgr, config);
    }//end constructor

    /**
     * Constructs an instance of this class that will register the
     * service with all discovered lookup services, using the supplied
     * <code>ServiceID</code>. This constructor is typically used by
     * services which have already been assigned a service ID (possibly
     * by the service provider itself or as a result of a prior registration
     * with some lookup service), and which do not wish to allow for
     * deployment-time configuration of the service's join processing.
     * <p>
     * Except that the desired <code>ServiceID</code> is supplied through the
     * <code>serviceID</code> parameter rather than through a notification
     * sent to a <code>ServiceIDListener</code>, all other parameters
     * of this form of the constructor, along with their associated semantics,
     * are identical to that of the five-argument constructor that takes
     * a <code>ServiceIDListener</code>.
     *
     * @param serviceProxy a reference to the service requesting the services
     *                     of this class
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service
     * @param serviceID    an instance of <code>ServiceID</code> with which to
     *                     register the service with all desired lookup
     *                     services
     * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
     *                     object this class should use to manage the given
     *                     service's lookup service discovery duties
     * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         <code>serviceProxy</code> parameter is not serializable
     * @throws java.lang.NullPointerException if either <code>null</code> is
     *         input to the <code>serviceProxy</code> parameter, or at least
     *         one of the elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     * @throws java.io.IOException if initiation of discovery process results
     *         in <code>IOException</code> when socket allocation occurs
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated <code>JoinManager</code> instance. Note that this
     *         exception is implementation-specific.
     *
     * @see net.jini.core.lookup.ServiceID
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     */
     public JoinManager(Object serviceProxy,
                        Entry[] attrSets,
      ServiceID serviceID,
      DiscoveryManagement discoveryMgr,
      LeaseRenewalManager leaseMgr)    throws IOException
    {
        discMgr = discoveryMgr;
        try {
           createJoinManager(serviceID, serviceProxy, attrSets,
                             (ServiceIDListener)null, leaseMgr,
                             EmptyConfiguration.INSTANCE);
        } catch(ConfigurationException e) { /* swallow this exception */ }
    }//end constructor

    /**
     * Constructs an instance of this class, configured using the items
     * retrieved through the given <code>Configuration</code>, that will
     * register the service with all discovered lookup services, using the
     * supplied <code>ServiceID</code>. This constructor is typically used by
     * services which have already been assigned a service ID (possibly
     * by the service provider itself or as a result of a prior registration
     * with some lookup service), and which wish to allow for deployment-time
     * configuration of the service's join processing.
     * <p>
     * The items used to configure the current instance of this class
     * are obtained through the object input to the <code>config</code>
     * parameter. If <code>null</code> is input to that parameter, a
     * <code>NullPointerException</code> is thrown.
     * <p>
     * Except that the desired <code>ServiceID</code> is supplied through the
     * <code>serviceID</code> parameter rather than through a notification
     * sent to a <code>ServiceIDListener</code>, all other parameters
     * of this form of the constructor, along with their associated semantics,
     * are identical to that of the six-argument constructor that takes
     * a <code>ServiceIDListener</code>.
     *
     * @param serviceProxy a reference to the service requesting the services
     *                     of this class
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the service.
     * @param serviceID    an instance of <code>ServiceID</code> with which to
     *                     register the service with all desired lookup
     *                     services
     * @param discoveryMgr reference to the <code>DiscoveryManagement</code>
     *                     object this class should use to manage lookup
     *                     service discovery on behalf of the given service
     * @param leaseMgr     reference to the <code>LeaseRenewalManager</code>
     *                     object this class should use to manage the leases
     *                     on the given service's residency in the lookup
     *                     services that have been joined
     * @param config       instance of <code>Configuration</code> through
     *                     which the items used to configure the current
     *                     instance of this class are obtained
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         <code>serviceProxy</code> parameter is not serializable
     * @throws java.lang.NullPointerException if <code>null</code> is input
     *         to the <code>serviceProxy</code> parameter or the
     *         <code>config</code> parameter, or if at least one of the
     *         elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     * @throws java.io.IOException if initiation of discovery process results
     *         in <code>IOException</code> when socket allocation occurs
     * @throws net.jini.config.ConfigurationException if an exception
     *         occurs while retrieving an item from the given
     *         <code>Configuration</code> object
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated <code>JoinManager</code> instance. Note that this
     *         exception is implementation-specific.
     *
     * @see net.jini.core.lookup.ServiceID
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     * @see net.jini.lease.LeaseRenewalManager
     * @see net.jini.config.Configuration
     * @see net.jini.config.ConfigurationException
     */
     public JoinManager(Object serviceProxy,
                        Entry[] attrSets,
      ServiceID serviceID,
      DiscoveryManagement discoveryMgr,
      LeaseRenewalManager leaseMgr,
                        Configuration config)
                                    throws IOException, ConfigurationException
    {
        discMgr = discoveryMgr;
        createJoinManager(serviceID, serviceProxy, attrSets,
                          (ServiceIDListener)null, leaseMgr, config);
    }//end constructor

    /**
     * Returns the instance of <code>DiscoveryManagement</code> that was
     * either passed into the constructor, or that was created as a result
     * of <code>null</code> being input to that parameter.
     * <p>
     * The object returned by this method encapsulates the mechanism by which
     * either the <code>JoinManager</code> or the entity itself can set
     * discovery listeners and discard previously discovered lookup services
     * when they are found to be unavailable.
     *
     * @return the instance of the <code>DiscoveryManagement</code> interface
     *         that was either passed into the constructor, or that was
     *         created as a result of <code>null</code> being input to that
     *         parameter.
     *
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.discovery.LookupDiscoveryManager
     */
    public DiscoveryManagement getDiscoveryManager(){
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  return discMgr;
    }//end getDiscoveryManager

    /**
     * Returns the instance of the <code>LeaseRenewalManager</code> class
     * that was either passed into the constructor, or that was created
     * as a result of <code>null</code> being input to that parameter.
     * <p>
     * The object returned by this method manages the leases requested and
     * held by the <code>JoinManager</code>. Although it may also manage
     * leases unrelated to the join process that are requested and held by
     * the service itself, the leases with which the <code>JoinManager</code>
     * is concerned are the leases that correspond to the service registration
     * requests made with each lookup service the service wishes to join.
     *
     * @return the instance of the <code>LeaseRenewalManager</code> class
     *         that was either passed into the constructor, or that was
     *         created as a result of <code>null</code> being input to that
     *         parameter.
     *
     * @see net.jini.discovery.DiscoveryManagement
     * @see net.jini.lease.LeaseRenewalManager
     */
    public LeaseRenewalManager getLeaseRenewalManager(){
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  return leaseRenewalMgr;
    }//end getLeaseRenewalManager

    /**
     * Returns an array of <code>ServiceRegistrar</code> objects, each
     * corresponding to a lookup service with which the service is currently
     * registered (joined). If there are no lookup services with which the
     * service is currently registered, this method returns the empty array.
     * This method returns a new array upon each invocation.
     *
     * @return array of instances of <code>ServiceRegistrar</code>, each
     *         corresponding to a lookup service with which the service is
     *         currently registered
     *
     * @see net.jini.core.lookup.ServiceRegistrar
     */
    public ServiceRegistrar[] getJoinSet() {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  synchronized(joinSet) {
            ArrayList retList = new ArrayList(joinSet.size());
      int k = 0;
      for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
                ProxyReg proxyReg = (ProxyReg)iter.next();
                if(proxyReg.srvcRegistration != null) {//test registration flag
                    retList.add(proxyReg.proxy);
                }//endif
      }//end loop
            return ( (ServiceRegistrar[])(retList.toArray
                                 (new ServiceRegistrar[retList.size()]) ) );
  }//end sync(joinSet)
    }//end getJoinSet

    /**
     * Returns an array containing the set of attributes currently associated
     * with the service. If the service is not currently associated with an
     * attribute set, this method returns the empty array. This method returns
     * a new array upon each invocation.
     *
     * @return array of instances of <code>Entry</code> consisting of the
     *         set of attributes with which the service is registered in
     *         each lookup service that it has joined
     *
     * @see net.jini.core.entry.Entry
     * @see #setAttributes
     */
    public Entry[] getAttributes() {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  synchronized(joinSet) {
      return (Entry[])lookupAttr.clone();
  }//end sync(joinSet)
    }//end getAttributes

    /**
     * Associates a new set of attributes with the service, in addition to
     * the service's current set of attributes. The association of this new
     * set of attributes with the service will be propagated to each lookup
     * service with which the service is registered. Note that this
     * propagation is performed asynchronously, thus there is no guarantee
     * that the propagation of the attributes to all lookup services with
     * which the service is registered will have completed upon return from
     * this method.
     * <p>
     * An invocation of this method with duplicate elements in the
     * <code>attrSets</code> parameter (where duplication means attribute
     * equality as defined by calling the <code>MarshalledObject.equals</code>
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     * <p>
     * Note that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that
     * invoke this method must take care not to modify the contents of the
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSets array of <code>Entry</code> consisting of the
     *                 attribute sets with which to augment the service's
     *                 current set of attributes
     *
     * @throws java.lang.NullPointerException if either <code>null</code> is
     *         input to the <code>attrSets</code> parameter, or one or more
     *         of the elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     *
     * @see net.jini.core.entry.Entry
     */
    public void addAttributes(Entry[] attrSets) {
  addAttributes(attrSets, false);
    }//end addAttributes

    /**
     * Associates a new set of attributes with the service, in addition to
     * the service's current set of attributes. The association of this new
     * set of attributes with the service will be propagated to each lookup
     * service with which the service is registered. Note that this
     * propagation is performed asynchronously, thus there is no guarantee
     * that the propagation of the attributes to all lookup services with
     * which the service is registered will have completed upon return from
     * this method.
     * <p>
     * An invocation of this method with duplicate elements in the
     * <code>attrSets</code> parameter (where duplication means attribute
     * equality as defined by calling the <code>MarshalledObject.equals</code>
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     * <p>
     * Note that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that
     * invoke this method must take care not to modify the contents of the
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     * <p>
     * A service typically employs this version of <code>addAttributes</code>
     * to prevent clients or other services from attempting to add what are
     * referred to as "service controlled attributes" to the service's set.
     * A service controlled attribute is an attribute that implements the
     * <code>ServiceControlled</code> marker interface.
     * <p>
     * Consider a printer service. With printers, there are often times error
     * conditions, that only the printer can detect (for example, a paper
     * jam or a toner low condition). To report conditions such as these to
     * interested parties, the printer typically adds an attribute to its
     * attribute set, resulting in an event being sent that notifies clients
     * that have registered interest in such events. When the condition is
     * corrected, the printer would then remove the attribute from its set
     * by invoking the <code>modifyAttributes</code> method in the appropriate
     * manner.
     * <p>
     * Attributes representing conditions that only the service can know about
     * or control are good candidates for being defined as service controlled
     * attributes. That is, the service provider (the developer of the printer
     * service for example) would define the attributes that represent
     * conditions such as those just described to implement the
     * <code>ServiceControlled</code> marker interface. Thus, when other
     * entities attempt to add new attributes, services that wish to employ
     * such service controlled attributes should ultimately invoke only this
     * version of <code>addAttributes</code> (with the <code>checkSC</code>
     * parameter set to <code>true</code>), resulting in a
     * <code>SecurityException</code> if any of the attributes being added
     * happen to be service controlled attributes. In this way, only the
     * printer itself would be able to set a "paper jammed" or "toner low"
     * attribute, not some arbitrary client.
     *
     * @param attrSets array of <code>Entry</code> consisting of the
     *                 attribute sets with which to augment the service's
     *                 current set of attributes
     * @param checkSC  <code>boolean</code> flag indicating whether the
     *                 elements of the set of attributes to add should be
     *                 checked to determine if they are service controlled
     *                 attributes
     *
     * @throws java.lang.NullPointerException if either <code>null</code> is
     *         input to the <code>attrSets</code> parameter, or one or more
     *         of the elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     *
     * @throws java.lang.SecurityException if the <code>checkSC</code>
     *         parameter is <code>true</code>, and at least one of the
     *         attributes to be added is an instance of the
     *         <code>ServiceControlled</code> marker interface
     *
     * @see net.jini.core.entry.Entry
     * @see net.jini.lookup.entry.ServiceControlled
     */
    public void addAttributes(Entry[] attrSets, boolean checkSC) {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  synchronized(joinSet) {
      lookupAttr = LookupAttributes.add(lookupAttr, attrSets, checkSC);
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)joinSet.get(i);
                proxyReg.addTask(new AddAttributesTask(proxyReg,attrSets));
            }//end loop
  }//end sync(joinSet)
    }//end addAttributes

    /**
     * Replaces the service's current set of attributes with a new set of
     * attributes. The association of this new set of attributes with the
     * service will be propagated to each lookup service with which the
     * service is registered. Note that this propagation is performed
     * asynchronously, thus there is no guarantee that the propagation of
     * the attributes to all lookup services with which the service is
     * registered will have completed upon return from this method.
     * <p>
     * An invocation of this method with duplicate elements in the
     * <code>attrSets</code> parameter (where duplication means attribute
     * equality as defined by calling the <code>MarshalledObject.equals</code>
     * method on field values) is equivalent to performing the invocation
     * with the duplicates removed from that parameter.
     * <p>
     * Note that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that
     * invoke this method must take care not to modify the contents of the
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSets array of <code>Entry</code> consisting of the
     *                 attribute sets with which to replace the service's
     *                 current set of attributes
     *
     * @throws java.lang.NullPointerException if either <code>null</code> is
     *         input to the <code>attrSets</code> parameter, or one or more
     *         of the elements of the <code>attrSets</code> parameter is
     *         <code>null</code>.
     *
     * @see net.jini.core.entry.Entry
     * @see #getAttributes
     */
    public void setAttributes(Entry[] attrSets) {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
        testForNullElement(attrSets);
  synchronized(joinSet) {
            lookupAttr = (Entry[]) attrSets.clone();
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)joinSet.get(i);
                proxyReg.addTask(new SetAttributesTask(proxyReg,attrSets));
            }//end loop
  }//end sync(joinSet)
    }//end setAttributes

    /**
     * Changes the service's current set of attributes using the same
     * semantics as the <code>modifyAttributes</code> method of the
     * <code>ServiceRegistration</code> class.
     * <p>
     * The association of the new set of attributes with the service will
     * be propagated to each lookup service with which the service is
     * registered. Note that this propagation is performed asynchronously,
     * thus there is no guarantee that the propagation of the attributes to
     * all lookup services with which the service is registered will have
     * completed upon return from this method.
     * <p>
     * Note that if the length of the array containing the templates does
     * not equal the length of the array containing the modifications, an
     * <code>IllegalArgumentException</code> will be thrown and propagated
     * through this method.
     * <p>
     * Note also that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that
     * invoke this method must take care not to modify the contents of the
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     *
     * @param attrSetTemplates array of <code>Entry</code> used to identify
     *                         which elements to modify from the service's
     *                         current set of attributes
     * @param attrSets         array of <code>Entry</code> containing the
     *                         actual modifications to make in the matching
     *                         sets found using the
     *                         <code>attrSetTemplates</code> parameter
     *
     * @throws java.lang.IllegalArgumentException if the array containing the
     *         templates does not equal the length of the array containing the
     *         modifications
     *
     * @see net.jini.core.entry.Entry
     * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
     */
    public void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets) {
  modifyAttributes(attrSetTemplates, attrSets, false);
    }//end modifyAttributes

    /**
     * Changes the service's current set of attributes using the same
     * semantics as the <code>modifyAttributes</code> method of the
     * <code>ServiceRegistration</code> class.
     * <p>
     * The association of the new set of attributes with the service will
     * be propagated to each lookup service with which the service is
     * registered. Note that this propagation is performed asynchronously,
     * thus there is no guarantee that the propagation of the attributes to
     * all lookup services with which the service is registered will have
     * completed upon return from this method.
     * <p>
     * Note that if the length of the array containing the templates does
     * not equal the length of the array containing the modifications, an
     * <code>IllegalArgumentException</code> will be thrown and propagated
     * through this method.
     * <p>
     * Note also that because there is no guarantee that attribute propagation
     * will have completed upon return from this method, services that
     * invoke this method must take care not to modify the contents of the
     * <code>attrSets</code> parameter. Doing so could cause the service's
     * attribute state to be corrupted or inconsistent on a subset of the
     * lookup services with which the service is registered as compared with
     * the state reflected on the remaining lookup services. It is for this
     * reason that the effects of modifying the contents of the
     * <code>attrSets</code> parameter, after this method is invoked, are
     * undefined.
     * <p>
     * A service typically employs this version of
     * <code>modifyAttributes</code> to prevent clients or other services
     * from attempting to modify what are referred to as "service controlled
     * attributes" in the service's set. A service controlled attribute is an
     * attribute that implements the <code>ServiceControlled</code> marker
     * interface.
     * <p>
     * Attributes representing conditions that only the service can know about
     * or control are good candidates for being defined as service controlled
     * attributes. When other entities attempt to modify a service's
     * attributes, if the service wishes to employ such service controlled
     * attributes, the service should ultimately invoke only this version
     * of <code>modifyAttributes</code> (with the <code>checkSC</code>
     * parameter set to <code>true</code>), resulting in a
     * <code>SecurityException</code> if any of the attributes being modified
     * happen to be service controlled attributes.
     *
     * @param attrSetTemplates array of <code>Entry</code> used to identify
     *                         which elements to modify from the service's
     *                         current set of attributes
     * @param attrSets         array of <code>Entry</code> containing the
     *                         actual modifications to make in the matching
     *                         sets found using the
     *                         <code>attrSetTemplates</code> parameter
     * @param checkSC          <code>boolean</code> flag indicating whether the
     *                         elements of the set of attributes to modify
     *                         should be checked to determine if they are
     *                         service controlled attributes
     *
     * @throws java.lang.IllegalArgumentException if the array containing the
     *         templates does not equal the length of the array containing
     *         the modifications
     *
     * @throws java.lang.SecurityException if the <code>checkSC</code>
     *         parameter is <code>true</code>, and at least one of the
     *         attributes to be modified is an instance of the
     *         <code>ServiceControlled</code> marker interface
     *
     * @see net.jini.core.entry.Entry
     * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes
     * @see net.jini.lookup.entry.ServiceControlled
     */
    public void modifyAttributes(Entry[] attrSetTemplates,
                                 Entry[] attrSets,
                                 boolean checkSC)
    {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  synchronized(joinSet) {
      lookupAttr = LookupAttributes.modify(lookupAttr, attrSetTemplates,
                                                 attrSets, checkSC);
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)joinSet.get(i);
                proxyReg.addTask(new ModifyAttributesTask(proxyReg,
                                                          attrSetTemplates,
                                                          attrSets));
            }//end loop
  }//end sync(joinSet)
    }//end modifyAttributes

    /**
     * Performs cleanup duties related to the termination of the lookup
     * service discovery event mechanism, as well as the lease and
     * thread management performed by the <code>JoinManager</code>. This
     * method will cancel all of the service's managed leases that were
     * granted by the lookup services with which the service is registered,
     * and will terminate all threads that have been created.
     * <p>
     * Note that if the discovery manager employed by the instance of this
     * class that is being terminated was created by the instance itself,
     * this method will terminate all discovery processing being performed by
     * that manager object on behalf of the service; otherwise, the discovery
     * manager supplied by the service is still valid.
     * <p>
     * Whether an instance of the <code>LeaseRenewalManager</code> class was
     * supplied by the service or created by the <code>JoinManager</code>
     * itself, any reference to that object obtained by the service prior to
     * termination will still be valid after termination.
     * Note also this class makes certain concurrency guarantees with respect
     * to an invocation of the terminate method while other method invocations
     * are in progress. The termination process will not begin until
     * completion of all invocations of the methods defined in the public
     * interface of this class. Furthermore, once the termination process has
     * begun, no further remote method invocations will be made by this class,
     * and all other method invocations made on this class will not return
     * until the termination process has completed.
     * <p>
     * Upon completion of the termination process, the semantics of all
     * current and future method invocations on the instance of this class
     * that was just terminated are undefined; although the reference to the
     * <code>LeaseRenewalManager</code> object employed by that instance
     * of <code>JoinManager</code> is still valid.
     */
    public void terminate() {
        synchronized(this) {
            if(bTerminated) return;//allow for multiple terminations
            bTerminated = true;
            /* Terminate discovery and task management */
            discMgr.removeDiscoveryListener(discMgrListener);
            if(bCreateDiscMgrdiscMgr.terminate();
        }//end sync(this)
        terminateTaskMgr();
        /* Clear the joinSet and cancel all leases held by the service */
        ArrayList srvcLeases = null;//store leases for use outside of sync blk
  synchronized(joinSet) {
            srvcLeases = new ArrayList(joinSet.size());
      for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
                srvcLeases.add
                    ( (((ProxyReg)iter.next()).serviceLease) );
      }//end loop
      joinSet.clear();
  }//end sync(joinSet)
        /* Must cancel leases outside of sync block because of remote call */
        if(srvcLeases == null) return;
        for(int i=0;i<srvcLeases.size();i++) {
            try {
                leaseRenewalMgr.cancel((Lease)srvcLeases.get(i));
            } catch (Exception e) { }
        }//end loop
    }//end terminate

    /**
     * Registers a new reference to the service with all current and future
     * discovered lookup services. The new service reference will replace
     * the reference that was previously registered as a result of either
     * constructing this utility, or a prior invocation of one of the forms
     * of this method. The new service reference will be registered using
     * the same <code>ServiceID</code> with which previous registrations
     * were made through this utility.
     * <p>
     * The value input to the <code>serviceProxy</code> parameter represents
     * the new service reference (proxy) to register with each discovered
     * lookup service. If the <code>Object</code> input to that parameter is
     * not <code>Serializable</code>, an <code>IllegalArgumentException</code>
     * is thrown. If <code>null</code> is input to that parameter, a
     * <code>NullPointerException</code> is thrown.
     * <p>
     * The attribute sets that this method associates with the new service
     * reference are the same attribute sets as those associated with the
     * old registration.
     *
     * @param serviceProxy the new service reference (proxy) to register with
     *                     all current and future discovered lookup services
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         <code>serviceProxy</code> parameter is not serializable
     * @throws java.lang.NullPointerException if <code>null</code> is input
     *         to the <code>serviceProxy</code> parameter
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated <code>JoinManager</code> instance. Note that this
     *         exception is implementation-specific.
     */
    public void replaceRegistration(Object serviceProxy) {
        replaceRegistrationDo(serviceProxy, null, false);
    }//end replaceRegistration

    /**
     * Registers a new reference to the service with all current and future
     * discovered lookup services, applying semantics identical to the
     * one-argument form of this method, except with respect to the
     * registration of the given attribute sets.
     * <p>
     * This form of <code>replaceRegistration</code> takes as its
     * second parameter, an array of <code>Entry</code> objects
     * (<code>attrSets</code>), none of whose elements may be
     * <code>null</code>, that represents the new set of attributes to
     * associate with the new service reference to be registered. As with
     * the constructor to this utility, passing <code>null</code> as the
     * value of the <code>attrSets</code> parameter is equivalent to passing
     * an empty array. If any of the elements of <code>attrSets</code> are
     * <code>null</code>, a <code>NullPointerException</code> is thrown.
     * This new set of attributes will be associated with the service in
     * all future join processing.
     *
     * @param serviceProxy the new service reference (proxy) to register with
     *                     all current and future discovered lookup services
     * @param attrSets     array of <code>Entry</code> consisting of the
     *                     attribute sets with which to register the new
     *                     service reference. Passing <code>null</code> as
     *                     the value of this parameter is equivalent to
     *                     passing an empty <code>Entry</code> array
     *
     * @throws java.lang.IllegalArgumentException if the object input to the
     *         <code>serviceProxy</code> parameter is not serializable
     * @throws java.lang.NullPointerException if either <code>null</code> is
     *         input to the <code>serviceProxy</code> parameter, or at least
     *         one of the elements of the <code>attrSets</code> parameter is
     *         <code>null</code>
     *
     * @throws java.lang.IllegalStateException if this method is called on
     *         a terminated <code>JoinManager</code> instance. Note that this
     *         exception is implementation-specific.
     */
    public void replaceRegistration(Object serviceProxy, Entry[] attrSets) {
        replaceRegistrationDo(serviceProxy, attrSets, true);
    }//end replaceRegistration

    /** Convenience method invoked by the constructors of this class that
     *  uses the given <code>Configuration</code> to initialize the current
     *  instance of this utility, and initiates all join processing for
     *  the given parameters. This method handles the various configurations
     *  allowed by the different constructors.
     */
    private void createJoinManager(ServiceID serviceID,
                                   Object serviceProxy,
                                   Entry[] attrSets,
                                   ServiceIDListener callback,
                                   LeaseRenewalManager leaseMgr,
                                   Configuration config)
                                    throws IOException, ConfigurationException
    {
  if(!(serviceProxy instanceof java.io.Serializable)) {
            throw new IllegalArgumentException
                                       ("serviceProxy must be Serializable");
  }//endif

        /* Retrieve configuration items if applicable */
        if(config == nullthrow new NullPointerException("config is null");
        /* Proxy preparers */
        registrarPreparer = (ProxyPreparer)config.getEntry
                                                   (COMPONENT_NAME,
                                                    "registrarPreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        registrationPreparer = (ProxyPreparer)config.getEntry
                                                   (COMPONENT_NAME,
                                                    "registrationPreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        serviceLeasePreparer = (ProxyPreparer)config.getEntry
                                                   (COMPONENT_NAME,
                                                    "serviceLeasePreparer",
                                                    ProxyPreparer.class,
                                                    new BasicProxyPreparer());
        /* Task manager */
        try {
            taskMgr = (TaskManager)config.getEntry(COMPONENT_NAME,
                                                   "taskManager",
                                                   TaskManager.class);
        } catch(NoSuchEntryException e) { /* use default */
            taskMgr = new TaskManager(MAX_N_TASKS,(15*1000),1.0f);
        }
        /* Wakeup manager */
        try {
            wakeupMgr = (WakeupManager)config.getEntry(COMPONENT_NAME,
                                                       "wakeupManager",
                                                       WakeupManager.class);
        } catch(NoSuchEntryException e) { /* use default */
            wakeupMgr = new WakeupManager
                                    (new WakeupManager.ThreadDesc(null,true));
        }
        /* Max number of times to re-schedule tasks in thru wakeup manager */
        maxNRetries = ((Integer)config.getEntry
                                        (COMPONENT_NAME,
                                         "wakeupRetries",
                                         int.class,
                                         new Integer(maxNRetries))).intValue();
        if(attrSets == null) {
            lookupAttr = new Entry[0];
        } else {
            attrSets = (Entry[])attrSets.clone();
            LookupAttributes.check(attrSets,false);//null elements NOT ok
            lookupAttr = attrSets;
        }//endif
  serviceItem = new ServiceItem(serviceID, serviceProxy, lookupAttr);
        /* Lease renewal manager */
        leaseRenewalMgr = leaseMgr;
  if(leaseRenewalMgr == null) {
            try {
                leaseRenewalMgr = (LeaseRenewalManager)config.getEntry
                                                  (COMPONENT_NAME,
                                                   "leaseManager",
                                                   LeaseRenewalManager.class);
            } catch(NoSuchEntryException e) { /* use default */
                leaseRenewalMgr = new LeaseRenewalManager(config);
            }
        }//endif
        renewalDuration = ((Long)config.getEntry
                                      (COMPONENT_NAME,
                                       "maxLeaseDuration",
                                       long.class,
                                       new Long(renewalDuration))).longValue();
        if( (renewalDuration == 0) || (renewalDuration < Lease.ANY) ) {
            throw new ConfigurationException("invalid configuration entry: "
                                             +"renewalDuration ("
                                             +renewalDuration+") must be "
                                             +"positive or Lease.ANY");
        }//endif
  this.callback = callback;
        /* Discovery manager */
  if(discMgr == null) {
      bCreateDiscMgr = true;
            try {
                discMgr = (DiscoveryManagement)config.getEntry
                                                 (COMPONENT_NAME,
                                                  "discoveryManager",
                                                  DiscoveryManagement.class);
            } catch(NoSuchEntryException e) { /* use default */
                discMgr = new LookupDiscoveryManager
                                     (new String[] {""}, null, null, config);
            }
  }//endif
  discMgr.addDiscoveryListener(discMgrListener);
    }//end createJoinManager

    /** For the given lookup service proxy, searches the <code>joinSet</code>
     *  for the corresponding <code>ProxyReg</code> element, and upon finding
     *  such an element, returns that element; otherwise returns
     *  <code>null</code>.
     */
    private ProxyReg findReg(ServiceRegistrar proxy) {
  for (Iterator iter = joinSet.iterator(); iter.hasNext(); ) {
      ProxyReg reg =(ProxyReg)iter.next();
      if(reg.proxy.equals(proxy))  return reg;
  }//end loop
  return null;
    }//end findReg

    /** Removes (from the task manager) and cancels (in the wakeup manager)
     *  all tasks associated with the given instance of <code>ProxyReg</code>.
     */
    private void removeTasks(ProxyReg proxyReg) {
        if(proxyReg == null) return;
        if(taskMgr == null) return;
        synchronized(proxyReg.taskList) {
            if(proxyReg.proxyRegTask != null) {
                synchronized(taskMgr) {
                    taskMgr.remove(proxyReg.proxyRegTask);
                }//end sync(taskMgr)
                proxyReg.proxyRegTask.cancel();//cancel retry in WakeupMgr
                proxyReg.proxyRegTask = null//don't reuse because of seq#
            }//endif
            proxyReg.taskList.clear();
        }//end sync(proxyReg.taskList)
    }//end removeTasks

    /** Removes from the task manager, all pending tasks regardless of the
     *  the instance of <code>ProxyReg</code> with which the task is
     *  associated, and then terminates the task manager, and makes it
     *  a candidate for garbage collection.
     */
    private void terminateTaskMgr() {
        synchronized(wakeupMgr) {
            /* Cancel all tasks scheduled for future retry by the wakeup mgr */
            wakeupMgr.cancelAll();//cancel all tickets
            wakeupMgr.stop();//stop execution of the wakeup manager
            synchronized(taskMgr) {
                /* Remove all pending tasks */
                ArrayList pendingTasks = taskMgr.getPending();
                for(int i=0;i<pendingTasks.size();i++) {
                    RetryTask pendingTask = (RetryTask)pendingTasks.get(i);
                    pendingTask.cancel();//cancel wakeup ticket
                    taskMgr.remove(pendingTask);//remove from task mgr
                }//end loop
                /* Interrupt all active tasks, prepare taskMgr for GC. */
                taskMgr.terminate();
                taskMgr = null;
            }//end sync(taskMgr)
            wakeupMgr = null;
        }//end sync(wakeupMgr)
    }//end terminateTaskMgr

    /** Examines the elements of the input set and, upon finding at least one
     *  <code>null</code> element, throws a <code>NullPointerException</code>.
     */
    private void testForNullElement(Object[] a) {
        if(a == null) return;
        for(int i=0;i<a.length;i++) {
            if(a[i] == null) {
                throw new NullPointerException
                          ("input array contains at least one null element");
            }//endif
        }//end loop
    }//end testForNullElement

    /** Convenience method invoked by either form of the method
     *  <code>replaceRegistration</code>. This method registers the
     *  given <code>serviceProxy</code> with all discovered lookup
     *  services, replacing all current registrations. If the value
     *  of the <code>doAttrs</code> parameter is <code>true</code>,
     *  this method will associate the given <code>attrSets</code>
     *  with the new service registration; otherwise, it will use
     *  the attribute sets currently associated with the old registration.
     */
    private void replaceRegistrationDo(Object serviceProxy,
                                       Entry[] attrSets,
                                       boolean doAttrs)
    {
        synchronized(this) {
            if(bTerminated) {
                throw new IllegalStateException("join manager was terminated");
            }//endif
        }//end sync
  if(!(serviceProxy instanceof java.io.Serializable)) {
            throw new IllegalArgumentException
                                        ("serviceProxy must be Serializable");
  }//endif
  synchronized(joinSet) {
            if(doAttrs) {
                if(attrSets == null) {
                    lookupAttr = new Entry[0];
                } else {
                    attrSets = (Entry[])attrSets.clone();
                    LookupAttributes.check(attrSets,false);//no null elements
                    lookupAttr = attrSets;
                }//endif
            }//endif
            serviceItem.service = serviceProxy;
            serviceItem.attributeSets = lookupAttr;
            for(int i=0;i<joinSet.size();i++) {
                ProxyReg proxyReg = (ProxyReg)(joinSet.get(i));
                removeTasks(proxyReg);
                try {
                    leaseRenewalMgr.remove( proxyReg.serviceLease );
                } catch (Exception e) { }
                proxyReg.addTask(new RegisterTask(proxyReg,
                                                 (Entry[])lookupAttr.clone()));
            }//end loop
  }//end sync(joinSet)
    }//end replaceRegistrationDo

}//end class JoinManager
TOP

Related Classes of net.jini.lookup.JoinManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.