/*
* 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.logging.Levels;
import com.sun.jini.lookup.entry.LookupAttributes;
import com.sun.jini.proxy.BasicProxyTrustVerifier;
import com.sun.jini.thread.TaskManager;
import com.sun.jini.thread.TaskManager.Task;
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.export.Exporter;
import net.jini.io.MarshalledInstance;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;
import net.jini.core.lease.Lease;
import net.jini.core.entry.Entry;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.EventRegistration;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceEvent;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* The <code>ServiceDiscoveryManager</code> class is a helper utility class
* that any client-like entity can use to "discover" services registered
* with any number of lookup services of interest. On behalf of such
* entities, this class maintains - as much as possible - up-to-date
* state information about both the lookup services the entity wishes
* to query, and the services the entity wishes to acquire and use.
* By maintaining current service state information, the entity can
* implement efficient mechanisms for service access and usage.
* <p>
* There are three basic usage patterns for this class. In order of
* importance and typical usage, those patterns are:
* <p>
* <ul>
* <li> The entity requests that the <code>ServiceDiscoveryManager</code>
* create a cache (an instance of
* {@link net.jini.lookup.LookupCache LookupCache}) which will
* asynchronously "discover", and locally store, references
* to services that match criteria defined by the entity; services
* which are registered with one or more lookup services managed
* by the <code>ServiceDiscoveryManager</code> on behalf of the entity.
* The cache can be viewed as a set of service references that the
* entity can access locally as needed through one of the public,
* non-remote methods provided in the cache's interface. Thus, rather
* than making costly remote queries of multiple lookup services at
* the point in time when the entity needs the service, the entity
* can simply make local queries on the cache for the services that
* the cache acquired and stored at a prior time. An entity should
* employ this pattern when the entity must make <i>frequent</i>
* queries for multiple services. By populating the cache with
* multiple instances of the desired services, redundancy in the
* availability of those services can be provided. Thus, if an
* instance of a service is found to be unavailable when needed,
* the entity can execute a local query on the cache rather than
* one or more remote queries on the lookup services to acquire
* an instance that is available. To employ this pattern, the entity
* invokes the method
* {@link net.jini.lookup.ServiceDiscoveryManager#createLookupCache
* createLookupCache}.
* <li> The entity can register with the event mechanism provided by the
* <code>ServiceDiscoveryManager</code>. This event mechanism allows the
* entity to request that it be notified when a service of interest
* is discovered for the first time, or has encountered a state change
* such as removal from all lookup services, or attribute set changes.
* Although interacting with a local cache of services in the way
* described in the first pattern can be very useful to entities that
* need frequent access to multiple services, some client-like
* entities may wish to interact with the cache in a reactive manner.
* For example, an entity such as a service browser typically wishes
* to be notified of the arrival of new services of interest as well
* as any changes in the state of the current services in the cache.
* In these situations, polling for such changes is usually viewed as
* undesirable. If the cache were to also provide an event mechanism
* with notification semantics, the needs of entities that employ
* either pattern can be satisfied. To employ this pattern, the entity
* must create a cache and supply it with an instance of the
* {@link net.jini.lookup.ServiceDiscoveryListener
* ServiceDiscoveryListener} interface that will receive instances of
* {@link net.jini.lookup.ServiceDiscoveryEvent ServiceDiscoveryEvent}
* when events of interest, related to the services in the cache, occur.
* <li> The entity, through the public API of the
* <code>ServiceDiscoveryManager</code>, can directly query the lookup
* services managed by the <code>ServiceDiscoveryManager</code> for
* services of interest; employing semantics similar to the semantics
* employed in a typical lookup service query made through the
* {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
* interface. Such queries will result in a remote call being made at
* the same time the service is needed (unlike the first pattern, in
* which remote calls typically occur prior to the time the service is
* needed). This pattern may be useful to entities needing to find
* services on an infrequent basis, or when the cost of making a remote
* call is outweighed by the overhead of maintaining a local cache (for
* example, due to limited resources). Although an entity that needs
* to query lookup service(s) can certainly make such queries through
* the {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
* interface, the <code>ServiceDiscoveryManager</code> provides a broad
* API with semantics that are richer than the semantics of the
* {@link net.jini.core.lookup.ServiceRegistrar#lookup lookup} methods
* provided by the {@link net.jini.core.lookup.ServiceRegistrar
* ServiceRegistrar}. This API encapsulates functionality that many
* client-like entities may find more useful when managing both the set
* of desired lookup services, and the service queries made on those
* lookup services. To employ this pattern, the entity simply
* instantiates this class with the desired parameters, and then
* invokes the appropriate version of the
* {@link net.jini.lookup.ServiceDiscoveryManager#lookup lookup}
* method when the entity wishes to acquire a service that matches
* desired criteria.
* </ul>
* <p>
* All three mechanisms just described - local queries on the cache,
* service discovery notification, and remote lookups - employ the same
* template-matching scheme as that employed in the
* {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar} interface.
* Additionally, each mechanism allows the entity to supply an object
* referred to as a <i>filter</i>; an instance of
* {@link net.jini.lookup.ServiceItemFilter ServiceItemFilter}. A filter
* is a non-remote object that defines additional matching criteria that the
* <code>ServiceDiscoveryManager</code> applies when searching for the
* entity's services of interest. Employing a filter is particularly useful
* to entities that wish to extend the capabilities of the standard
* template-matching scheme.
* <p>
* In addition to (or instead of) employing a filter to apply additional
* matching criteria to candidate service proxies initially found through
* template matching, filters can also be used to extend the selection
* process so that only proxies that are <i>safe</i> to use are returned
* to the entity. To do this, the entity would use the
* {@link net.jini.lookup.ServiceItemFilter ServiceItemFilter} interface to
* supply the <code>ServiceDiscoveryManager</code> or
* {@link net.jini.lookup.LookupCache LookupCache} with a filter that,
* when applied to a candidate proxy, performs a set of operations that
* is referred to as <i>proxy preparation</i>. As described in the
* documentation for {@link net.jini.security.ProxyPreparer}, proxy
* preparation typically includes operations such as, verifying trust
* in the proxy, specifying client constraints, and dynamically granting
* necessary permissions to the proxy.
* <p>
* Note that this utility class is not remote. Clients and services that wish
* to use this class will create an instance of this class in their own address
* space to manage the state of discovered services and their associated
* lookup services locally.
*
* @com.sun.jini.impl <!-- Implementation Specifics -->
*
* The following implementation-specific items are discussed below:
* <ul><li> <a href="#sdmConfigEntries">Configuring ServiceDiscoveryManager</a>
* <li> <a href="#sdmLogging">Logging</a>
* </ul>
*
* <a name="sdmConfigEntries">
* <p>
* <b><font size="+1">Configuring ServiceDiscoveryManager</font></b>
* <p>
* </a>
*
* This implementation of <code>ServiceDiscoveryManager</code> supports
* the following configuration entries; where each configuration entry
* name is associated with the component name
* <code>net.jini.lookup.ServiceDiscoveryManager</code>. Note that the
* configuration entries specified here are specific to this implementation
* of <code>ServiceDiscoveryManager</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.
* <p>
* It is important to note that in addition to allowing a client of this
* utility to request - through the public API - the creation of a cache
* that is used externally by the client, this utility also creates
* instances of the cache that are used internally by the utility itself.
* As such, in addition to the configuration entries that are used only
* in this utility (and not in any cache), and the configuration entries
* that are retrieved during the construction of each new cache (and used
* by only that cache), there are configuration entries specified below
* that are retrieved once during the construction of this utility, but
* which are shared with, and used by, the caches that are created.
*
*
* <a name="cacheTaskManager">
* <table summary="Describes the cacheTaskManager configuration entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>cacheTaskManager</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link com.sun.jini.thread.TaskManager}
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>new
* {@link com.sun.jini.thread.TaskManager#TaskManager()
* TaskManager}(10, (15*1000), 1.0f)</code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description:
* <td> The object that pools and manages the various threads
* executed by each of the lookup caches created by this
* utility. There is one such task manager created for each
* cache. The default manager creates a maximum of 10 threads,
* waits 15 seconds before removing idle threads, and uses a
* load factor of 1.0 when determining whether to create a new
* thread. For each cache that is created in this utility, a
* single, separate instance of this task manager will be
* retrieved and employed by that cache. This object should
* not be shared with other components in the application that
* employs this utility.
* </table>
*
* <a name="discardTaskManager">
* <table summary="Describes the discardTaskManager configuration entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>discardTaskManager</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link com.sun.jini.thread.TaskManager}
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>new
* {@link com.sun.jini.thread.TaskManager#TaskManager()
* TaskManager}(10, (15*1000), 1.0f)</code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description:
* <td> The object that pools and manages the threads, executed
* by a cache, that wait on verification events after a
* previousy discovered service has been discarded. The
* default manager creates a maximum of 10 threads, waits
* 15 seconds before removing idle threads, and uses a load
* factor of 1.0 when determining whether to create a new
* thread. For each cache that is created in this utility,
* a single, separate instance of this task manager will be
* retrieved and employed by that cache. This object should
* not be shared with other components in the application
* that employs this utility.
* </table>
*
* <a name="discardWait">
* <table summary="Describes the discardWait
* configuration entry" border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>discardWait</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> <code>long</code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>2*(5*60*1000)</code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description:
* <td> The value used to affect the behavior of the mechanism
* that handles the <i>service discard problem</i> described
* in this utility's specification. This item allows each
* entity that uses this utility to define how long (in
* milliseconds) to wait for verification from the lookup
* service(s) that a discarded service is actually down
* before committing or un-committing a requested service
* discard. The current implementation of this utility
* defaults to waiting 10 minutes (twice the maximum lease
* duration granted by the Reggie implementation of the
* lookup service). Note that this item is used only by the
* caches (both internal and external) that are created by
* this utility, and not by the utility itself.
* </table>
*
* <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">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>discoveryManager</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.discovery.DiscoveryManagement}
*
* <tr valign="top"> <td>   <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>   <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. This item is used only by the service
* discovery manager, and not by any cache that is created.
* </table>
*
* <a name="eventLeasePreparer">
* <table summary="Describes the eventLeasePreparer configuration entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>eventLeasePreparer</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.security.ProxyPreparer}
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
* </code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description:
* <td> Preparer for the leases returned when a cache registers
* with the event mechanism of any of the discovered lookup
* services. This item is used only by the caches (both
* internal and external) that are created by this utility,
* and not by the utility itself.
* <p>
* Currently, no methods of the returned proxy are invoked by
* this utility.
* </table>
*
* <a name="eventListenerExporter">
* <table summary="Describes the eventListenerExporter configuration entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>eventListenerExporter</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.export.Exporter}
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code> new
* {@link net.jini.jeri.BasicJeriExporter#BasicJeriExporter(
* net.jini.jeri.ServerEndpoint,
* net.jini.jeri.InvocationLayerFactory,
* boolean,
* boolean) BasicJeriExporter}(
* {@link net.jini.jeri.tcp.TcpServerEndpoint#getInstance
* TcpServerEndpoint.getInstance}(0),<br>
*            
*            
* new {@link net.jini.jeri.BasicILFactory}(),<br>
*            
*            
* false, false)</code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description:
* <td> Exporter for the remote event listener that each cache
* supplies to the lookup services whose event mechanisms
* those caches register with. Note that for each cache that
* is created in this utility, a single, separate instance
* of this exporter will be retrieved and employed by that
* cache. Note also that the default exporter defined here
* will disable distributed garbage collection (DGC) for the
* server endpoint associated with the exported listener,
* and the listener backend (the "impl") will be strongly
* referenced. This means that the listener will not "go away"
* unintentionally. Additionally, that exporter also sets the
* <code>keepAlive</code> flag to <code>false</code> to allow
* the VM in which this utility runs to "go away" when
* desired; and not be kept alive simply because the listener
* is still exported.
* </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">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>leaseManager</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.lease.LeaseRenewalManager}
*
* <tr valign="top"> <td>   <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>   <th scope="row" align="right">
* Description:
* <td> The object used to manage any event leases returned
* to a cache that has registered with the event mechanism
* of the various discovered lookup services. This entry will
* be retrieved from the configuration only if no lease
* renewal manager is specified in the constructor. This item
* is used only by the caches (both internal and external)
* that are created by this utility, and not by the utility
* itself.
* </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">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1">
* <code>registrarPreparer</code></font>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.security.ProxyPreparer}
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>new {@link net.jini.security.BasicProxyPreparer}()
* </code>
*
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description:
* <td> Preparer for the proxies to the lookup services that are
* discovered and used by this utility. This item is used only
* by the service discovery manager, and not by any cache that
* is created.
* <p>
* The following methods of the proxy returned by this preparer are
* invoked by this utility:
* <ul>
* <li>{@link net.jini.core.lookup.ServiceRegistrar#lookup lookup}
* <li>{@link net.jini.core.lookup.ServiceRegistrar#notify notify}
* </ul>
*
* </table>
*
* <a name="sdmLogging">
* <p>
* <b><font size="+1">Logging</font></b>
* <p>
* </a>
*
* This implementation of <code>ServiceDiscoveryManager</code> uses the
* {@link Logger} named <code>net.jini.lookup.ServiceDiscoveryManager</code>
* to log information at the following logging levels: <p>
*
* <table border="1" cellpadding="5"
* summary="Describes the information logged by ServiceDiscoveryManager,
* and the levels at which that information is logged">
*
*
* <caption halign="center" valign="top">
* <b><code>net.jini.lookup.ServiceDiscoveryManager</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 any exception occurs while querying a lookup service, or upon
* applying a filter to the results of such a query
* </td>
* </tr>
* <tr>
* <td>{@link java.util.logging.Level#INFO INFO}</td>
* <td>
* when any exception occurs while attempting to register with the event
* mechanism of a lookup service, or while attempting to prepare the lease
* on the registration with that event mechanism
* </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#INFO INFO}</td>
* <td>
* when an <code>IllegalStateException</code> occurs while discarding
* a lookup service proxy after logging a failure that has occurred in
* one of the tasks executed by this utility
* </td>
* </tr>
* <tr>
* <td>{@link java.util.logging.Level#INFO INFO}</td>
* <td>upon failure of the lease renewal process</td>
* </tr>
* <tr>
* <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
* <td>
* when an exception occurs because a remote call to a lookup service
* has been interrupted as a result of the termination of a cache
* </td>
* </tr>
* <tr>
* <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td>
* <td>
* when a "gap" is encountered in an event sequence from a lookup service
* </td>
* </tr>
* <tr>
* <td>{@link java.util.logging.Level#FINER FINER}</td>
* <td>upon failure of the lease cancellation process</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 lookup cache is created</td>
* </tr>
* <tr>
* <td>{@link java.util.logging.Level#FINEST FINEST}</td>
* <td>whenever a lookup cache is terminated</td>
* </tr>
* <tr>
* <td>{@link java.util.logging.Level#FINEST FINEST}</td>
* <td>whenever a proxy is prepared</td>
* </tr>
* <tr>
* <td>{@link java.util.logging.Level#FINEST FINEST}</td>
* <td>
* when an exception (that is, <code>IllegalStateException</code>)
* occurs while unexporting a cache's remote event listener while the
* cache is being terminated
* </td>
* </tr>
* </table>
* <p>
* See the {@link com.sun.jini.logging.LogManager} class for one way to use
* the logging level {@link com.sun.jini.logging.Levels#HANDLED HANDLED} in
* standard logging configuration files.
* <p>
*
* @author Sun Microsystems, Inc.
*
* @see net.jini.discovery.DiscoveryManagement
* @see net.jini.lookup.LookupCache
* @see net.jini.lookup.ServiceDiscoveryListener
* @see net.jini.lookup.ServiceDiscoveryEvent
* @see net.jini.core.lookup.ServiceRegistrar
*/
public class ServiceDiscoveryManager {
/** Class for implementing register/lookup/notify/dropProxy/discard tasks*/
private static abstract class CacheTask implements TaskManager.Task {
protected ProxyReg reg;
protected long thisTaskSeqN;
public CacheTask(ProxyReg reg, long seqN) {
this.reg = reg;
this.thisTaskSeqN = seqN;
}//end constructor
/* check if the task is on a specific ProxyReg, return true if it is */
public boolean isFromProxy(ProxyReg reg) {
if(this.reg == null) return false;
return (this.reg).equals(reg);
}//end isFromProxy
/** Returns true if current instance must be run after task(s) in
* task manager queue.
* @param tasks the tasks to consider.
* @param size elements with index less than size are considered.
*/
public boolean runAfter(List tasks, int size) {
return false;
}//end runAfter
/** Returns the ProxyReg associated with this task (if any). */
public ProxyReg getProxyReg() {
return reg;
}//end ProxyReg
/** Returns the unique sequence number of this task. */
public long getSeqN() {
return thisTaskSeqN;
}//end getSeqN
public abstract void run();
}//end class ServiceDiscoveryManager.CacheTask
/** Abstract base class for controlling the order-of-execution of tasks
* corresponding to a particular serviceID associated with a particular
* lookup service.
*/
private static abstract class ServiceIdTask extends CacheTask {
protected ServiceID thisTaskSid;
ServiceIdTask(ServiceID srvcId, ProxyReg reg, long seqN) {
super(reg, seqN);
this.thisTaskSid = srvcId;
}//end constructor
/** Returns true if the current instance of this task must be run
* after at least one task in task manager queue.
*
* The criteria for determining what value to return is as follows:
*
* If there is at least one task in the given task list that is
* associated with the same serviceID as this task, and that task
* has a sequence number less than the sequence number of this task,
* then run this task *after* the task in the list (return true);
* otherwise run this task now (return false).
*
* @param tasks the tasks to consider.
* @param size elements with index less than size are considered.
*/
public boolean runAfter(List tasks, int size) {
for(int i=0; i<size; i++) {
TaskManager.Task t = (TaskManager.Task)tasks.get(i);
//Compare only instances of this task class
if( !(t instanceof ServiceIdTask) ) continue;
ServiceID otherTaskSid = ((ServiceIdTask)t).getServiceID();
if( thisTaskSid.equals(otherTaskSid) ) {
if(thisTaskSeqN > ((ServiceIdTask)t).getSeqN()) {
return true;//run this task after the other task
}//endif
}//endif
}//end loop
return false;//run this task now
}//end runAfter
/** Returns the ServiceID associated with this task. */
public ServiceID getServiceID() {
return thisTaskSid;
}//end getServiceID
}//end class ServiceIdTask
/** Class that defines the listener that will receive local events from
* the internal LookupCache used in the blocking versions of lookup().
*/
private final static class ServiceDiscoveryListenerImpl
implements ServiceDiscoveryListener
{
ArrayList items = new ArrayList(1);
public synchronized void serviceAdded(ServiceDiscoveryEvent event) {
items.add(event.getPostEventServiceItem());
this.notifyAll();
}
public void serviceRemoved(ServiceDiscoveryEvent event){ }
public void serviceChanged(ServiceDiscoveryEvent event){ }
public synchronized ServiceItem[] getServiceItem() {
ServiceItem[] r = new ServiceItem[items.size()];
items.toArray(r);
items.clear();
return r;
}
}//end class ServiceDiscoveryManager.ServiceDiscoveryListenerImpl
/**
* Data structure used to group together the lease and event sequence
* number. For each LookupCache, there is a HashMap that maps a ProxyReg
* to an EventReg.
*/
private final static class EventReg {
/* The Event source from the event registration */
Object source;
/* The Event ID */
public long eventID;
/* The current event sequence number for the Service template */
public long seqNo;
/* The Event notification lease */
public Lease lease;
public EventReg(Object source, long eventID, long seqNo, Lease lease) {
this.source = source;
this.eventID = eventID;
this.seqNo = seqNo;
this.lease = lease;
}
}//end class ServiceDiscoveryManager.EventReg
/**
* Used in the LookupCache. For each LookupCache, there is a HashMap that
* maps ServiceId to a ServiceItemReg. The ServiceItemReg class helps
* track where the ServiceItem comes from.
*/
private final static class ServiceItemReg {
/* Stores ServiceRegistrars that has the ServiceItem registered. */
private final ArrayList proxys = new ArrayList(1);
/* Flag that indicates that the ServiceItem has been discarded. */
private boolean bDiscarded = false;
/* The discovered service, prior to filtering. */
public ServiceItem item;
/* The discovered service, after filtering. */
public ServiceItem filteredItem;
/* Creates an instance of this class, and associates it with the given
* lookup service proxy.
*/
public ServiceItemReg(ServiceRegistrar proxy, ServiceItem item) {
addProxy(proxy);
this.item = item;
}
/* Adds the given proxy to the 'id-to-itemReg' map. This method is
* called from this class' constructor, LookupTask, NotifyEventTask,
* and ProxyRegDropTask.
*/
public void addProxy(ServiceRegistrar proxy) {
if(!proxys.contains(proxy)) proxys.add(proxy);
}
/* Removes the given proxy from the 'id-to-itemReg' map. This method
* is called from NotifyEventTask and ProxyRegDropTask.
*/
public void removeProxy(ServiceRegistrar proxy) {
int index = proxys.indexOf(proxy);
if(index != -1) proxys.remove(index);
}
/* Determines if the 'id-to-itemReg' map contains any mappings.
* This method is called from NotifyEventTask and ProxyRegDropTask.
*/
public boolean hasNoProxys() {
return proxys.isEmpty();
}
/* Marks the ServiceItem as discarded. */
public void setDiscarded(boolean b) {
bDiscarded = b;
}
/* Returns the flag indicating whether the ServiceItem is discarded. */
public boolean isDiscarded() {
return bDiscarded;
}
}//end class ServiceDiscoveryManager.ServiceItemReg
/** A wrapper class for a ServiceRegistrar. */
private final static class ProxyReg {
public ServiceRegistrar proxy;
public ProxyReg(ServiceRegistrar proxy) {
if(proxy == null) throw new IllegalArgumentException
("proxy cannot be null");
this.proxy = proxy;
}//end constructor
public boolean equals(Object obj) {
if (obj instanceof ProxyReg){
return proxy.equals(((ProxyReg)obj).proxy);
} else return false;
}//end equals
public int hashCode() {
return proxy.hashCode();
}//end hashCode
}//end class ServiceDiscoveryManager.ProxyReg
/** The Listener class for the LeaseRenewalManager. */
private final class LeaseListenerImpl implements LeaseListener {
private ServiceRegistrar proxy;
public LeaseListenerImpl(ServiceRegistrar proxy) {
this.proxy = proxy;
}
/* When lease renewal fails, we discard the proxy */
public void notify(LeaseRenewalEvent e) {
fail(e.getException(),proxy, this.getClass().getName(), "notify",
"failure occurred while renewing an event lease", false);
}
}//end class ServiceDiscoveryManager.LeaseListenerImpl
/** Internal implementation of the LookupCache interface. Instances of
* this class are used in the blocking versions of lookup() and are
* returned by createLookupCache.
*/
private final class LookupCacheImpl implements LookupCache {
/* RemoteEventListener class that is registered with the proxy to
* receive notifications from lookup services when any ServiceItem
* changes (NOMATCH_MATCH, MATCH_NOMATCH, MATCH_MATCH)
*/
private final class LookupListener implements RemoteEventListener,
ServerProxyTrust
{
public LookupListener() throws ExportException {
lookupListenerProxy =
(RemoteEventListener)lookupListenerExporter.export(this);
}//end constructor
public void notify(RemoteEvent evt) {
ServiceEvent theEvent = (ServiceEvent)evt;
notifyServiceMap( theEvent.getSource(),
theEvent.getID(),
theEvent.getSequenceNumber(),
theEvent.getServiceID(),
theEvent.getServiceItem(),
theEvent.getTransition() );
}//end notify
/** Returns a <code>TrustVerifier</code> which can be used to
* verify that a given proxy to this listener can be trusted.
*/
public TrustVerifier getProxyVerifier() {
return new BasicProxyTrustVerifier(lookupListenerProxy);
}//end getProxyVerifier
}//end class LookupCacheImpl.LookupListener
/** This task class, when executed, first registers to receive
* ServiceEvents from the given ServiceRegistrar. If the registration
* process succeeds (no RemoteExceptions), it then executes the
* LookupTask to query the given ServiceRegistrar for a "snapshot"
* of its current state with respect to services that match the
* given template.
*
* Note that the order of execution of the two tasks is important.
* That is, the LookupTask must be executed only after registration
* for events has completed. This is because when an entity registers
* with the event mechanism of a ServiceRegistrar, the entity will
* only receive notification of events that occur "in the future",
* after the registration is made. The entity will not receive events
* about changes to the state of the ServiceRegistrar that may have
* occurred before or during the registration process.
*
* Thus, if the order of these tasks were reversed and the LookupTask
* were to be executed prior to the RegisterListenerTask, then the
* possibility exists for the occurrence of a change in the
* ServiceRegistrar's state between the time the LookupTask retrieves
* a snapshot of that state, and the time the event registration
* process has completed, resulting in an incorrect view of the
* current state of the ServiceRegistrar.
*/
private final class RegisterListenerTask extends CacheTask {
public RegisterListenerTask(ProxyReg reg, long seqN) {
super(reg, seqN);
}
public void run() {
logger.finest("ServiceDiscoveryManager - RegisterListenerTask "
+"started");
long duration = getLeaseDuration();
if(duration < 0) return;
try {
EventReg eventReg = registerListener(reg.proxy,
tmpl,
lookupListenerProxy,
duration);
synchronized(serviceIdMap) {
/* Cancel the lease if the cache has been terminated */
if(bCacheTerminated) {
cancelLease(eventReg.lease);
} else {
eventRegMap.put(reg, eventReg);
}//endif
}//end sync(serviceIdMap)
/* Execute the LookupTask only if there were no problems */
(new LookupTask(reg, this.getSeqN())).run();
} catch (Exception e) {
boolean cacheTerminated;
synchronized(serviceIdMap) {
cacheTerminated = bCacheTerminated;
}//end sync
ServiceDiscoveryManager.this.fail
(e,reg.proxy,this.getClass().getName(),"run",
"Exception occurred while attempting to register "
+"with the lookup service event mechanism",
cacheTerminated);
}
logger.finest("ServiceDiscoveryManager - RegisterListenerTask "
+"completed");
}//end run
}//end class LookupCacheImpl.RegisterListenerTask
/** This class requests a "snapshot" of the given registrar's state.*/
private final class LookupTask extends CacheTask {
public LookupTask(ProxyReg reg, long seqN) {
super(reg, seqN);
}
public void run() {
logger.finest("ServiceDiscoveryManager - LookupTask started");
ServiceRegistrar proxy = reg.proxy;
ServiceMatches matches;
/* For the given lookup, get all services matching the tmpl */
try {
matches = proxy.lookup(tmpl, Integer.MAX_VALUE);
} catch (Exception e) {
boolean cacheTerminated;
synchronized(serviceIdMap) {
cacheTerminated = bCacheTerminated;
}//end sync
ServiceDiscoveryManager.this.fail
(e,proxy,this.getClass().getName(),"run",
"Exception occurred during call to lookup",
cacheTerminated);
return;
}
if(matches.items == null) {
throw new AssertionError("spec violation in queried "
+"lookup service: ServicesMatches instance "
+"returned by call to lookup() method contains "
+"null 'items' field");
}
synchronized(serviceIdMap) {
/* 1. Cleanup "orphaned" itemReg's. */
Iterator iter = (serviceIdMap.entrySet()).iterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
ServiceID srvcID = (ServiceID)e.getKey();
ServiceItem itemInSnapshot = findItem(srvcID,
matches.items);
if(itemInSnapshot != null) continue;//not an orphan
ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
UnmapProxyTask t = new UnmapProxyTask(reg,
itemReg,
srvcID,
taskSeqN++);
cacheTaskMgr.add(t);
}//end loop
/* 2. Handle "new" and "old" items from the given lookup */
for(int i=0; i<(matches.items).length; i++) {
/* Skip items with null service field (Bug 4378751) */
if( (matches.items[i]).service == null ) continue;
NewOldServiceTask t =
new NewOldServiceTask(reg,
matches.items[i],
false,
taskSeqN++);
cacheTaskMgr.add(t);
}//end loop
}//end sync(serviceIdMap)
logger.finest("ServiceDiscoveryManager - LookupTask "
+"completed");
}//end run
/** Returns true if the current instance of this task must be run
* after at least one task in the task manager queue.
*
* The criteria for determining what value to return:
*
* If the task list contains any RegisterListenerTasks,
* other LookupTasks, or NotifyEventTasks associated with
* this task's lookup service (ProxyReg), if those tasks
* were queued prior to this task (have lower sequence
* numbers), then run those tasks before this task (return
* true). Otherwise this task can be run immediately
* (return false).
*
* This method was added to address Bug ID 6291851.
*
* @param tasks the tasks to consider.
* @param size elements with index less than size are considered.
*/
public boolean runAfter(List tasks, int size) {
for(int i=0; i<size; i++) {
CacheTask t = (CacheTask)tasks.get(i);
if( t instanceof RegisterListenerTask
|| t instanceof LookupTask
|| t instanceof NotifyEventTask )
{
ProxyReg otherReg = t.getProxyReg();
if( reg.equals(otherReg) ) {
if(thisTaskSeqN > t.getSeqN()) return true;
}//endif
}//endif
}//end loop
return super.runAfter(tasks, size);
}//end runAfter
}//end class LookupCacheImpl.LookupTask
/** When the given registrar is discarded, this Task class is used to
* remove the registrar from the various maps maintained by this
* cache.
*/
private final class ProxyRegDropTask extends CacheTask {
public ProxyRegDropTask(ProxyReg reg, long seqN) {
super(reg, seqN);
}
public void run() {
logger.finest("ServiceDiscoveryManager - ProxyRegDropTask "
+"started");
synchronized(serviceIdMap) {
//lease has already been cancelled by removeProxyReg
if(eventRegMap.containsKey(reg)) {
eventRegMap.remove(reg);
}
}//end sync(serviceIdMap)
/* For each itemReg in the serviceIdMap, disassociate the
* lookup service referenced here from the itemReg; and
* if the itemReg then has no more lookup services associated
* with it, remove the itemReg from the map and send a
* service removed event.
*/
synchronized(serviceIdMap) {
Iterator iter = (serviceIdMap.entrySet()).iterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
ServiceID srvcID = (ServiceID)e.getKey();
ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
UnmapProxyTask t = new UnmapProxyTask(reg,
itemReg,
srvcID,
taskSeqN++);
cacheTaskMgr.add(t);
}//end loop
}//end sync(serviceIdMap)
logger.finest("ServiceDiscoveryManager - ProxyRegDropTask "
+"completed");
}//end run
}//end class LookupCacheImpl.ProxyRegDropTask
/** Task class used to asynchronously discard the given service. */
private final class DiscardServiceTask extends CacheTask {
private Object service;
public DiscardServiceTask(Object service) {
super(null, 0);
this.service = service;
}
public void run() {
logger.finest("ServiceDiscoveryManager - DiscardServiceTask "
+"started");
Iterator iter = getServiceIdMapEntrySetIterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
ServiceItem item;
synchronized(itemReg) {
item = itemReg.filteredItem;
}//end sync(itemReg)
if( (item.service).equals(service) ) {
ServiceID sid = (ServiceID)e.getKey();
removeServiceNotify(item);
serviceDiscardTimerTaskMgr.add
( new ServiceDiscardTimerTask(sid) );
return;
}//endif
}//end loop
logger.finest("ServiceDiscoveryManager - DiscardServiceTask "
+"completed");
}//end run
}//end class LookupCacheImpl.DiscardServiceTask
/** Task class used to asynchronously notify all registered service
* discovery listeners of serviceAdded/serviceRemoved/serviceChanged
* events.
*/
private final class NotifyEventTask extends ServiceIdTask {
private ServiceID sid;
private ServiceItem item;
private int transition;
public NotifyEventTask(ProxyReg reg,
ServiceID sid,
ServiceItem item,
int transition,
long seqN)
{
super(sid, reg, seqN);
this.sid = sid;
this.item = item;
this.transition = transition;
}//end constructor
public void run() {
logger.finest("ServiceDiscoveryManager - NotifyEventTask "
+"started");
/* Fix for Bug ID 4378751. The conditions described by that
* bug involve a ServiceItem (corresponding to a previously
* discovered service ID) having a null service field. A
* null service field is due to an UnmarshalException caused
* by a SecurityException that results from the lack of a
* connection permission for the lookup service codebase
* to the service's remote codebase. Skip this ServiceItem,
* otherwise an un-expected serviceRemoved event will result
* because the primary if-block will be unintentionally
* entered due to the null service field in the ServiceItem.
*/
if( (item != null) && (item.service == null) ) {
return;
}//endif
/* Handle the event by the transition type, and by whether
* the associated ServiceItem is an old, previously discovered
* item, or a newly discovered item.
*/
if(transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) {
handleMatchNoMatch(reg.proxy, sid, item);
} else {//(transition == NOMATCH_MATCH or MATCH_MATCH)
(new NewOldServiceTask(reg, item,
(transition == ServiceRegistrar.TRANSITION_MATCH_MATCH),
thisTaskSeqN)).run();
}//endif(transition)
logger.finest("ServiceDiscoveryManager - NotifyEventTask "
+"completed");
}//end run
/** Returns true if the current instance of this task must be run
* after at least one task in the task manager queue.
*
* The criteria for determining what value to return:
*
* If the task list contains any RegisterListenerTasks
* or LookupTasks associated with this task's lookup service
* (ProxyReg), and if those tasks were queued prior to this
* task (have lower sequence numbers), then run those tasks
* before this task (return true).
*
* Additionally, if the task list contains any other
* ServiceIdTasks associated with this task's service ID
* which were queued prior to this task, then run those
* tasks before this task.
*
* If the criteria outlined above is not satisfied, then this
* task can be run immediately (return false).
*
* This method was added to address Bug ID 6291851.
*
* @param tasks the tasks to consider.
* @param size elements with index less than size are considered.
*/
public boolean runAfter(List tasks, int size) {
for(int i=0; i<size; i++) {
CacheTask t = (CacheTask)tasks.get(i);
if( t instanceof RegisterListenerTask
|| t instanceof LookupTask )
{
ProxyReg otherReg = t.getProxyReg();
if( reg.equals(otherReg) ) {
if(thisTaskSeqN > t.getSeqN()) return true;
}//endif
}//endif
}//end loop
return super.runAfter(tasks, size);
}//end runAfter
}//end class LookupCacheImpl.NotifyEventTask
/** Task class used to determine whether or not to "commit" a service
* discard request, increasing the chances that the service will
* eventually be re-discovered. This task is also used to attempt
* a filter retry on an item in which the cache's filter initially
* returned indefinite.
*/
private final class ServiceDiscardTimerTask implements TaskManager.Task
{
private ServiceID serviceID;
public ServiceDiscardTimerTask(ServiceID serviceID) {
this.serviceID = serviceID;
}//end constructor
public void run(){
logger.finest("ServiceDiscoveryManager - "
+"ServiceDiscardTimerTask started");
/* Exit if this cache has already been terminated. */
synchronized(serviceIdMap) {
if(bCacheTerminated) return;
}//end sync(serviceIdMap)
/* Simply return if a MATCH_NOMATCH event arrived for this
* item prior to this task running and as a result, the item
* was removed from the map.
*/
synchronized(serviceIdMap) {
if(!serviceIdMap.containsKey(serviceID)) return;
}//end sync(serviceIdMap)
/* Default to twice the (common) 5 minute max lease duration */
long curDur = discardWait;
long endTime = curDur+System.currentTimeMillis();
synchronized(serviceDiscardMutex) {
/* Wait until interrupted or time expires */
while(curDur > 0) {
try {
serviceDiscardMutex.wait(curDur);
} catch(InterruptedException e){ }
/* Exit if this cache was terminated while waiting. */
synchronized(serviceIdMap) {
if(bCacheTerminated) return;
}//end sync(serviceIdMap)
/* Either the wait period has completed or has been
* interrupted. If the service ID is no longer in
* in the serviceIdMap, then it's assumed that a
* MATCH_NOMATCH event must have arrived which could be
* viewed as an indication that the service's lease
* expired, which then could be interpreted as meaning
* the service is actually down, and will be
* re-discovered when it comes back on line. In that
* case, exit the thread.
*/
synchronized(serviceIdMap) {
if(!serviceIdMap.containsKey(serviceID)) return;
}//end sync(serviceIdMap)
curDur = endTime-System.currentTimeMillis();
}//end loop
}//end sync
/* The thread was not interrupted, time expired.
*
* If the service ID is still contained in the serviceIdMap,
* then a MATCH_NOMATCH event did not arrive, which is
* interpreted here to mean that the service is still up.
* The service ID will still be in the map if one (or both)
* of the following is true:
* - the client discarded an unreachable service that never
* actually went down (so it's lease never expired, and
* a MATCH_NOMATCH event was never received)
* - upon applying the cache's filter to the service, the
* filter returned indefinite, and this task was queued
* to request that filtering be retried at a later time
*
* For the first case above, the service is "un-discarded" so
* the service will be available to the client again. For the
* second case, the filter is retried. If the service passes
* the filter, the service is "un-discarded"; otherwise, it is
* 'quietly' removed from the map (because a service removed
* event was already sent when the service was originally
* discarded.
*/
ServiceItemReg itemReg = null;
synchronized(serviceIdMap) {
itemReg = (ServiceItemReg)serviceIdMap.get(serviceID);
}//end sync(serviceIdMap)
if(itemReg != null) {
ServiceItem item = null;
ServiceItem filteredItem = null;
synchronized(itemReg) {
if(itemReg.filteredItem == null) {
item = new ServiceItem
( (itemReg.item).serviceID,
(itemReg.item).service,
(itemReg.item).attributeSets);
filteredItem = new ServiceItem
( (itemReg.item).serviceID,
(itemReg.item).service,
(itemReg.item).attributeSets);
}//endif
}//end sync(itemReg)
if(filteredItem != null) {//retry the filter
if( filterPassFail(filteredItem,filter) ) {
addFilteredItemToMap(item,filteredItem);
} else {//'quietly' remove the item
removeServiceIdMapSendNoEvent(serviceID);
return;
}//endif
}//endif
/* Either the filter was retried and passed, in which case,
* the filtered itemCopy was placed in the map; or the
* filter wasn't applied above (a non-null filteredItem
* field in the itemReg in the map means that the filter
* was applied at some previous time). In either case, the
* service can now be "un-discarded", and a notification
* that the service is now available can be sent.
*/
ServiceItem itemToSend;
synchronized(itemReg) {
itemReg.setDiscarded(false);
itemToSend = itemReg.filteredItem;
}//end sync(itemReg)
addServiceNotify(itemToSend);
}//endif
logger.finest("ServiceDiscoveryManager - "
+"ServiceDiscardTimerTask completed");
}//end run
/** Returns true if current instance must be run after task(s) in
* task manager queue.
* @param tasks the tasks to consider.
* @param size elements with index less than size are considered.
*/
public boolean runAfter(List tasks, int size) {
return false;
}//end runAfter
}//end class LookupCacheImpl.ServiceDiscardTimerTask
/** Task class used to asynchronously process the service state
* ("snapshot"), matching this cache's template, that was retrieved
* from the given lookup service.
*
* After retrieving the snapshot S, the LookupTask queues an instance
* of this task for each service referenced in S. This task determines
* if the given service is an already-discovered service (is currently
* in this cache's serviceIdMap), or is a new service. This task
* handles the service differently, depending on whether the service
* is a new or old.
*
* a. if the item is old, then this task will:
* - compare the given item from the snapshot to the UN-filtered
* item in given itemReg
* if(same version but attributes have changed)
* send changed event
* else if( version has changed )
* send removed event followed by added event
* else
* do nothing
* - apply the filter to the given item
* if(filter fails)
* send removed event
* else if(filter passes)
* set the filtered item in the itemReg in the map
* else if (filter is indefinite)
* discard item
* send removed event
* queue another filter attempt for later
* b. if the given item is newly discovered, then this task will:
* - create a new ServiceItemReg containing the given item
* - place the new itemReg in the serviceIdMap
* - apply the filter to the given item
* if(filter fails)
* remove the item from the map but
* send NO removed event
* else if(filter passes)
* send added event for the FILTERED item
* else if (filter is indefinite)
* discard item
* queue another filter attempt for later but
* send NO removed event
*/
private final class NewOldServiceTask extends ServiceIdTask {
private ServiceItem srvcItem;
private boolean matchMatchEvent;
public NewOldServiceTask(ProxyReg reg,
ServiceItem item,
boolean matchMatchEvent,
long seqN)
{
super(item.serviceID, reg, seqN);
this.srvcItem = item;
this.matchMatchEvent = matchMatchEvent;
}//end constructor
public void run() {
logger.finest("ServiceDiscoveryManager - NewOldServiceTask "
+"started");
ServiceItemReg itemReg;
synchronized(serviceIdMap) {
itemReg = (ServiceItemReg)serviceIdMap.get(thisTaskSid);
}//end sync(serviceIdMap)
if(itemReg != null) {//a. old, previously discovered item
itemMatchMatchChange(reg.proxy, srvcItem,
itemReg, matchMatchEvent);
} else {//(itemReg == null) ==> b. newly discovered item
synchronized(serviceIdMap) {
if( !eventRegMap.containsKey(reg) ) {
/* reg must have been discarded, simply return */
logger.finest("ServiceDiscoveryManager - "
+"NewOldServiceTask completed");
return;
}//endif
itemReg = new ServiceItemReg( reg.proxy, srvcItem );
serviceIdMap.put( thisTaskSid, itemReg );
}//end sync(serviceIdMap)
ServiceItem newFilteredItem =
filterMaybeDiscard(srvcItem,reg.proxy,false);
if(newFilteredItem != null) {
addServiceNotify(newFilteredItem);
}//endif
}//endif
logger.finest("ServiceDiscoveryManager - NewOldServiceTask "
+"completed");
}//end run
}//end class LookupCacheImpl.NewOldServiceTask
/** Task class used to asynchronously disassociate the given lookup
* service proxy from the given ServiceItemReg. This task is created
* and queued in both the LookupTask, and the ProxyRegDropTask.
*
* When the LookupTask determines that the service referenced by the
* given ServiceItemReg is an "orphan", the LookupTask queues an
* instance of this task. A service is an orphan if it is referenced
* in the serviceIdMap, but is no longer registered in any of the
* lookup service(s) to which it is mapped in the serviceIdMap.
* Note that the existence of orphans is possible when events from
* a particular lookup service are missed; that is, there is a "gap"
* in the event sequence numbers.
*
* When a previously discovered lookup service is discarded, the
* ProxyRegDropTask is initiated, and that task creates and queues
* an instance of this task for each mapping in this cache's
* serviceIdMap.
*
* This task removes the given lookup service proxy from the set
* associated with the service item referenced in the given
* ServiceItemReg, and determines whether that service is still
* associated with at least one lookup service. If the service is
* no longer associated with any other lookup service in the managed
* set of lookup services, the mapping that references the given
* ServiceItemReg is removed from the serviceIdMap, and a
* serviceRemoved event is sent.
*
* In this way, other tasks from this cache operating on the same
* service will not concurrently modify any state related to that
* service.
*/
private final class UnmapProxyTask extends ServiceIdTask {
private ServiceItemReg itemReg;
public UnmapProxyTask(ProxyReg reg,
ServiceItemReg itemReg,
ServiceID srvcId,
long seqN)
{
super(srvcId, reg, seqN);
this.itemReg = itemReg;
}//end constructor
public void run() {
logger.finest("ServiceDiscoveryManager - UnmapProxyTask "
+"started");
ServiceItem item = null;
synchronized(itemReg) {
itemReg.removeProxy(reg.proxy);//disassociate the LUS
if( itemReg.hasNoProxys() ) {//no more LUSs, remove map
item = itemReg.filteredItem;
}//endif
}//end sync(itemReg)
if(item != null) removeServiceIdMap(thisTaskSid,item);
logger.finest("ServiceDiscoveryManager - UnmapProxyTask "
+"completed");
}//end run
}//end class LookupCacheImpl.UnmapProxyTask
private final static int ITEM_ADDED = 0;
private final static int ITEM_REMOVED = 2;
private final static int ITEM_CHANGED = 3;
/* The listener that receives remote events from the lookup services */
private LookupListener lookupListener;
/* Exporter for the remote event listener (lookupListener) */
private Exporter lookupListenerExporter;
/* Proxy to the listener that receives remote events from lookups */
private RemoteEventListener lookupListenerProxy;
/** Task manager for the various tasks executed by this LookupCache */
private TaskManager cacheTaskMgr;
/* Flag that indicates if the LookupCache has been terminated. */
private boolean bCacheTerminated = false;
/* Contains the ServiceDiscoveryListener's that receive local events */
private final ArrayList sItemListeners = new ArrayList(1);
/* Map from ServiceID to ServiceItemReg */
private final HashMap serviceIdMap = new HashMap();
/* Map from ProxyReg to EventReg: (proxyReg, {source,id,seqNo,lease})*/
private final HashMap eventRegMap = new HashMap();
/* Template current cache instance should use for primary matching */
private ServiceTemplate tmpl;
/* Filter current cache instance should use for secondary matching */
private ServiceItemFilter filter = null;
/* Desired lease duration to request from lookups' event mechanisms */
private long leaseDuration;
/* Log the time when the cache gets created. This value is used to
* calculate the time when the cache should expire.
*/
private final long startTime = System.currentTimeMillis();
/** For tasks waiting on verification events after service discard */
private TaskManager serviceDiscardTimerTaskMgr;
/* Thread mutex used to interrupt all ServiceDiscardTimerTasks */
private Object serviceDiscardMutex = new Object();
/** Whenever a ServiceIdTask is created in this cache, it is assigned
* a unique sequence number to allow such tasks associated with the
* same ServiceID to be executed in the order in which they were
* queued in the TaskManager. This field contains the value of
* the sequence number assigned to the most recently created
* ServiceIdTask.
*/
private long taskSeqN = 0;
public LookupCacheImpl(ServiceTemplate tmpl,
ServiceItemFilter filter,
ServiceDiscoveryListener sListener,
long leaseDuration) throws RemoteException
{
this.tmpl = copyServiceTemplate(tmpl);
this.leaseDuration = leaseDuration;
this.filter = filter;
initCache();
lookupListener = new LookupListener();
synchronized(sItemListeners) {
if(sListener != null ) sItemListeners.add(sListener);
}//end sync(sItemListeners)
ArrayList set;
synchronized(proxyRegSet) {
set = (ArrayList)proxyRegSet.clone();
}//end sync(proxyRegSet)
for(int i=0; i<set.size(); i++) {
ProxyReg reg = (ProxyReg)set.get(i);
addProxyReg(reg);
}//end loop
}//end constructor
// This method's javadoc is inherited from an interface of this class
public void terminate() {
synchronized(serviceIdMap) {
if(bCacheTerminated) return;//allow for multiple terminations
bCacheTerminated = true;
}//end sync
synchronized(caches) {
int index = caches.indexOf(this);
if(index != -1) caches.remove(index);
}//end sync
/* Terminate all tasks: first, terminate this cache's TaskManager*/
terminateTaskMgr(cacheTaskMgr);
/* Terminate ServiceDiscardTimerTasks running for this cache */
synchronized(serviceDiscardMutex) {
terminateTaskMgr(serviceDiscardTimerTaskMgr);
}//end sync(serviceDiscardMutex)
/* Cancel all event registration leases held by this cache. */
synchronized(serviceIdMap) {
Set set = eventRegMap.entrySet();
Iterator iter = set.iterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
EventReg eReg = (EventReg)e.getValue();
cancelLease(eReg.lease);
}//end loop
}//end sync(serviceIdMap)
/* Un-export the remote listener for events from lookups. */
try {
lookupListenerExporter.unexport(true);
} catch(IllegalStateException e) {
logger.log(Level.FINEST,
"IllegalStateException occurred while unexporting "
+"the cache's remote event listener",
e);
}
logger.finest("ServiceDiscoveryManager - LookupCache terminated");
}//end LookupCacheImpl.terminate
// This method's javadoc is inherited from an interface of this class
public ServiceItem lookup(ServiceItemFilter myFilter) {
checkCacheTerminated();
ServiceItem[] ret = getServiceItems(myFilter);
if (ret.length == 0 ) return null;
int rand = Math.abs(random.nextInt()) % ret.length;
return ret[rand];
}//end LookupCacheImpl.lookup
// This method's javadoc is inherited from an interface of this class
public ServiceItem[] lookup(ServiceItemFilter myFilter,int maxMatches){
checkCacheTerminated();
if (maxMatches < 1)
throw new IllegalArgumentException("maxMatches must be > 0");
ArrayList items = new ArrayList(1);
ServiceItem[] sa = getServiceItems(myFilter);
int len = sa.length;
if (len == 0 ) return new ServiceItem[0];
int rand = Math.abs(random.nextInt()) % len;
for(int i=0; i<len; i++) {
items.add(sa[(i+rand) % len ]);
if(items.size() == maxMatches)
break;
}//end loop
ServiceItem[] ret = new ServiceItem[items.size()];
items.toArray(ret);
return ret;
}//end LookupCacheImpl.lookup
// This method's javadoc is inherited from an interface of this class
public void discard(Object serviceReference) {
checkCacheTerminated();
/* Loop through the serviceIdMap, looking for the itemReg that
* corresponds to given serviceReference. If such an itemReg
* exists, and it's not already discarded, then queue a task
* to discard the given serviceReference.
*/
boolean discardIt = false;
Iterator iter = getServiceIdMapEntrySetIterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
synchronized(itemReg) {
if((itemReg.filteredItem.service).equals(serviceReference))
{
if( itemReg.isDiscarded() ) return;//already discarded
itemReg.setDiscarded(true);
discardIt = true;
break;
}//endif
}//end sync(itemReg)
}//end loop
if(discardIt) {
CacheTask t = new DiscardServiceTask(serviceReference);
cacheTaskMgr.add(t);
}//endif
}//end LookupCacheImpl.discard
/* Returns the iterator of entry set in the copy of the ServiceIdMap */
private Iterator getServiceIdMapEntrySetIterator() {
HashMap serviceIdMapCopy;
synchronized(serviceIdMap) {
serviceIdMapCopy = (HashMap)serviceIdMap.clone();
}
Set set = serviceIdMapCopy.entrySet();
return set.iterator();
}//end LookupCacheImpl.getServiceIdMapEntrySetIterator
/** This method returns a <code>ServiceItem</code> array containing
* elements that satisfy the following conditions:
* - is referenced by one of the <code>itemReg</code> elements
* contained in the <code>serviceIdMap</code>
* - is not currently discarded
* - satisfies the given <code>ServiceItemFilter</code>
*
* Note that the <code>filter</code> parameter is a "2nd stage"
* filter. That is, for each <code>itemReg</code> element in the
* <code>serviceIdMap</code>, the "1st stage" filter corresponding
* to the current instance of <code>LookupCache</code> has already
* been applied to the <code>ServiceItem</code> referenced in
* that <code>itemReg</code>. The <code>ServiceItemFilter</code>
* applied here is supplied by the entity interacting with the cache,
* and provides a second filtering process. Thus, this method
* applies the given <code>filter</code> parameter to the
* <code>filteredItem</code> field (not the <code>item</code> field)
* of each non-discarded <code>itemReg</code> element in the
* <code>serviceIdMap</code>.
*
* This method returns all the instances of <code>ServiceItem</code>
* that pass the given <code>filter</code>; and it discards all the
* items that produce an indefinite result when that
* <code>filter</code> is applied.
*/
private ServiceItem[] getServiceItems(ServiceItemFilter filter2) {
ArrayList items = new ArrayList(1);
Iterator iter = getServiceIdMapEntrySetIterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
ServiceItemReg itemReg = (ServiceItemReg)e.getValue();
ServiceItem itemToFilter;
ServiceItem itemToDiscard;
synchronized(itemReg) {
if( (itemReg.isDiscarded())
|| (itemReg.filteredItem == null) ) continue;
/* Make a copy because the filter may change it to null */
itemToFilter = new ServiceItem
( (itemReg.filteredItem).serviceID,
(itemReg.filteredItem).service,
(itemReg.filteredItem).attributeSets );
}//end sync(itemReg)
Object serviceToDiscard = itemToFilter.service;
/* Apply the filter */
boolean pass = ( (filter2 == null)
|| (filter2.check(itemToFilter)) );
/* Handle filter fail - skip to next item */
if( !pass ) continue;
/* Handle filter pass - add item to return set */
if(itemToFilter.service != null) {
items.add(itemToFilter);
continue;
}//endif(pass)
/* Handle filter indefinite - discard the item */
discard(serviceToDiscard);
}//end loop
ServiceItem[] ret = new ServiceItem[items.size()];
items.toArray(ret);
return ret;
}//end LookupCacheImpl.getServiceItems
// This method's javadoc is inherited from an interface of this class
public void addListener(ServiceDiscoveryListener listener){
checkCacheTerminated();
if(listener == null) {
throw new NullPointerException("can't add null listener");
}
synchronized(sItemListeners) {
sItemListeners.add(listener);
}
ServiceItem[] items = getServiceItems(null);
for(int i=0; i<items.length; i++) {
addServiceNotify(items[i],listener);
}//end loop
}//end LookupCacheImpl.addListener
// This method's javadoc is inherited from an interface of this class
public void removeListener(ServiceDiscoveryListener listener){
checkCacheTerminated();
if( listener == null) return;
synchronized(sItemListeners) {
int index = sItemListeners.indexOf(listener);
if(index != -1) sItemListeners.remove(listener);
}
}//end LookupCacheImpl.removeListener
/** Add a new ProxyReg to the lookupCache. Called by the constructor
* and the DiscMgrListener's discovered() method.
* @param reg a ProxyReg to add.
*/
public void addProxyReg(ProxyReg reg) {
RegisterListenerTask treg;
synchronized(serviceIdMap) {
treg = new RegisterListenerTask(reg, taskSeqN++);
}//end sync(serviceIdMap)
cacheTaskMgr.add(treg);
}//end LookupCacheImpl.addProxyReg
/** Remove a ProxyReg from the lookupCache. Called by DiscMgrListener's
* discarded() method.
* @param reg a ProxyReg to remove.
*/
public void removeProxyReg(ProxyReg reg) {
ProxyRegDropTask t;
synchronized(serviceIdMap) {
//let the ProxyRegDropTask do the eventRegMap.remove
EventReg eReg = (EventReg)eventRegMap.get(reg);
if(eReg != null) {
try {
leaseRenewalMgr.remove(eReg.lease);
} catch(Exception e) {
logger.log(Level.FINER,
"exception occurred while removing an "
+"event registration lease", e);
}
}//endif
t = new ProxyRegDropTask(reg, taskSeqN++);
}//end sync(serviceIdMap)
removeUselessTask(reg);
cacheTaskMgr.add(t);
}//end LookupCacheImpl.removeProxyReg
/* Throws IllegalStateException if this lookup cache has been
* terminated
*/
private void checkCacheTerminated() {
checkTerminated();
synchronized(serviceIdMap) {
if(bCacheTerminated) {
throw new IllegalStateException
("this lookup cache was terminated");
}//endif
}//end sync(serviceIdMap)
}//end LookupCacheImpl.checkCacheTerminated
/** Called by the lookupListener's notify() method. Checks the event
* sequence number and, based on whether or not a "gap" is found in
* in the event sequence, creates and places on the queue, either a
* LookupTask (if a gap was found) or a NotifyTask.
*
* Recall that the Event specification states that if the sequence
* numbers of two successive events differ by only 1, then one can
* be assured that no events were missed. On the other hand, if
* the difference is greater than 1 (the sequence contains a "gap"),
* then one or more events may -- or may not -- have been missed.
* Thus, if a gap is found in the events, although it's possible that
* no events were missed, this method takes the conservative approach
* by assuming events were missed. When this method determines that
* an event may have been missed, it requests a current "snapshot"
* of the given ServiceRegistrar's state by queueing the execution
* of a LookupTask. Since this method can safely assume that no
* events have been missed if it finds no gaps in the event sequence,
* this method queues the notification of the entity by requesting
* the execution of a NotifyEventTask.
*
* Note that when a lookup service is discovered, this utility
* registers with that lookup service's event mechanism for service
* events related to the services of interest. Upon registering with
* the event mechanism, a data structure (of type EventReg)
* containing information about that registration is placed in a
* Map for later processing when events do arrive. If the timing is
* right, it is possible that a service event may arrive between the
* time the registration is made and the time the EventReg is stored
* in the map. Thus, this method may find that the eventRegMap does
* not contain an element corresponding to the event this method is
* currently processing. In that case, this method will do nothing.
* It will simply return so that the service referenced in the event
* can be discovered using the snapshot returned by the LookupTask
* that is ultimately queued by the RegisterListenerTask (whose
* listener registration caused this method to be invoked in the
* first place).
*/
private void notifyServiceMap(Object eventSource,
long eventID,
long seqNo,
ServiceID sid,
ServiceItem item,
int transition)
{
if(eventSource == null) return;
synchronized(serviceIdMap) {
/* Search eventRegMap for ProxyReg corresponding to event. */
ProxyReg reg = null;
EventReg eReg = null;
Set set = eventRegMap.entrySet();
Iterator iter = set.iterator();
while(iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
eReg = (EventReg)e.getValue();
if( eventSource.equals(eReg.source)
&& (eventID == eReg.eventID) )
{
reg = (ProxyReg)e.getKey();
break;
}//endif
}//end loop
if(reg == null) return;//event arrived before eventReg in map
/* Next, look for gaps in the event sequence. */
long prevSeqNo = eReg.seqNo;
eReg.seqNo = seqNo;
CacheTask t;
if(seqNo == (prevSeqNo+1)) {//no gap, handle current event
t = new NotifyEventTask
(reg, sid, item, transition, taskSeqN++);
} else {//gap in event sequence, request snapshot
t = new LookupTask(reg, taskSeqN++);
if( logger.isLoggable(Levels.HANDLED) ) {
String msg ="notifyServiceMap - GAP in event sequence "
+"[serviceRegistrar={0}], "
+"[serviceItem={1}, "
+"serviceID={2}], "
+"[eventSource={3}, "
+"eventID={4,number,#}, "
+"oldSeqN={5,number,#}, "
+"newSeqN={6,number,#}]";
Object[] params = new Object[] { reg.proxy,
item.service,
sid,
eventSource,
new Long(eventID),
new Long(prevSeqNo),
new Long(seqNo) };
logger.log(Levels.HANDLED, msg, params);
}//endif
}//endif
cacheTaskMgr.add(t);
}//end sync(serviceIdMap)
}//end LookupCacheImpl.notifyServiceMap
/** Removes from the cache's task manager, all pending tasks
* associated with the given ProxyReg. This method is called
* when the given ProxyReg has been discarded.
*/
private void removeUselessTask(ProxyReg reg) {
ArrayList pendingTasks = cacheTaskMgr.getPending();
for(int i=0;i<pendingTasks.size();i++) {
CacheTask t = (CacheTask)pendingTasks.get(i);
if(t.isFromProxy(reg)) cacheTaskMgr.remove(t);
}//end loop
}//end LookupCacheImpl.removeUselessTask
/** For the given TaskManager, this method removes all pending and
* active tasks.
*/
private void terminateTaskMgr(TaskManager taskMgr) {
synchronized(taskMgr) {
/* Remove all pending tasks */
ArrayList pendingTasks = taskMgr.getPending();
for(int i=0;i<pendingTasks.size();i++) {
taskMgr.remove((TaskManager.Task)pendingTasks.get(i));
}//end loop
/* Interrupt all active tasks, prepare the taskMgr for GC. */
taskMgr.terminate();
taskMgr = null;
}//end sync(taskMgr)
}//end LookupCacheImpl.terminateTaskMgr
/** Removes an entry from the serviceIdMap, and sends a notification.*/
private void removeServiceIdMap(ServiceID sid, ServiceItem item) {
removeServiceIdMapSendNoEvent(sid);
removeServiceNotify(item);
}//end LookupCacheImpl.removeServiceIdMap
/** Removes an entry in the serviceIdMap, but sends no notification. */
private void removeServiceIdMapSendNoEvent(ServiceID sid) {
synchronized(serviceIdMap) {
serviceIdMap.remove(sid);
}
}//end LookupCacheImpl.removeServiceIdMapSendNoEvent
/** Returns the element in the given items array having the given
* ServiceID.
*/
private ServiceItem findItem(ServiceID sid, ServiceItem[] items) {
if(items != null) {
for(int i=0; i<items.length; i++) {
if(items[i].serviceID.equals(sid) ) return items[i];
}//end loop
}//endif
return null;
}//end LookupCacheImpl.findItem
/** With respect to a given service (referenced by both the parameter
* newItem and the parameter itemReg), if either an event has been
* received from the given lookup service (referenced by the proxy
* parameter), or a snapshot of the given lookup service's state
* has been retrieved, this method determines whether the service's
* attributes have changed, or whether a new version of the service
* has been registered. After the appropriate determination has been
* made, this method applies the filter associated with the current
* cache and sends the appropriate local ServiceDiscoveryEvent(s).
*
* This method is called under the following conditions:
* - when a new lookup service is discovered, this method will
* be called for each previously discovered service
* - when a gap in the events from a previously discovered lookup
* service is discovered, this method will be called for each
* previously discovered service
* - when a MATCH_MATCH event is received, this method will
* be called for each previously discovered service
* - when a NOMATCH_MATCH event is received, this method will
* be called for each previously discovered service
* Note that this method is never called when a MATCH_NOMATCH event
* is received; such an event is always handled in NotifyEventTask.
*
* When this method is called, it may send one of the following events
* or combination of events:
* - a service changed event
* - a service removed event followed by a service added event
* - a service removed event
*
* A service removed event is sent when the service either fails the
* filter, or the filter produces an indefinite result; in which
* case, the service is also discarded.
*
* A service changed event is sent when the service passes the filter,
* and it is determined that the service's attributes have changed.
* In this case, the old and new service proxies are treated as the
* same if one of the following conditions is met:
* - this method was called because of the receipt of a
* MATCH_MATCH event
* - the old and new service proxies are byte-wise fully equal
* (Note that the lookup service specification guarantees that the
* proxies are the same when a MATCH_MATCH event is received.)
*
* A service removed event followed by a service added event is sent
* when the service passes the filter, and the conditions for which
* a service changed event would be considered are not met; that is,
* this method was not called because of the receipt of a MATCH_MATCH
* event; or the old and new service proxies are not byte-wise fully
* equal.
*
* The if-else-block contained in this method implements the logic
* just described. The parameter matchMatchEvent reflects the
* pertinent event state that causes this method to be called.
* That is, either a MATCH_MATCH event was received, or it wasn't,
* (and if it wasn't, then a full byte-wise comparison is performed
* to determine whether the proxies are still the same).
*
* To understand when the 'else' part of the if-else-block is
* executed, consider the following conditions:
* - there is more than one lookup service with which the service
* registers (ex. LUS-0 and LUS-1)
* - after the service registers with LUS-0, a NOMATCH_MATCH
* event is received and handled (so the service is now known
* to the cache)
* - before the service registers with LUS-1, the service is
* replaced with a new version
* - the NOMATCH_MATCH event resulting from the service's
* registration with LUS-1 is received BEFORE receiving the
* MATCH_NOMATCH/NOMATCH_MATCH event sequence that will
* ultimately result from the re-registration of that new
* version with LUS-0
* When the above conditions occur, the NOMATCH_MATCH event that
* resulted from the service's registration with LUS-1 will cause
* this method to be invoked and the proxies to be fully compared
* (because the event was not a MATCH_MATCH event); and since the old
* service proxy and the new service proxy will not be fully equal,
* the else part of the if-else-block will be executed.
*
* This method applies the filter only after the above comparisons
* and determinations have been completed.
*/
private void itemMatchMatchChange(ServiceRegistrar proxy,
ServiceItem newItem,
ServiceItemReg itemReg,
boolean matchMatchEvent )
{
/* Save the pre-event state. Update the post-event state after
* applying the filter.
*/
ServiceItem oldItem;
ServiceItem oldFilteredItem;
synchronized(itemReg) {
oldItem = itemReg.item;
oldFilteredItem = itemReg.filteredItem;
if(itemReg.isDiscarded()) {
itemReg.item = newItem;//capture changes for discard
itemReg.filteredItem = null;//so filter will be retried
return;
}//endif
itemReg.addProxy(proxy);
}//end sync(itemReg)
/* For an explanation of the logic of the following if-else-block,
* refer to the method description above.
*/
boolean attrsChanged = false;
boolean versionChanged = false;
if( matchMatchEvent || sameVersion(newItem,oldItem) ) {
/* Same version, determine if the attributes have changed.
* But first, replace the new service proxy with the old
* service proxy so the client always uses the old proxy
* (at least, until the version is changed).
*/
newItem.service = oldItem.service;
/* Now compare attributes */
attrsChanged = !LookupAttributes.equal(newItem.attributeSets,
oldItem.attributeSets);
if(!attrsChanged) return;//no change, no need to filter
} else {//(!matchMatchEvent && !same version) ==> re-registration
versionChanged = true;
}//endif
/* Now apply the filter, and send events if appropriate */
ServiceItem newFilteredItem = filterMaybeDiscard
(newItem, proxy, true);
if(newFilteredItem != null) {
/* Passed the filter, okay to send event(s). */
if(attrsChanged) changeServiceNotify(newFilteredItem,
oldFilteredItem);
if(versionChanged) {
removeServiceNotify(oldFilteredItem);
addServiceNotify(newFilteredItem);
}//endif
}//endif
}//end LookupCacheImpl.itemMatchMatchChange
/** Convenience method that performs a byte-wise comparison, including
* codebases, of the services referenced by the given service items,
* and returns the result. If the services cannot be compared, it is
* assumed that the versions are not the same, and <code>false</code>
* is returned.
*/
private boolean sameVersion(ServiceItem item0,ServiceItem item1) {
boolean fullyEqual = false;
try {
MarshalledInstance mi0 = new MarshalledInstance(item0.service);
MarshalledInstance mi1 = new MarshalledInstance(item1.service);
fullyEqual = mi0.fullyEquals(mi1);
} catch(IOException e) {
logger.log(Level.INFO, "failure marshalling old and new "
+"services for equality check", e);
}
return fullyEqual;
}//end LookupCacheImpl.sameVersion
/** Gets the remaining time left on the current cache's "lifespan". */
public long getLeaseDuration() {
if(leaseDuration == Long.MAX_VALUE) return Long.MAX_VALUE;
return leaseDuration + startTime - System.currentTimeMillis();
}//end LookupCacheImpl.getLeaseDuration
/** Sends a notification to all listeners when a ServiceItem has
* been added.
*/
private void addServiceNotify(ServiceItem item) {
serviceNotifyDo(null, item, ITEM_ADDED);
} //end LookupCacheImpl.addServiceNotify
/** Sends a notification to the given listener when a ServiceItem has
* been added.
*/
private void addServiceNotify(ServiceItem item,
ServiceDiscoveryListener srvcListener)
{
serviceNotifyDo(null, item, ITEM_ADDED, srvcListener);
} //end LookupCacheImpl.addServiceNotify
/** Sends a notification when a ServiceItem has been removed. */
private void removeServiceNotify(ServiceItem item) {
serviceNotifyDo(item, null, ITEM_REMOVED);
}//end LookupCacheImpl.removeServiceNotify
/** Sends a notification when a ServiceItem has been changed, but
* still matches.
*/
private void changeServiceNotify(ServiceItem newItem,
ServiceItem oldItem )
{
serviceNotifyDo(oldItem, newItem, ITEM_CHANGED);
}//end LookupCacheImpl.changeServiceNotify
/** Common code for performing service notification to all listeners.*/
private void serviceNotifyDo(ServiceItem oldItem,
ServiceItem item,
int action)
{
ArrayList notifies;
synchronized(sItemListeners) {
if(sItemListeners.isEmpty()) return;
notifies = (ArrayList)sItemListeners.clone();
}
Iterator iter = notifies.iterator();
while (iter.hasNext()) {
ServiceDiscoveryListener sl
= (ServiceDiscoveryListener)iter.next();
serviceNotifyDo(oldItem,item,action,sl);
}//end loop
}//end LookupCacheImpl.serviceNotifyDo
/** Common code for performing service notification to one listener. */
private void serviceNotifyDo(ServiceItem oldItem,
ServiceItem item,
int action,
ServiceDiscoveryListener sl)
{
ServiceDiscoveryEvent event = new ServiceDiscoveryEvent
(this, oldItem, item);
switch(action) {
case ITEM_ADDED: sl.serviceAdded(event);break;
case ITEM_REMOVED: sl.serviceRemoved(event);break;
case ITEM_CHANGED: sl.serviceChanged(event);break;
}//end switch(action)
}//end LookupCacheImpl.serviceNotifyDo
private void initCache() throws RemoteException {
/* Get the exporter for the remote event listener from the
* configuration.
*/
try {
Exporter defaultExporter =
new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
new BasicILFactory(),
false, false);
lookupListenerExporter =
(Exporter)thisConfig.getEntry(COMPONENT_NAME,
"eventListenerExporter",
Exporter.class,
defaultExporter );
} catch(ConfigurationException e) {// exception, use default
ExportException e1 = new ExportException
("Configuration exception while "
+"retrieving exporter for "
+"cache's remote event listener",
e);
throw e1;
}
/* Get a general-purpose task manager for this cache from the
* configuration. This task manager will be used to manage the
* various tasks executed by this instance of the lookup cache.
*/
try {
cacheTaskMgr = (TaskManager)thisConfig.getEntry
(COMPONENT_NAME,
"cacheTaskManager",
TaskManager.class);
} catch(ConfigurationException e) { /* use default */
cacheTaskMgr = new TaskManager(10,(15*1000),1.0f);
}
/* Get a special-purpose task manager for this cache from the
* configuration. That task manager will be used to manage the
* various instances of the special-purpose task, executed by
* this instance of the lookup cache, that waits on verification
* events after a previousy discovered service has been discarded.
*/
try {
serviceDiscardTimerTaskMgr
= (TaskManager)thisConfig.getEntry
(COMPONENT_NAME,
"discardTaskManager",
TaskManager.class);
} catch(ConfigurationException e) { /* use default */
serviceDiscardTimerTaskMgr = new TaskManager
(10,(15*1000),1.0f);
}
}//end LookupCacheImpl.initCache
/** Applies the first-stage <code>filter</code> associated with
* the current instance of <code>LookupCache</code> to the given
* <code>item</code> and returns the resulting filtered item if
* the <code>filter</code> is passed (or is <code>null</code>);
* otherwise, returns <code>null</code> and sends a service removed
* event if the <code>sendEvent</code> parameter is <code>true</code>.
* <p>
* This method is called only when the <code>item</code> to be
* filtered corresponds to an element that currently exists in
* the <code>serviceIdMap</code>.
* <p>
* As described in the <code>ServiceItemFilter</code> specification,
* when the <code>item</code> passes the <code>filter</code>, the
* <code>service</code> field of the <code>item</code> is replaced
* with the filtered form of the object previously contained in
* that field. In this case, the <code>filteredItem</code> field
* of the corresponding <code>ServiceItemReg</code> element of the
* <code>serviceIdMap</code> is set to this new filtered item.
* <p>
* If the <code>filter</code> returns <code>indefinite</code>,
* then that specification states that the <code>service</code>
* field is replaced with <code>null</code>. In this case, the
* <code>filteredItem</code> field of the corresponding
* <code>ServiceItemReg</code> element of the
* <code>serviceIdMap</code> is left unchanged.
*/
private ServiceItem filterMaybeDiscard(ServiceItem item,
ServiceRegistrar proxy,
boolean sendEvent)
{
if( (item == null) || (item.service == null) ) return null;
if(filter == null) {
addFilteredItemToMap(item,item);
return item;
}//endif
/* Make a copy to filter because the filter may modify it. */
ServiceItem filteredItem = new ServiceItem(item.serviceID,
item.service,
item.attributeSets);
boolean pass = filter.check(filteredItem);
/* Handle filter fail */
if(!pass) {
ServiceID srvcID = item.serviceID;
ServiceItemReg itemReg = null;
synchronized(serviceIdMap) {
itemReg = (ServiceItemReg)serviceIdMap.get(srvcID);
}//end sync(serviceIdMap)
if(itemReg != null) {
if(sendEvent) {
ServiceItem oldFilteredItem;
synchronized(itemReg) {
oldFilteredItem = itemReg.filteredItem;
}//end sync(itemReg)
removeServiceIdMap(srvcID, oldFilteredItem);
} else {
removeServiceIdMapSendNoEvent(srvcID);
}//endif
}//endif
return null;
}//endif(fail)
/* Handle filter pass */
if(filteredItem.service != null) {
addFilteredItemToMap(item,filteredItem);
return filteredItem;
}//endif(pass)
/* Handle filter indefinite */
discardRetryLater(item, proxy, sendEvent);
return null;
}//end LookupCacheImpl.filterMaybeDiscard
/** Convenience method called by <code>filterMaybeDiscard</code>
* and <code>ServiceDiscardTimerTask.run</code> that finds
* the <code>ServiceItemReg</code> element in the
* <code>serviceIdMap</code> that corresponds to the given
* <code>ServiceItem</code> and, if such an element is found,
* replaces the <code>item</code> field of that element with
* the given <code>item</code> parameter; and sets the
* <code>filteredItem</code> field of that element to the value
* contained in the <code>filteredItem</code> parameter.
*/
private void addFilteredItemToMap(ServiceItem item,
ServiceItem filteredItem)
{
ServiceItemReg itemReg = null;
synchronized(serviceIdMap) {
itemReg = (ServiceItemReg)serviceIdMap.get(item.serviceID);
}//end sync(serviceIdMap)
if(itemReg == null) return;
synchronized(itemReg) {
itemReg.item = item;
itemReg.filteredItem = filteredItem;
}//end sync(itemReg)
}//end LookupCacheImpl.addFilteredItemToMap
/** Convenience method called by <code>filterMaybeDiscard</code>
* that finds in the <code>serviceIdMap</code>, the
* <code>ServiceItemReg</code> element corresponding to the
* given <code>ServiceItem</code>, sets a service removed event,
* and queues a <code>ServiceDiscardTimerTask</code> to retry the
* filter at a later time. If the <code>serviceIdMap</code> does not
* contain a <code>ServiceItemReg</code> corresponding to the
* given <code>ServiceItem</code>, then this method simply returns.
*/
private void discardRetryLater(ServiceItem item,
ServiceRegistrar proxy,
boolean sendEvent) {
ServiceItemReg itemReg = null;
synchronized(serviceIdMap) {
itemReg = (ServiceItemReg)serviceIdMap.get(item.serviceID);
}//end sync(serviceIdMap)
if(itemReg == null) return;
ServiceItem oldFilteredItem;
synchronized(itemReg) {
oldFilteredItem = itemReg.filteredItem;
/* If there's been any change in what is being discarded for
* filter retry, then update the item field in the map to
* capture that change; and set the filteredItem field to
* to null to guarantee that the filter is re-applied to
* that changed item.
*/
if(itemReg.item != item) {
itemReg.item = item;
itemReg.filteredItem = null;
}//endif
itemReg.setDiscarded(true);
}//end sync(itemReg)
if(sendEvent) removeServiceNotify(oldFilteredItem);
serviceDiscardTimerTaskMgr.add
( new ServiceDiscardTimerTask(item.serviceID) );
}//end LookupCacheImpl.discardRetryLater
/** Convenience method called by <code>NotifyEventTask.run</code> (only
* when a TRANSITION_MATCH_NOMATCH event is received) that removes
* the given <code>item</code> from the <code>serviceIdMap</code>
* and wakes up the <code>ServiceDiscardTimerTask</code> if the given
* <code>item</code> is discarded; otherwise, sends a removed event.
*/
private void handleMatchNoMatch(ServiceRegistrar proxy,
ServiceID srvcID,
ServiceItem item)
{
ServiceItemReg itemReg = null;
synchronized(serviceIdMap) {
itemReg = (ServiceItemReg)serviceIdMap.get(srvcID);
}//end sync(serviceIdMap)
if(itemReg != null) {
boolean itemRegHasNoProxys;
boolean itemRegIsDiscarded;
ServiceItem filteredItem;
synchronized(itemReg) {
itemReg.removeProxy(proxy);
itemRegHasNoProxys = itemReg.hasNoProxys();
itemRegIsDiscarded = itemReg.isDiscarded();
filteredItem = itemReg.filteredItem;
}//end sync(itemReg)
if(itemRegHasNoProxys) {
if(itemRegIsDiscarded) {
/* Remove item from map and wake up the discard task */
removeServiceIdMapSendNoEvent(srvcID);
synchronized(serviceDiscardMutex) {
serviceDiscardMutex.notifyAll();
}//end sync
} else {//remove item from map and send removed event
removeServiceIdMap(srvcID, filteredItem);
}//endif
}//endif
}//endif
}//end LookupCacheImpl.handleMatchNoMatch
}//end class ServiceDiscoveryManager.LookupCacheImpl
/* Name of this component; used in config entry retrieval and the logger.*/
private static final String COMPONENT_NAME
= "net.jini.lookup.ServiceDiscoveryManager";
/* Logger used by this utility. */
private static final Logger logger = Logger.getLogger(COMPONENT_NAME);
/* The discovery manager to use (passed in, or create one). */
private DiscoveryManagement discMgr;
/* Indicates whether the discovery manager was created internally or not */
private boolean discMgrInternal = false;
/* The listener added to discMgr that receives DiscoveryEvents */
private DiscMgrListener discMgrListener = new DiscMgrListener();
/* The LeaseRenewalManager to use (passed in, or create one). */
private LeaseRenewalManager leaseRenewalMgr;
/* Contains all of the discovered lookup services (ServiceRegistrar). */
private final ArrayList proxyRegSet = new ArrayList(1);
/* Contains all of the DiscoveryListener's employed in lookup discovery. */
private final ArrayList listeners = new ArrayList(1);
/* Random number generator for use in lookup. */
private final Random random = new Random();
/* Contains all of the instances of LookupCache that are requested. */
private final ArrayList caches = new ArrayList(1);
/* Flag to indicate if the ServiceDiscoveryManager has been terminated. */
private boolean bTerminated = false;
/* Object used to obtain the configuration items for this utility. */
private Configuration thisConfig;
/* 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 leases returned to this utility when
* it registers with the event mechanism of any of the discovered lookup
* services.
*/
private ProxyPreparer eventLeasePreparer;
/* Wait value used when handling the "service discard problem". */
private long discardWait = 2*(5*60*1000);
/* Listener class for lookup service discovery notification. */
private class DiscMgrListener implements DiscoveryListener {
/* New or previously discarded proxy has been discovered. */
public void discovered(DiscoveryEvent e) {
ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
ArrayList newProxys = new ArrayList(1);
ArrayList notifies = null;
for(int i=0; i<proxys.length; i++) {
/* Prepare each lookup service proxy before using it. */
try {
proxys[i]
= (ServiceRegistrar)registrarPreparer.prepareProxy
(proxys[i]);
logger.log(Level.FINEST, "ServiceDiscoveryManager - "
+"discovered lookup service proxy prepared: {0}",
proxys[i]);
} catch(Exception e1) {
logger.log(Level.INFO,
"failure preparing discovered ServiceRegistrar "
+"proxy, discarding the proxy",
e1);
discard(proxys[i]);
continue;
}
ProxyReg reg = new ProxyReg(proxys[i]);
synchronized(proxyRegSet) {
proxyRegSet.add(reg);
newProxys.add(reg);
}//end sync(proxyRegSet)
}//end loop
synchronized(listeners) {
if(!listeners.isEmpty())
notifies = (ArrayList)listeners.clone();
}//end sync(listeners)
Iterator iter = newProxys.iterator();
while(iter.hasNext()) {
ProxyReg reg = (ProxyReg)iter.next();
cacheAddProxy(reg);
if(notifies != null) listenerDiscovered(reg.proxy, notifies);
}//end loop
}//end DiscMgrListener.discovered
/* Previously discovered proxy has been discarded. */
public void discarded(DiscoveryEvent e) {
ServiceRegistrar[] proxys = (ServiceRegistrar[])e.getRegistrars();
ArrayList notifies;
ArrayList drops = new ArrayList(1);
synchronized(proxyRegSet) {
for(int i=0; i<proxys.length; i++) {
ProxyReg reg = findReg(proxys[i]);
if(reg != null ) { // this check can be removed.
proxyRegSet.remove(proxyRegSet.indexOf(reg));
drops.add(reg);
} else {
throw new RuntimeException("discard error");
}//endif
}//end loop
}//end sync(proxyRegSet)
Iterator iter = drops.iterator();
while(iter.hasNext()) {
dropProxy((ProxyReg)iter.next());
}//end loop
synchronized(listeners) {
if(listeners.isEmpty()) return;
notifies = (ArrayList)listeners.clone();
}//end sync(listeners)
listenerDropped(drops, notifies);
}//end DiscMgrListener.discarded
}//end class ServiceDiscoveryManager.DiscMgrListener
/** Adds the given proxy to all the caches maintained by the SDM. */
private void cacheAddProxy(ProxyReg reg) {
synchronized(caches) {
Iterator iter = caches.iterator();
while (iter.hasNext()) {
LookupCacheImpl cache = (LookupCacheImpl)iter.next();
cache.addProxyReg(reg);
}//end loop
}
}//end cacheAddProxy
/** Removes the given proxy from all the caches maintained by the SDM. */
private void dropProxy(ProxyReg reg ) {
synchronized(caches) {
Iterator iter = caches.iterator();
while (iter.hasNext()) {
LookupCacheImpl cache= (LookupCacheImpl)iter.next();
cache.removeProxyReg(reg);
}//end loop
}
}//end dropProxy
/**
* Constructs an instance of <code>ServiceDiscoveryManager</code> which
* will, on behalf of the entity that constructs this class, discover and
* manage a set of lookup services, as well as discover and manage sets
* of services registered with those lookup services. The entity indicates
* which lookup services to discover and manage through the parameters
* input to this constructor.
* <p>
* As stated in the class description, this class has three usage patterns:
* <p>
* <ul>
* <li> the entity uses a {@link net.jini.lookup.LookupCache
* LookupCache} to locally store and manage discovered services
* so that those services can be accessed quickly
* <li> the entity registers with the event mechanism provided by a
* {@link net.jini.lookup.LookupCache LookupCache} to be notified
* when services of interest are discovered
* <li> the entity uses the <code>ServiceDiscoveryManager</code> to
* perform remote queries of the lookup services, employing richer
* semantics than that provided through the standard
* {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar}
* interface
* </ul>
* <p>
* Although the first two usage patterns emphasize the use of a cache
* object, that cache is acquired only through an instance of the
* <code>ServiceDiscoveryManager</code> class.
* <p>
* It is important to note that some of the methods of this class
* ({@link net.jini.lookup.ServiceDiscoveryManager#createLookupCache
* createLookupCache} and the <i>blocking</i> versions of
* {@link net.jini.lookup.ServiceDiscoveryManager#lookup lookup} to
* be exact) can throw a {@link java.rmi.RemoteException} when invoked.
* This is because each of these methods may attempt to register with
* the event mechanism of at least one lookup service, a process that
* requires a remote object (a listener) to be exported to the lookup
* service(s). Both the process of registering with a lookup service's
* event mechanism and the process of exporting a remote object are
* processes that can result in a {@link java.rmi.RemoteException}.
* <p>
* In order to facilitate the exportation of the remote listener
* just described, the <code>ServiceDiscoveryManager</code> class
* instantiates an inner class that implements the
* {@link net.jini.core.event.RemoteEventListener RemoteEventListener}
* interface. Although this class defines, instantiates, and exports this
* remote listener, <i>it is the entity's responsibility</i> to provide a
* mechanism for any lookup service to acquire the proxy to the exported
* listener. One way to do this is to configure this utility to export
* the listener using the Jini Extensible Remote Invocation (Jini ERI)
* communication framework. When the listener is exported to use Jini ERI,
* and no proxy customizations (such as a custom invocation handler or
* transport endpoint) are used, no other action is necessary to make the
* proxy to the listener available to the lookup service(s) with which
* that listener is registered.
* <p>
* The <a href="#eventListenerExporter">default exporter</a> for this
* utility will export the remote event listener under Jini ERI,
* specifying that the port and object ID with which the listener is
* to be exported should be chosen by the Jini ERI framework, not the
* deployer.
* <p>
* If it is required that the remote event listener be exported under
* JRMP instead of Jini ERI, then the entity that employs this utility
* must specify this in its configuration. For example, the entity's
* configuration would need to contain something like the following:
* <p>
* <blockquote>
* <pre>
* import net.jini.jrmp.JrmpExporter;
*
* application.configuration.component.name {
* .......
* .......
* // configuration items specific to the application
* .......
* .......
* }//end application.configuration.component.name
*
* net.jini.lookup.ServiceDiscoveryManager {
*
* serverExporter = new JrmpExporter();
*
* }//end net.jini.lookup.ServiceDiscoveryManager
* </pre>
* </blockquote>
* <p>
* It is important to note that when the remote event listener is exported
* under JRMP, unlike Jini ERI, the JRMP remote communication framework
* does <b><i>not</i></b> provide a mechanism that automatically makes
* the listener proxy available to the lookup service(s) with which the
* listener is registered; the deployer of the entity, or the entity
* itself, must provide such a mechanism.
* <p>
* When exported under JRMP, one of the more common mechanisms for making
* the listener proxy available to the lookup service(s) with which the
* listener is registered consists of the following:
* <p>
* <ul><li> store the necessary class files in a JAR file
* <li> make the class files in the JAR file <i>preferred</i>
* (see {@link net.jini.loader.pref} for details)
* <li> run an HTTP server to serve up the JAR file to any requesting
* lookup service
* <li> advertise the location of that JAR file by setting the
* <code>java.rmi.server.codebase</code> property of the entity
* to "point" at the JAR file
* </ul>
* <p>
* For example, suppose an application consists of an entity that intends
* to use the <code>ServiceDiscoveryManager</code> will run on a host named
* <b><i>myHost</i></b>. And suppose that the <i>down-loadable</i> JAR
* file named <b><i>sdm-dl.jar</i></b> that is provided in the
* distribution is located in the directory <b><i>/files/jini/lib</i></b>,
* and will be served by an HTTP server listening on port
* <b><i>8082</i></b>. If the application is run with its codebase
* property set to
* <code>-Djava.rmi.server.codebase="http://myHost:8082/sdm-dl.jar"</code>,
* the lookup service(s) should then be able to access the remote listener
* exported under JRMP by the <code>ServiceDiscoveryManager</code> on
* behalf of the entity.
* <p>
* If a mechanism for lookup services to access the remote listener
* exported by the <code>ServiceDiscoveryManager</code> is not provided
* (either by the remote communication framework itself, or by some other
* means), the remote methods of the <code>ServiceDiscoveryManager</code>
* - the methods involved in the two most important usage patterns of
* that utility - will be of no use.
* <p>
* This constructor takes two arguments: an object that implements the
* <code>DiscoveryManagement</code> interface and a reference to a
* <code>LeaseRenewalManager</code> object. The constructor throws an
* <code>IOException</code> because construction of a
* <code>ServiceDiscoveryManager</code> may initiate the multicast
* discovery process, a process that can throw an
* <code>IOException</code>.
*
* @param discoveryMgr the <code>DiscoveryManagement</code>
* implementation through which notifications
* that indicate a lookup service has been
* discovered or discarded will be received.
* If the value of the argument is <code>null</code>,
* then an instance of the
* <code>LookupDiscoveryManager</code> utility
* class will be constructed to listen for events
* announcing the discovery of only those lookup
* services that are members of the public group.
*
* @param leaseMgr the <code>LeaseRenewalManager</code> to use. A
* value of <code>null</code> may be passed as the
* <code>LeaseRenewalManager</code> argument. If
* the value of the argument is <code>null</code>,
* an instance of the
* <code>LeaseRenewalManager</code> class will be
* created, initially managing no
* <code>Lease</code> objects.
*
* @throws IOException because construction of a
* <code>ServiceDiscoveryManager</code> may initiate
* the multicast discovery process which can throw
* an <code>IOException</code>.
*
* @see net.jini.discovery.DiscoveryManagement
* @see net.jini.core.event.RemoteEventListener
* @see net.jini.core.lookup.ServiceRegistrar
*/
public ServiceDiscoveryManager(DiscoveryManagement discoveryMgr,
LeaseRenewalManager leaseMgr)
throws IOException
{
try {
init(discoveryMgr, leaseMgr, EmptyConfiguration.INSTANCE);
} catch(ConfigurationException e) { /* swallow this exception */ }
}//end constructor
/**
* Constructs an instance of this class, which is configured using the
* items retrieved through the given <code>Configuration</code>, that
* will, on behalf of the entity that constructs this class, discover and
* manage a set of lookup services, as well as discover and manage sets
* of services registered with those lookup services. Through the
* parameters input to this constructor, the client of this utility
* indicates which lookup services to discover and manage, and how it
* wants the utility additionally configured.
* <p>
* For a more details, refer to the description of the alternate
* constructor of this class.
* <p>
* This constructor takes three arguments: an object that implements the
* <code>DiscoveryManagement</code> interface, a reference to an instance
* of the <code>LeaseRenewalManager</code> class, and a
* <code>Configuration</code> object. The constructor throws an
* <code>IOException</code> because construction of a
* <code>ServiceDiscoveryManager</code> may initiate the multicast
* discovery process, a process that can throw an
* <code>IOException</code>. The constructor also throws a
* <code>ConfigurationException</code> when an exception occurs while
* retrieving an item from the given <code>Configuration</code>
*
* @param discoveryMgr the <code>DiscoveryManagement</code>
* implementation through which notifications
* that indicate a lookup service has been
* discovered or discarded will be received.
* If the value of the argument is <code>null</code>,
* then an instance of the
* <code>LookupDiscoveryManager</code> utility
* class will be constructed to listen for events
* announcing the discovery of only those lookup
* services that are members of the public group.
*
* @param leaseMgr the <code>LeaseRenewalManager</code> to use. A
* value of <code>null</code> may be passed as the
* <code>LeaseRenewalManager</code> argument. If
* the value of the argument is <code>null</code>,
* an instance of the
* <code>LeaseRenewalManager</code> class will be
* created, initially managing no
* <code>Lease</code> objects.
*
* @throws IOException because construction of a
* <code>ServiceDiscoveryManager</code> may initiate
* the multicast discovery process which can throw
* an <code>IOException</code>.
*
* @throws net.jini.config.ConfigurationException indicates
* an exception occurred while retrieving an item from the given
* <code>Configuration</code>
*
* @throws java.lang.NullPointerException if <code>null</code> is input
* for the configuration
*
* @see net.jini.discovery.DiscoveryManagement
* @see net.jini.core.event.RemoteEventListener
* @see net.jini.core.lookup.ServiceRegistrar
* @see net.jini.config.Configuration
* @see net.jini.config.ConfigurationException
*/
public ServiceDiscoveryManager(DiscoveryManagement discoveryMgr,
LeaseRenewalManager leaseMgr,
Configuration config)
throws IOException,
ConfigurationException
{
init(discoveryMgr, leaseMgr, config);
}//end constructor
/** Sends discarded event to each listener waiting for discarded lookups.*/
private void listenerDropped(ArrayList drops, ArrayList notifies) {
ServiceRegistrar[] proxys = new ServiceRegistrar[drops.size()];
drops.toArray(proxys);
listenerDropped(proxys, notifies);
}//end listenerDropped
/** Sends discarded event to each listener waiting for discarded lookups.*/
private void listenerDropped(ServiceRegistrar[] proxys,ArrayList notifies){
Iterator iter = notifies.iterator();
while (iter.hasNext()) {
DiscoveryEvent evt = new DiscoveryEvent
( this,
(ServiceRegistrar[])proxys.clone() );
((DiscoveryListener)iter.next()).discarded(evt);
}//end loop
}//end listenerDropped
/** Sends discovered event to each listener listening for new lookups. */
private void listenerDiscovered(ServiceRegistrar proxy,ArrayList notifies){
Iterator iter = notifies.iterator();
while (iter.hasNext()) {
DiscoveryEvent evt = new DiscoveryEvent
( this,
new ServiceRegistrar[]{proxy} );
((DiscoveryListener)iter.next()).discovered(evt);
}//end loop
}//end listenerDiscovered
/** Returns array of ServiceRegistrar created from the proxyRegSet */
private ServiceRegistrar[] buildServiceRegistrar() {
int k = 0;
ServiceRegistrar[] proxys = new ServiceRegistrar[proxyRegSet.size()];
Iterator iter = proxyRegSet.iterator();
while(iter.hasNext()) {
ProxyReg reg = (ProxyReg)iter.next();
proxys[k++] = reg.proxy;
}//end loop
return proxys;
}//end buildServiceRegistrar
/**
* Queries each available lookup service in the set of lookup services
* managed by the <code>ServiceDiscoveryManager</code> (the <i>managed
* set</i>) for a service reference that matches criteria defined by the
* entity that invokes this method. The semantics of this method are
* similar to the semantics of the <code>lookup</code> method provided
* by the <code>ServiceRegistrar</code> interface; employing the same
* template-matching scheme. Additionally, this method allows any entity
* to supply an object referred to as a <i>filter</i>. Such an object is
* a non-remote object that defines additional matching criteria that the
* <code>ServiceDiscoveryManager</code> applies when searching for the
* entity's services of interest. This filtering facility is particularly
* useful to entities that wish to extend the capabilities of standard
* template-matching.
* <p>
* Entities typically employ this method when they need infrequent access
* to services, and when the cost of making remote queries is outweighed
* by the overhead of maintaining a local cache (for example, because of
* resource limitations).
* <p>
* This version of <code>lookup</code> returns a <i>single</i> instance
* of <code>ServiceItem</code> corresponding to one of possibly many
* service references that satisfy the matching criteria. If multiple
* services matching the input criteria happen to exist, it is arbitrary
* as to which reference is actually returned. It is for this reason that
* entities that invoke this method typically care only that <i>a</i>
* service is returned, not <i>which</i> service.
* <p>
* Note that, unlike other versions of <code>lookup</code> provided
* by the <code>ServiceDiscoveryManager</code>, this version does not
* <i>block</i>. That is, this version will return immediately upon
* failure (or success) to find a service matching the input criteria.
*
* It is important to understand this characteristic because there is
* a common usage scenario that can cause confusion when this version
* of <code>lookup</code> is used but fails to discover the expected
* service of interest. Suppose an entity creates a service discovery
* manager and then immediately calls this version of <code>lookup</code>,
* which simply queries the currently discovered lookup services
* for the service of interest. If the discovery manager employed by
* the service discovery manager has not yet disovered any lookup
* services (thus, there are no lookup services to query) the method
* will immediately return a value of <code>null</code>. This can be
* confusing when one verifies that such a service of interest has
* indeed been started and registered with the existing lookup
* service(s). To address this issue, one of the blocking versions
* of <code>lookup</code> could be used instead of this version, or
* the entity could simply wait until the discovery manager has been
* given enough time to complete its own (lookup) discovery processing.
*
* @param tmpl an instance of <code>ServiceTemplate</code> corresponding
* to the object to use for template-matching when searching
* for desired services. If <code>null</code> is input to
* this parameter, this method will use a <i>wildcarded</i>
* template (will match all services) when performing
* template-matching. Note that the effects of modifying
* contents of this parameter before this method returns
* are unpredictable and undefined.
* @param filter an instance of <code>ServiceItemFilter</code> containing
* matching criteria that should be applied in addition to
* the template-matching employed when searching for desired
* services. If <code>null</code> is input to this parameter,
* then only template-matching will be employed to find the
* desired services.
*
* @return a single instance of <code>ServiceItem</code> corresponding to
* a reference to a service that matches the criteria represented
* in the input parameters; or <code>null</code> if no matching
* service can be found. Note that if multiple services matching
* the input criteria exist, it is arbitrary as to which reference
* is returned.
*
* @see net.jini.core.lookup.ServiceRegistrar#lookup
* @see net.jini.core.lookup.ServiceTemplate
* @see net.jini.lookup.ServiceItemFilter
*/
public ServiceItem lookup(ServiceTemplate tmpl, ServiceItemFilter filter) {
checkTerminated();
ServiceRegistrar[] proxys;
synchronized(proxyRegSet) {
proxys = buildServiceRegistrar();
}
int len = proxys.length;
if(len == 0 ) return null;
int rand = Math.abs(random.nextInt()) % len;
for(int i=0; i<len; i++) {
ServiceRegistrar proxy = proxys[(i + rand) % len];
ServiceItem sItem = null;
try {
int maxMatches = ( (filter != null) ? Integer.MAX_VALUE : 1 );
ServiceMatches sm = proxy.lookup(tmpl, maxMatches);
sItem = getMatchedServiceItem(sm, filter);
} catch(Exception e) {
logger.log(Level.INFO,
"Exception occurred during query, discarding proxy",
e);
discard(proxy);
}
if(sItem != null) return sItem;
}//end loop
return null;
}//end lookup
/**
* Queries each available lookup service in the managed set for a service
* that matches the input criteria. The semantics of this method are
* similar to the semantics of the <code>lookup</code> method provided by
* the <code>ServiceRegistrar</code> interface; employing the same
* template-matching scheme. Additionally, this method allows any entity
* to supply an object referred to as a <i>filter</i>. Such an object is
* a non-remote object that defines additional matching criteria that the
* <code>ServiceDiscoveryManager</code> applies when searching for the
* entity's services of interest. This filtering facility is particularly
* useful to entities that wish to extend the capabilities of standard
* template-matching.
* <p>
* This version of <code>lookup</code> returns a <i>single</i> instance
* of <code>ServiceItem</code> corresponding to one of possibly many
* service references that satisfy the matching criteria. If multiple
* services matching the input criteria happen to exist, it is arbitrary
* as to which reference is actually returned. It is for this reason that
* entities that invoke this method typically care only that <i>a</i>
* service is returned, not <i>which</i> service.
* <p>
* Note that this version of <code>lookup</code> provides a
* <i>blocking</i> feature that is controlled through the
* <code>waitDur</code> parameter. That is, this version will not return
* until either a service that matches the input criteria has been
* found, or the amount of time contained in the <code>waitDur</code>
* parameter has passed. If, while waiting for the service of interest
* to be found, the entity decides that it no longer wishes to wait the
* entire period for this method to return, the entity may interrupt this
* method by invoking the interrupt method from the <code>Thread</code>
* class. The intent of this mechanism is to allow the entity to interrupt
* this method in the same way it would a sleeping thread.
* <p>
* Entities typically employ this method when they need infrequent access
* to services, are willing (or forced) to wait for those services to be
* found, and consider the cost of making remote queries for those
* services is outweighed by the overhead of maintaining a local cache
* (for example, because of resource limitations).
*
* @param tmpl an instance of <code>ServiceTemplate</code> corresponding
* to the object to use for template-matching when searching
* for desired services. If <code>null</code> is input to
* this parameter, this method will use a <i>wildcarded</i>
* template (will match all services) when performing
* template-matching. Note that the effects of modifying
* contents of this parameter before this method returns
* are unpredictable and undefined.
* @param filter an instance of <code>ServiceItemFilter</code> containing
* matching criteria that should be applied in addition
* to the template-matching employed when searching for
* desired services. If <code>null</code> is input to this
* parameter, then only template-matching will be employed
* to find the desired services.
* @param waitDur the amount of time (in milliseconds) to wait before
* ending the "search" and returning <code>null</code>.
* If a non-positive value is input to this parameter,
* then this method will not wait; it will simply query
* the available lookup services and return a matching
* service reference or <code>null</code>.
*
* @return a single instance of <code>ServiceItem</code> corresponding to
* a reference to a service that matches the criteria represented
* in the input parameters; or <code>null</code> if no matching
* service can be found. Note that if multiple services matching
* the input criteria exist, it is arbitrary as to which reference
* is returned.
*
* @throws java.lang.InterruptedException this exception occurs when the
* entity interrupts this method by invoking the interrupt method
* from the <code>Thread</code> class.
*
* @throws java.rmi.RemoteException typically, this exception occurs when
* a RemoteException occurs either as a result of an attempt
* to export a remote listener, or an attempt to register with the
* event mechanism of a lookup service.
*
* @see net.jini.core.lookup.ServiceRegistrar#lookup
* @see net.jini.core.lookup.ServiceTemplate
* @see net.jini.lookup.ServiceItemFilter
* @see java.lang.Thread
*/
public ServiceItem lookup(ServiceTemplate tmpl,
ServiceItemFilter filter,
long waitDur) throws InterruptedException,
RemoteException
{
/* First query each lookup for the desired service */
ServiceItem sm = lookup(tmpl,filter);//checkTerminated() is done here
if(sm != null) return sm;
/* If the desired service is not in any of the lookups, wait for it. */
ServiceDiscoveryListener cacheListener
= new ServiceDiscoveryListenerImpl();
LookupCacheImpl cache = null;
try {
/* The cache must be created inside the listener sync block,
* otherwise a race condition can occur. This is because the
* creation of a cache results in event registration which
* will ultimately result in the invocation of the serviceAdded()
* method in the cache's listener, and the interruption of any
* objects waiting on the cache's listener. If the notifications
* happen to occur before commencing the wait on the listener
* object (see below), then the wait will never be interrupted
* because the interrupts were sent before the wait() method
* was invoked. Synchronizing on the listener and the listener's
* serviceAdded() method, and creating the cache only after the
* lock has been acquired, together will prevent this situation
* since event registration cannot occur until the cache is
* created, and the lock that allows entry into the serviceAdded()
* method (which is invoked once the events do arrive) is not
* released until the wait() method is invoked .
*/
synchronized(cacheListener) {
cache = createLookupCache(tmpl,filter,cacheListener,waitDur);
long duration = cache.getLeaseDuration();
while ( duration > 0 ) {
cacheListener.wait(duration);
sm = cache.lookup(null);
if(sm != null ) return sm;
duration = cache.getLeaseDuration();
}//end loop
}//end sync(cacheListener)
return sm;
} finally {
if(cache != null) cache.terminate();
}
}//end lookup
/**
* The <code>createLookupCache</code> method allows the client-like
* entity to request that the <code>ServiceDiscoveryManager</code>
* create a new managed set (or cache) and populate it with
* services, which match criteria defined by the entity, and whose
* references are registered with one or more of the lookup
* services the entity has targeted for discovery.
* <p>
* This method returns an object of type <code>LookupCache</code>.
* Through this return value, the entity can query the cache for
* services of interest, manage the cache's event mechanism for
* service discoveries, or terminate the cache.
* <p>
* An entity typically uses the object returned by this method to
* provide local storage of, and access to, references to services
* that it is interested in using. Entities needing frequent access
* to numerous services will find the object returned by this
* method quite useful because acquisition of those service
* references is provided through local method invocations.
* Additionally, because the object returned by this method provides
* an event mechanism, it is also useful to entities wishing to
* simply monitor, in an event-driven manner, the state changes that
* occur in the services of interest.
* <p>
* Although not required, a common usage pattern for entities that
* wish to use the <code>LookupCache</code> class to store and manage
* "discovered" services is to create a separate cache for each service
* type of interest.
*
* @param tmpl template to match. It uses template-matching
* semantics to identify the service(s) to acquire from
* lookup services in the managed set. If this value is
* <code>null</code>, it is the equivalent of passing a
* <code>ServiceTemplate</code> constructed with all
* <code>null</code> arguments (all wildcards).
* @param filter used to apply additional matching criteria to any
* <code>ServiceItem</code> found through template-matching.
* If this value is <code>null</code>, no additional filtering
* will be applied beyond the template-matching.
* @param listener object that will receive notifications when
* services matching the input criteria are discovered for
* the first time, or have encountered a state change such as
* removal from all lookup services or attribute set changes.
* If this value is <code>null</code>, the cache resulting from
* that invocation will send no such notifications.
*
* @return LookupCache used to query the cache for services of
* interest, manage the cache's event mechanism for service
* discoveries, or terminate the cache.
*
* @throws java.rmi.RemoteException typically, this exception occurs when
* a RemoteException occurs as a result of an attempt to export
* the remote listener that receives service events from the
* lookup services in the managed set.
*
* @see net.jini.lookup.ServiceItemFilter
*/
public LookupCache createLookupCache(ServiceTemplate tmpl,
ServiceItemFilter filter,
ServiceDiscoveryListener listener)
throws RemoteException
{
checkTerminated();
return createLookupCache(tmpl, filter, listener, Long.MAX_VALUE);
}//end createLookupCache
/** The <code>getDiscoveryManager</code> method will return an
* object that implements the <code>DiscoveryManagement</code>
* interface. The object returned by this method provides the
* <code>ServiceDiscoveryManager</code> with the ability to set
* discovery listeners and to discard previously discovered lookup
* services when they are found to be unavailable.
*
* @return DiscoveryManagement implementation
* @see net.jini.discovery.DiscoveryManagement
*/
public DiscoveryManagement getDiscoveryManager() {
checkTerminated();
return discMgr;
}//end getDiscoveryManager
/**
* The <code>getLeaseRenewalManager</code> method will return an
* instance of the <code>LeaseRenewalManager</code> class. The
* object returned by this method manages the leases requested and
* held by the <code>ServiceDiscoveryManager</code>. In general, these
* leases correspond to the registrations made by the
* <code>ServiceDiscoveryManager</code> with the event mechanism of
* each lookup service in the managed set.
*
* @return LeaseRenewalManager for this instance of the
* <code>ServiceDiscoveryManager</code>.
* @see net.jini.lease.LeaseRenewalManager
*/
public LeaseRenewalManager getLeaseRenewalManager() {
checkTerminated();
return leaseRenewalMgr;
}//end getLeaseRenewalManager
/**
* The <code>terminate</code> method performs cleanup duties
* related to the termination of the event mechanism for lookup
* service discovery, the event mechanism for service discovery,
* and the cache management duties of the
* <code>ServiceDiscoveryManager</code>.
* <p>
* For each instance of <code>LookupCache</code> created and
* managed by the <code>ServiceDiscoveryManager</code>, the
* <code>terminate</code> method will do the following:
* <ul>
* <li>Either remove all listener objects registered for receipt
* of <code>DiscoveryEvent</code> objects or, if the discovery
* manager employed by the <code>ServiceDiscoveryManager</code> was
* created by the <code>ServiceDiscoveryManager</code> itself,
* terminate all discovery processing being performed by that
* manager object on behalf of the entity.
* <p>
* <li>Cancel all event leases granted by each lookup service in
* the managed set of lookup services.
* <p>
* <li>Un-export all remote listener objects registered with each
* lookup service in the managed set.
* <p>
* <li>Terminate all threads involved in the process of retrieving
* and storing references to discovered services of interest.
* </ul>
* Calling any method after the termination will result in an
* <code>IllegalStateException</code>.
*
* @see net.jini.lookup.LookupCache
* @see net.jini.discovery.DiscoveryEvent
*/
public void terminate() {
synchronized(this) {
if(bTerminated) return;//allow for multiple terminations
bTerminated = true;
/* Terminate lookup service discovery processing */
discMgr.removeDiscoveryListener(discMgrListener);
if(discMgrInternal) discMgr.terminate();
}//end sync
/* Terminate all caches: cancel event leases, un-export listeners */
boolean terminateCaches = false;
ArrayList cachesClone = null;
synchronized(caches) {
if( !caches.isEmpty() ) {
terminateCaches = true;
cachesClone = (ArrayList)caches.clone();
}
}//end sync
if(terminateCaches) {
Iterator iter = cachesClone.iterator();
while (iter.hasNext()) {
LookupCacheImpl cache = (LookupCacheImpl)iter.next();
cache.terminate();
}//end loop
}//endif(terminateCaches)
}//end terminate
/**
* Queries each available lookup service in the managed set for service(s)
* that match the input criteria. The semantics of this method are
* similar to the semantics of the <code>lookup</code> method provided by
* the <code>ServiceRegistrar</code> interface; employing the same
* template-matching scheme. Additionally, this method allows any entity
* to supply an object referred to as a <i>filter</i>. Such an object is
* a non-remote object that defines additional matching criteria that the
* <code>ServiceDiscoveryManager</code> applies when searching for the
* entity's services of interest. This filtering facility is particularly
* useful to entities that wish to extend the capabilities of standard
* template-matching.
* <p>
* Entities typically employ this method when they need infrequent access
* to multiple instances of services, and when the cost of making remote
* queries is outweighed by the overhead of maintaining a local cache
* (for example, because of resource limitations).
* <p>
* This version of <code>lookup</code> returns an <i>array</i> of instances
* of <code>ServiceItem</code> in which each element corresponds to a
* service reference that satisfies the matching criteria. The number
* of elements in the returned set will be no greater than the value of
* the <code>maxMatches</code> parameter, but may be less.
* <p>
* Note that this version of <code>lookup</code> does not provide a
* <i>blocking</i> feature. That is, this version will return immediately
* with whatever number of service references it can find, up to
* the number indicated in the <code>maxMatches</code> parameter. If
* no services matching the input criteria can be found on the first
* attempt, an empty array is returned.
*
* It is important to understand this characteristic because there is
* a common usage scenario that can cause confusion when this version
* of <code>lookup</code> is used but fails to discover any instances
* of the expected service of interest. Suppose an entity creates a
* service discovery manager and then immediately calls this version
* of <code>lookup</code>, which simply queries the currently discovered
* lookup services for the service of interest. If the discovery manager
* employed by the service discovery manager has not yet disovered any
* lookup services (thus, there are no lookup services to query) the
* method will immediately return an empty array. This can be confusing
* when one verifies that instance(s) of such a service of interest
* have indeed been started and registered with the existing lookup
* service(s). To address this issue, one of the blocking versions
* of <code>lookup</code> could be used instead of this version, or
* the entity could simply wait until the discovery manager has been
* given enough time to complete its own (lookup) discovery processing.
*
* @param tmpl an instance of <code>ServiceTemplate</code>
* corresponding to the object to use for
* template-matching when searching for desired services.
* If <code>null</code> is input to this parameter,
* this method will use a <i>wildcarded</i> template
* (will match all services) when performing
* template-matching. Note that the effects of modifying
* contents of this parameter before this method returns
* are unpredictable and undefined.
* @param maxMatches this method will return no more than this number of
* service references
* @param filter an instance of <code>ServiceItemFilter</code>
* containing matching criteria that should be applied
* in addition to the template-matching employed when
* searching for desired services. If <code>null</code>
* is input to this parameter, then only
* template-matching will be employed to find the
* desired services.
*
* @return an array of instances of <code>ServiceItem</code> where each
* element corresponds to a reference to a service that matches
* the criteria represented in the input parameters; or an
* empty array if no matching service can be found.
*
* @see net.jini.core.lookup.ServiceRegistrar#lookup
* @see net.jini.core.lookup.ServiceTemplate
* @see net.jini.lookup.ServiceItemFilter
*/
public ServiceItem[] lookup(ServiceTemplate tmpl,
int maxMatches,
ServiceItemFilter filter)
{
checkTerminated();
if (maxMatches < 1)
throw new IllegalArgumentException("maxMatches must be > 0");
/* retrieve the lookup service(s) to query for matching service(s) */
ServiceRegistrar[] proxys;
synchronized(proxyRegSet) {
proxys = buildServiceRegistrar();
}
int len = proxys.length;
ArrayList sItemSet = new ArrayList(len);
if(len > 0) {
/* loop thru the set of lookups, randomly selecting each lookup */
int rand = (Math.abs(random.nextInt())) % len;
for(int i=0; i<len; i++) {
int max = maxMatches;
ServiceRegistrar proxy = proxys[(i + rand) % len];
try {
/* If a filter is to be applied (filter != null), then
* the value of the maxMatches parameter will not
* suffice when querying the current lookup service.
* This is because although services returned from a
* query of the lookup service will match the template,
* some of those services may get filtered out. Thus,
* asking for exactly maxMatches may result in fewer
* matching services than actually are contained in
* the lookup. Thus, all matching services are
* requested by passing in "infinity" for the maximum
* number of matches (Integer.MAX_VALUE).
*/
if(filter != null) max = Integer.MAX_VALUE;
/* Query the current lookup for matching service(s). */
ServiceMatches sm = proxy.lookup(tmpl, max);
int nItems = sm.items.length;
if(nItems == 0) continue;//no matches, query next lookup
/* Loop thru the matching services, randomly selecting
* each service, applying the filter if appropriate,
* and making sure the service has not already been
* selected (it may have been returned from a previously
* queried lookup).
*/
int r = (Math.abs(random.nextInt())) % nItems;
for(int j=0; j<nItems; j++) {
ServiceItem sItem = sm.items[(j+r) % nItems];
if(sItem == null) continue;
if( !filterPassFail(sItem,filter) ) continue;
if(!isArrayContainsServiceItem(sItemSet, sItem))
sItemSet.add(sItem);
if(sItemSet.size() >= maxMatches) {
return (ServiceItem [])(sItemSet.toArray
(new ServiceItem[sItemSet.size()]));
}
}//end loop(j)
} catch(Exception e) {
logger.log(Level.INFO,
"Exception occurred during query, "
+"discarding proxy",
e);
discard(proxy);
}
}//end loop(i)
}//endif(len>0)
/* Will reach this return statement only when less than the number
* of services requested have been found in the loop above.
*/
return (ServiceItem [])(sItemSet.toArray
(new ServiceItem[sItemSet.size()]));
}//end lookup
/**
* Queries each available lookup service in the managed set for service(s)
* that match the input criteria. The semantics of this method are
* similar to the semantics of the <code>lookup</code> method provided by
* the <code>ServiceRegistrar</code> interface; employing the same
* template-matching scheme. Additionally, this method allows any entity
* to supply an object referred to as a <i>filter</i>. Such an object is
* a non-remote object that defines additional matching criteria that the
* <code>ServiceDiscoveryManager</code> applies when searching for the
* entity's services of interest. This filtering facility is particularly
* useful to entities that wish to extend the capabilities of standard
* template-matching.
* <p>
* This version of <code>lookup</code> returns an <i>array</i> of instances
* of <code>ServiceItem</code> in which each element corresponds to a
* service reference that satisfies the matching criteria. The number
* of elements in the returned set will be no greater than the value of
* the <code>maxMatches</code> parameter, but may be less.
* <p>
* Note that this version of <code>lookup</code> provides a
* <i>blocking</i> feature that is controlled through the
* <code>waitDur</code> parameter in conjunction with the
* <code>minMatches</code> and the <code>maxMatches</code> parameters.
* This method will not return until one of the following occurs:
* <p>
* <ul>
* <li> the number of matching services found on the first attempt is
* greater than or equal to the value of the
* <code>minMatches</code> parameter, in which case this method
* returns each of the services found up to the value of
* the <code>maxMatches</code> parameter
* <li> the number of matching services found <i>after</i> the first
* attempt (that is, after the method enters the "wait state")
* is at least as great as the value of the
* <code>minMatches</code> parameter in which case this method
* returns each of the services found up to the value of
* the <code>maxMatches</code> parameter
* <li> the amount of time that has passed since this method entered
* the wait state exceeds the value of the <code>waitDur</code>
* parameter, in which case this method returns all of the
* currently discovered services
* </ul>
* <p>
* The purpose of the <code>minMatches</code> parameter is to allow the
* entity to balance its need for multiple matching service references
* with its need to minimize the time spent in the wait state; time that
* most would consider wasted if an acceptable number of matching service
* references were found, but this method continued to wait until the end
* of the designated time period.
* <p>
* If, while waiting for the minimum number of desired services to
* be discovered, the entity decides that it no longer wishes to wait the
* entire period for this method to return, the entity may interrupt this
* method by invoking the interrupt method from the <code>Thread</code>
* class. The intent of this mechanism is to allow the entity to interrupt
* this method in the same way it would a sleeping thread.
* <p>
* Entities typically employ this method when they need infrequent access
* to multiple instances of services, are willing (or forced) to
* wait for those services to be found, and consider the cost of making
* remote queries for those services is outweighed by the overhead
* of maintaining a local cache (for example, because of resource
* limitations).
*
* @param tmpl an instance of <code>ServiceTemplate</code>
* corresponding to the object to use for
* template-matching when searching for desired
* services. If <code>null</code> is input to this
* parameter, this method will use a
* <i>wildcarded</i> template (will match all
* services) when performing template-matching. Note
* that the effects of modifying contents of this
* parameter before this method returns are
* unpredictable and undefined.
* @param minMatches this method will immediately exit the wait state
* and return once this number of service references
* is found
* @param maxMatches this method will return no more than this number of
* service references
* @param filter an instance of <code>ServiceItemFilter</code>
* containing matching criteria that should be applied
* in addition to the template-matching employed when
* searching for desired services. If <code>null</code>
* is input to this parameter, then only
* template-matching will be employed to find the
* desired services.
* @param waitDur the amount of time (in milliseconds) to wait before
* ending the "search" and returning an empty array.
* If a non-positive value is input to this parameter,
* then this method will not wait; it will simply query
* the available lookup services and return whatever
* matching service reference(s) it could find, up
* to <code>maxMatches</code>.
*
* @return an array of instances of <code>ServiceItem</code> where each
* element corresponds to a reference to a service that matches
* the criteria represented in the input parameters; or an
* empty array if no matching service can be found within the
* time allowed.
*
* @throws java.lang.InterruptedException this exception occurs when the
* entity interrupts this method by invoking the interrupt method
* from the <code>Thread</code> class.
*
* @throws java.lang.IllegalArgumentException this exception occurs when
* one of the following conditions is satisfied:
* <p><ul> <li>the <code>minMatches</code> parameter is non-positive
* <li>the <code>maxMatches</code> parameter is non-positive
* <li>the value of <code>maxMatches</code> is <i>less than</i>
* the value of <code>minMatches</code>
* </ul>
*
* @throws java.rmi.RemoteException typically, this exception occurs when
* a RemoteException occurs either as a result of an attempt
* to export a remote listener, or an attempt to register with the
* event mechanism of a lookup service.
*
* @see net.jini.core.lookup.ServiceRegistrar#lookup
* @see net.jini.core.lookup.ServiceTemplate
* @see net.jini.lookup.ServiceItemFilter
* @see java.lang.Thread
*/
public ServiceItem[] lookup(ServiceTemplate tmpl,
int minMatches,
int maxMatches,
ServiceItemFilter filter,
long waitDur ) throws InterruptedException,
RemoteException
{
checkTerminated();
if (minMatches < 1)
throw new IllegalArgumentException("minMatches must be > 0");
if (maxMatches < minMatches)
throw new IllegalArgumentException
("maxMatches must be > minMatches");
ServiceItem [] sItems = lookup(tmpl, maxMatches, filter);
if(sItems.length >= minMatches) return sItems;
ArrayList sItemSet = new ArrayList(sItems.length);
for(int i=0; i<sItems.length; i++) {
//if(!sItemSet.contains(sItems[i])
sItemSet.add(sItems[i]);
}//end loop
ServiceDiscoveryListenerImpl cacheListener =
new ServiceDiscoveryListenerImpl();
/* The cache must be created inside the listener sync block,
* otherwise a race condition can occur. This is because the
* creation of a cache results in event registration which
* will ultimately result in the invocation of the serviceAdded()
* method in the cache's listener, and the interruption of any
* objects waiting on the cache's listener. If the notifications
* happen to occur before commencing the wait on the listener
* object (see below), then the wait will never be interrupted
* because the interrupts were sent before the wait() method
* was invoked. Synchronizing on the listener and the listener's
* serviceAdded() method, and creating the cache only after the
* lock has been acquired, together will prevent this situation
* since event registration cannot occur until the cache is
* created, and the lock that allows entry into the serviceAdded()
* method (which is invoked once the events do arrive) is not
* released until the wait() method is invoked.
*/
LookupCacheImpl cache = null;
synchronized(cacheListener) {
cache = createLookupCache(tmpl,filter,cacheListener,waitDur);
long duration = cache.getLeaseDuration();
while ( duration > 0 ) {
cacheListener.wait(duration);
ServiceItem items[] = cacheListener.getServiceItem();
for(int i=0; i<items.length; i++) {
if(!isArrayContainsServiceItem(sItemSet, items[i])) {
sItemSet.add(items[i]);
}//endif
}//end loop
if(sItemSet.size() == minMatches) break;
duration = cache.getLeaseDuration();
}//end loop
}//end sync(cacheListener)
cache.terminate();
ServiceItem [] r = new ServiceItem[sItemSet.size()];
sItemSet.toArray(r);
return r;
}//end lookup
/** From the given set of ServiceMatches, randomly selects and returns
* a ServiceItem that matches the given filter (if applicable).
*/
private ServiceItem getMatchedServiceItem(ServiceMatches sm,
ServiceItemFilter filter)
{
int len = sm.items.length;
if(len > 0) {
int rand = Math.abs(random.nextInt()) % len;
for(int i=0; i<len; i++) {
ServiceItem sItem = sm.items[(i+rand) % len];
if(sItem == null) continue;
if( !filterPassFail(sItem,filter) ) continue;
return sItem;
}//end loop
}//endif
return null;
}//end getMatchedServiceItem
/** Creates a LookupCache with specific lease duration. */
private LookupCacheImpl createLookupCache
(ServiceTemplate tmpl,
ServiceItemFilter filter,
ServiceDiscoveryListener listener,
long leaseDuration)
throws RemoteException
{
if(tmpl == null) tmpl = new ServiceTemplate(null, null, null);
LookupCacheImpl cache = new LookupCacheImpl(tmpl, filter,
listener, leaseDuration);
synchronized(caches) {
caches.add(cache);
}
logger.finest("ServiceDiscoveryManager - LookupCache created");
return cache;
}//end createLookupCache
/** Returns element from proxyRegSet that corresponds to the given proxy.*/
private ProxyReg findReg(ServiceRegistrar proxy) {
Iterator iter = proxyRegSet.iterator();
while(iter.hasNext()) {
ProxyReg reg =(ProxyReg)iter.next();
if(reg.proxy.equals(proxy)) return reg;
}//end loop
return null;
}//end findReg
/** Convenience method invoked when failure occurs in the cache
* tasks executed in this utility. If the appropriate logging level
* is enabled, this method will log the stack trace of the given
* <code>Throwable</code>; noting the given source class and method,
* and displaying the given message. Additionally, this method will
* discard the given lookup service proxy. Note that if the utility
* itself has already been terminated, or if the cache in which the
* failure occurred has been terminated, then the failure is logged
* at the HANDLED level, and the lookup service proxy is not discarded.
*
* Also, note that if the discovery manager employed by this utility has
* already been terminated, then the attempt to discard the given lookup
* service proxy will result in an <code>IllegalStateException</code>.
* Since this method is called from within the tasks run by this
* utility, and since propagating an <code>IllegalStateException</code>
* out into the ThreadGroup of those tasks is undesirable, this method
* does not propagate <code>IllegalStateException</code>s that occur as
* a result of an attempt to discard a lookup service proxy from the
* discovery manager.
*
* For more information, refer to Bug 4490358 and 4858211.
*/
private void fail(Throwable e,
ServiceRegistrar proxy,
String sourceClass,
String sourceMethod,
String msg,
boolean cacheTerminated)
{
Level logLevel = Level.INFO;
boolean discardProxy = true;
synchronized(this) {
if(bTerminated || cacheTerminated) {
logLevel = Levels.HANDLED;
discardProxy = false;
}//endif
}//end sync(this)
if( (e != null) && (logger.isLoggable(logLevel)) ) {
logger.logp(logLevel, sourceClass, sourceMethod, msg, e);
}//endif
try {
if(discardProxy) discard(proxy);
} catch(IllegalStateException e1) {
if(logger.isLoggable(logLevel) ) {
logger.logp(logLevel,
sourceClass,
sourceMethod,
"failure discarding lookup service proxy, "
+"discovery manager already terminated",
e1);
}//endif
}
}//end fail
/** Discards a ServiceRegistrar through the discovery manager.*/
private void discard(ServiceRegistrar proxy) {
discMgr.discard(proxy);
}//end discard
/** Cancels the given event lease. */
private void cancelLease(Lease lease ) {
try {
leaseRenewalMgr.cancel(lease);
} catch(Exception e) {
logger.log(Level.FINER,
"exception occurred while cancelling an event "
+"registration lease",
e);
}
}//end cancelLease
/** Registers for events from the lookup service associated with the
* given proxy, and returns both the lease and the event sequence number
* from the event registration wrapped in the locally-defined class,
* <code>EventReg</code>.
*
* This method is called from the <code>RegisterListenerTask</code>. If
* a <code>RemoteException</code> occurs during the event registration
* attempt, this method discards the lookup service and returns
* <code>null</code>.
*/
private EventReg registerListener(ServiceRegistrar proxy,
ServiceTemplate tmpl,
RemoteEventListener listenerProxy,
long duration) throws RemoteException
{
/* Register with the event mechanism of the given lookup service */
EventRegistration e = null;
int transition = ( ServiceRegistrar.TRANSITION_NOMATCH_MATCH
| ServiceRegistrar.TRANSITION_MATCH_NOMATCH
| ServiceRegistrar.TRANSITION_MATCH_MATCH );
e = proxy.notify(tmpl, transition, listenerProxy, null, duration);
/* Proxy preparation -
*
* Prepare the proxy to the lease on the event registration just
* returned. Because lease management (renewal and cancellation)
* involves remote calls, lease proxies should be prepared before
* management of the associated leases begins. This allows one to
* verify trust in the lease, and ensures that the appropriate
* constraints are attached to the lease.
*/
Lease eventLease = e.getLease();
eventLease = (Lease)eventLeasePreparer.prepareProxy(eventLease);
logger.log(Level.FINEST, "ServiceDiscoveryManager - proxy to event "
+"registration lease prepared: {0}", eventLease);
/* Management the lease on the event registration */
leaseRenewalMgr.renewFor(eventLease,
duration,
new LeaseListenerImpl(proxy));
/* Wrap source, id, event sequence & lease in EventReg, and return. */
return ( new EventReg(e.getSource(),
e.getID(),
e.getSequenceNumber(),
eventLease) );
}//end registerListener
/** Throws an IllegalStateException if the current instance of the
* ServiceDiscoveryManager has been terminated.
*/
private void checkTerminated() {
synchronized(this) {
if(bTerminated) {
throw new IllegalStateException
("service discovery manager was terminated");
}//endif
}//end sync
}//end checkTerminated
/** Returns a "non-shallow" (not just a clone) copy of the given
* template.
*/
static private ServiceTemplate copyServiceTemplate(ServiceTemplate tmpl) {
Class[] serviceTypes = null;
Entry[] attributeSetTemplates = null;
if(tmpl.serviceTypes != null) {
int len = tmpl.serviceTypes.length;
serviceTypes = new Class[len];
System.arraycopy(tmpl.serviceTypes, 0, serviceTypes, 0, len );
}
if(tmpl.attributeSetTemplates != null) {
int len = tmpl.attributeSetTemplates.length;
attributeSetTemplates = new Entry[len];
System.arraycopy(tmpl.attributeSetTemplates, 0,
attributeSetTemplates, 0, len);
}
return new ServiceTemplate(tmpl.serviceID,
serviceTypes,
attributeSetTemplates);
}//end copyServiceTemplate
/** Determines if the given ServiceItem is an element of the given array.*/
static private boolean isArrayContainsServiceItem(ArrayList a,
ServiceItem s)
{
Iterator iter = a.iterator();
while(iter.hasNext()) {
Object o = iter.next();
if ( !(o instanceof ServiceItem )) continue;
ServiceItem sa = (ServiceItem)o;
if( sa.serviceID.equals(s.serviceID)
&& LookupAttributes.equal(sa.attributeSets,s.attributeSets)
&& (sa.service.equals(s.service)) )
return true;
}//end loop
return false;
}//end isArrayContainsServiceItems
/* Convenience method that encapsulates the retrieval of the configurable
* items from the given <code>Configuration</code> object.
*/
private void init(DiscoveryManagement discoveryMgr,
LeaseRenewalManager leaseMgr,
Configuration config)
throws IOException, ConfigurationException
{
/* Retrieve configuration items if applicable */
if(config == null) throw new NullPointerException("config is null");
thisConfig = config;
/* Proxy preparers */
registrarPreparer = (ProxyPreparer)thisConfig.getEntry
(COMPONENT_NAME,
"registrarPreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
eventLeasePreparer = (ProxyPreparer)thisConfig.getEntry
(COMPONENT_NAME,
"eventLeasePreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
/* Lease renewal manager */
leaseRenewalMgr = leaseMgr;
if(leaseRenewalMgr == null) {
try {
leaseRenewalMgr
= (LeaseRenewalManager)thisConfig.getEntry
(COMPONENT_NAME,
"leaseManager",
LeaseRenewalManager.class);
} catch(NoSuchEntryException e) { /* use default */
leaseRenewalMgr = new LeaseRenewalManager(thisConfig);
}
}//endif
/* Wait value for the "service discard problem". */
discardWait = ((Long)thisConfig.getEntry
(COMPONENT_NAME,
"discardWait",
long.class,
new Long(discardWait))).longValue();
/* Discovery manager */
discMgr = discoveryMgr;
if(discMgr == null) {
discMgrInternal = true;
try {
discMgr = (DiscoveryManagement)thisConfig.getEntry
(COMPONENT_NAME,
"discoveryManager",
DiscoveryManagement.class);
} catch(NoSuchEntryException e) { /* use default */
discMgr = new LookupDiscoveryManager
(new String[] {""}, null, null, thisConfig);
}
}//endif
discMgr.addDiscoveryListener(discMgrListener);
}//end init
/** Applies the given <code>filter</code> to the given <code>item</code>,
* and returns <code>true</code> if the <code>filter</code> returns a
* <code>pass</code> value; otherwise, returns <code>false</code>.
* <p>
* Note that as described in the specification of
* <code>ServiceItemFilter</code>, when the <code>item</code> passes
* the <code>filter</code>, the <code>service</code> field of
* the <code>item</code> is replaced with the filtered form of the
* object previously contained in that field. Additionally, if the
* <code>filter</code> returns <code>indefinite</code>, then as specified,
* the <code>service</code> field is replaced with <code>null</code>
* (in which case, this method returns <code>false</code>).
* <p>
* This method is used by the non-blocking version(s) of the
* <code>lookup</code> method of the <code>ServiceDiscoveryManager</code>,
* as well as when second-stage filtering is performed in the
* <code>LookupCache</code>.
*/
private boolean filterPassFail(ServiceItem item, ServiceItemFilter filter){
if( (item == null) || (item.service == null) ) return false;
if(filter == null) return true;
boolean pass = filter.check(item);
if( pass && (item.service != null) ) return true;
return false;
}//end filterPassFail
}//end class ServiceDiscoveryManager