![JAR search and dependency download from the Maven repository](/logo.png)
net.jini.lookup.ServiceDiscoveryManager Maven / Gradle / Ivy
/*
* 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 java.io.IOException;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.config.NoSuchEntryException;
import net.jini.core.entry.Entry;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceID;
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 net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryManagement;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lookup.ServiceAttributesAccessor;
import net.jini.lookup.ServiceIDAccessor;
import net.jini.lookup.ServiceProxyAccessor;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import net.jini.lease.LeaseRenewalManager;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import org.apache.river.action.GetBooleanAction;
import org.apache.river.action.GetLongAction;
import org.apache.river.logging.Levels;
import org.apache.river.lookup.entry.LookupAttributes;
import org.apache.river.thread.NamedThreadFactory;
/**
* The ServiceDiscoveryManager
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.
*
* There are three basic usage patterns for this class. In order of importance
* and typical usage, those patterns are:
*
*
* - The entity requests that the
ServiceDiscoveryManager
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
* ServiceDiscoveryManager
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 frequent
* 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}.
* - The entity can register with the event mechanism provided by the
*
ServiceDiscoveryManager
. 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.
* - The entity, through the public API of the
*
ServiceDiscoveryManager
, can directly query the lookup services
* managed by the ServiceDiscoveryManager
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
* ServiceDiscoveryManager
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.
*
*
* 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 filter; an instance of
* {@link net.jini.lookup.ServiceItemFilter ServiceItemFilter}. A filter is a
* non-remote object that defines additional matching criteria that the
* ServiceDiscoveryManager
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.
*
* 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 safe 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 ServiceDiscoveryManager
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 proxy preparation. 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.
*
* 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.
*
*
*
* The following implementation-specific items are discussed below:
*
*
*
*
* Configuring ServiceDiscoveryManager
*
*
*
* This implementation of ServiceDiscoveryManager
supports the
* following configuration entries; where each configuration entry name is
* associated with the component name
* net.jini.lookup.ServiceDiscoveryManager
. Note that the
* configuration entries specified here are specific to this implementation of
* ServiceDiscoveryManager
. 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.
*
* 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.
*
*
*
*
*
* •
*
* cacheExecutorService
*
*
* Type: {@link java.util.concurrent.ExecutorService ExecutorService}
*
*
* Default: new
* {@link java.util.concurrent.ThreadPoolExecutor
* ThreadPoolExecutor}( 6, 6, 15, TimeUnit.SECONDS, new LinkedBlockingQueue(),
* new NamedThreadFactory( "SDM lookup cache", false ))
*
*
* Description:
* The object that pools and manages the various threads executed by each
* of the lookup caches created by this utility. There is one such
* ExecutorService created for each cache. For each cache that is created in
* this utility, a single, separate instance of this ExecutorService will be
* retrieved and employed by that cache. This object should not be shared with
* other components in the application that employs this utility.
*
*
*
*
*
* •
*
* discardExecutorService
*
*
* Type: {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService}
*
*
* Default: new
* {@link java.util.concurrent.ScheduledThreadPoolExecutor
* ScheduledThreadPoolExecutor}( 4,
* new NamedThreadFactory( "SDM discard timer", false ))
*
*
* Description:
* 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. For each cache that is created in this utility, a single, separate
* instance of this ExecutorService will be retrieved and employed by that
* cache. This object should not be shared with other components in the
* application that employs this utility.
*
*
* *
*
*
*
* •
*
* ServiceEventExecutorService
*
*
* Type: {@link java.util.concurrent.ExecutorService ExecutorService}
*
*
* Default: new
* {@link java.util.concurrent.ThreadPoolExecutor
* ThreadPoolExecutor}( 2, 2, 15, TimeUnit.SECONDS, new PriorityBlockingQueue(256),
* new NamedThreadFactory( "SDM ServiceEvent: " +toString(), false ))
*
*
* Description:
* The ExecutorService object that processes incoming ServiceEvent's. This
* executor is wrapped by an {@link org.apache.river.thread.ExtensibleExecutorService}
* that implements a {@link java.lang.Comparable} {@link java.util.concurrent.FutureTask},
* which orders the ServiceEvent's in the {@link java.util.concurrent.PriorityBlockingQueue}.
*
* A single threaded executor is usually sufficient for most purposes, and is
* usually seldom found in a running state, testing has shown a single threaded
* executor is very unlikely to perform unnecessary lookup's, while a
* ThreadPoolExecutor with 4 threads will perform lookup for about 1% of cases.
* This can be measured by setting the SDM logger to fine.
*
* There is however an issue that does occur when running a single threaded
* executor, the lookup events that occur, seem to occur because of a problem
* when registering
*
* For the purpose of testing, it is beneficial to use a ThreadPoolExecutor with
* numerous threads, as this will test the alternate execution paths that
* include lookup.
*
*
*
*
*
*
* •
*
* discardWait
*
*
* Type: long
*
*
* Default: 2*(5*60*1000)
*
*
* Description:
* The value used to affect the behavior of the mechanism that handles the
* service discard problem 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.
*
*
*
*
*
* •
*
* discoveryManager
*
*
* Type: {@link net.jini.discovery.DiscoveryManagement}
*
*
* Default: 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)
*
*
* Description:
* 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.
*
*
*
*
*
* •
*
* eventLeasePreparer
*
*
* Type: {@link net.jini.security.ProxyPreparer}
*
*
* Default: new {@link net.jini.security.BasicProxyPreparer}()
*
*
*
* Description:
* 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.
*
* Currently, no methods of the returned proxy are invoked by this utility.
*
*
*
*
*
* •
*
* eventListenerExporter
*
*
* Type: {@link net.jini.export.Exporter}
*
*
* Default: 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),
* new
* {@link net.jini.jeri.BasicILFactory}(),
*
* false, false)
*
*
* Description:
* 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 keepAlive
flag to false
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.
*
*
*
*
*
* •
*
* leaseManager
*
*
* Type: {@link net.jini.lease.LeaseRenewalManager}
*
*
* Default: new
* {@link net.jini.lease.LeaseRenewalManager#LeaseRenewalManager(
* net.jini.config.Configuration) LeaseRenewalManager}(config)
*
*
* Description:
* 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.
*
*
*
*
*
* •
*
* registrarPreparer
*
*
* Type: {@link net.jini.security.ProxyPreparer}
*
*
* Default: new {@link net.jini.security.BasicProxyPreparer}()
*
*
*
* Description:
* 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.
*
* The following methods of the proxy returned by this preparer are invoked by
* this utility:
*
* - {@link net.jini.core.lookup.ServiceRegistrar#lookup lookup}
*
- {@link net.jini.core.lookup.ServiceRegistrar#notify notify}
*
*
*
*
*
*
*
*
* •
*
* bootstrapPreparer
*
*
* Type: {@link net.jini.security.ProxyPreparer}
*
*
* Default: new {@link net.jini.security.BasicProxyPreparer}()
*
*
*
* Description:
* Preparer for bootstrap proxy results returned by lookup services
* that are discovered and used by this utility.
*
* The following methods of the proxy returned by this preparer are invoked by
* this utility:
*
* - {@link SafeServiceRegistrar#lookUp(net.jini.core.lookup.ServiceTemplate, int) lookup}
*
- {@link SafeServiceRegistrar#notiFy notiFy}
*
*
*
*
*
*
*
*
*
* •
*
* useInsecureLookup
*
*
* Type: {@link java.lang.Boolean}
*
*
* Default: new {@link java.lang.Boolean}("FALSE")
*
*
*
* Description:
* When true, ServiceDiscoveryManager and LookupCache use
* {@link net.jini.core.lookup.ServiceRegistrar#lookup(net.jini.core.lookup.ServiceTemplate, int)}
* instead of {@link SafeServiceRegistrar#lookUp(net.jini.core.lookup.ServiceTemplate, int)}
* to perform service discovery.
*
*
*
*
*
* Logging
*
*
*
* This implementation of ServiceDiscoveryManager
uses the
* {@link Logger} named net.jini.lookup.ServiceDiscoveryManager
to
* log information at the following logging levels:
*
*
*
*
*
*
* net.jini.lookup.ServiceDiscoveryManager
*
*
* Level
* Description
*
*
*
* {@link java.util.logging.Level#INFO INFO}
*
* when any exception occurs while querying a lookup service, or upon applying a
* filter to the results of such a query
*
*
*
* {@link java.util.logging.Level#INFO INFO}
*
* 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
*
*
*
* {@link java.util.logging.Level#INFO INFO}
* when any exception occurs while attempting to prepare a proxy
*
*
* {@link java.util.logging.Level#INFO INFO}
*
* when an IllegalStateException
occurs while discarding a lookup
* service proxy after logging a failure that has occurred in one of the tasks
* executed by this utility
*
*
*
* {@link java.util.logging.Level#INFO INFO}
* upon failure of the lease renewal process
*
*
* {@link org.apache.river.logging.Levels#HANDLED HANDLED}
*
* when an exception occurs because a remote call to a lookup service has been
* interrupted as a result of the termination of a cache
*
*
*
* {@link org.apache.river.logging.Levels#HANDLED HANDLED}
*
* when a "gap" is encountered in an event sequence from a lookup service
*
*
*
* {@link java.util.logging.Level#FINER FINER}
* upon failure of the lease cancellation process
*
*
* {@link java.util.logging.Level#FINEST FINEST}
* whenever any task is started
*
*
* {@link java.util.logging.Level#FINEST FINEST}
* whenever any task completes successfully
*
*
* {@link java.util.logging.Level#FINEST FINEST}
* whenever a lookup cache is created
*
*
* {@link java.util.logging.Level#FINEST FINEST}
* whenever a lookup cache is terminated
*
*
* {@link java.util.logging.Level#FINEST FINEST}
* whenever a proxy is prepared
*
*
* {@link java.util.logging.Level#FINEST FINEST}
*
* when an exception (that is, IllegalStateException
) occurs while
* unexporting a cache's remote event listener while the cache is being
* terminated
*
*
*
*
* See the {@link org.apache.river.logging.LogManager} class for one way to use the
* logging level {@link org.apache.river.logging.Levels#HANDLED HANDLED} in standard
* logging configuration files.
*
*
* @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 {
final ProxyPreparer bootstrapProxyPreparer;
final boolean useInsecureLookup;
private final static String DISCARD_PROPERTY = "org.apache.river.sdm.discardWait";
private final static String INSECURE_LOOKUP_PROPERTY = "org.apache.river.sdm.insecureLookup";
private static final ExecutorService logExec = Executors.newSingleThreadExecutor(new NamedThreadFactory("SDM logger", false));
static void log(Level level, String message){
log(level, message, null, null);
}
static void log(Level level, String message, Throwable thrown){
log(level, message, null, thrown);
}
static void log(Level level, String message, Object[] parameters){
log(level, message, parameters, null);
}
static void log(Level level, String message, Object[] parameters, Throwable thrown){
final LogRecord record = new LogRecord(level, message);
record.setParameters(parameters);
record.setThrown(thrown);
logExec.submit(new Runnable(){
public void run() {
logger.log(record);
}
});
}
static void logp(Level logLevel, String sourceClass, String sourceMethod, String message, Throwable thrown) {
final LogRecord record = new LogRecord(logLevel, message);
record.setSourceClassName(sourceClass);
record.setSourceMethodName(sourceMethod);
record.setThrown(thrown);
logExec.submit(new Runnable(){
public void run() {
logger.log(record);
}
});
}
/**
* @return the discardWait
*/
long getDiscardWait() {
long disWait = AccessController.doPrivileged(new GetLongAction(DISCARD_PROPERTY, discardWait));
if (logger.isLoggable(Level.FINEST))
log(Level.FINEST, "discard wait = {0}", new Object[]{ disWait});
return disWait;
}
/**
* @return the useInsecureLookup
*/
boolean useInsecureLookup() {
if (AccessController.doPrivileged(new GetBooleanAction(INSECURE_LOOKUP_PROPERTY))) {
if (logger.isLoggable(Level.CONFIG)){
log(Level.CONFIG, "useInsecureLookup = {0}", new Object[]{true});
}
return true;
}
return useInsecureLookup;
}
/**
* 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 {
private final List items = new LinkedList();
@Override
public synchronized void serviceAdded(ServiceDiscoveryEvent event) {
items.add(event.getPostEventServiceItem());
this.notifyAll();
}
@Override
public void serviceRemoved(ServiceDiscoveryEvent event) {
}
@Override
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
/**
* The Listener class for the LeaseRenewalManager.
*/
private final class LeaseListenerImpl implements LeaseListener {
private final ServiceRegistrar proxy;
public LeaseListenerImpl(ServiceRegistrar proxy) {
this.proxy = proxy;
}
/* When lease renewal fails, we discard the proxy */
@Override
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
/**
* Allows termination of LookupCacheImpl so blocking lookup can return
* quickly
*/
private static final class LookupCacheTerminator implements Runnable {
private final BlockingQueue cacheList = new LinkedBlockingQueue(20);
@Override
public void run() {
while (!cacheList.isEmpty() || !Thread.currentThread().isInterrupted()) {
try {
LookupCacheImpl cache = cacheList.take();
synchronized (cache) {
cache.terminate();
}
} catch (InterruptedException ex) {
if (logger.isLoggable(Level.FINEST))
log(Level.FINEST, "SDM lookup cache terminator interrupted", ex);
Thread.currentThread().interrupt();
}
}
}
void terminate(LookupCacheImpl cache) {
boolean added = cacheList.offer(cache);
if (!added) { // happens if cacheList is full.
// Do it yourself you lazy caller thread! Can't you see I'm busy?
synchronized (cache) {
cache.terminate();
}
}
}
}
/* Name of this component; used in config entry retrieval and the logger.*/
static final String COMPONENT_NAME
= "net.jini.lookup.ServiceDiscoveryManager";
/* Logger used by this utility. */
static final Logger logger = Logger.getLogger(COMPONENT_NAME);
/* The discovery manager to use (passed in, or create one). */
final DiscoveryManagement discMgr;
/* Indicates whether the discovery manager was created internally or not */
final boolean discMgrInternal;
/* The listener added to discMgr that receives DiscoveryEvents */
final DiscMgrListener discMgrListener;
/* The LeaseRenewalManager to use (passed in, or create one). */
final LeaseRenewalManager leaseRenewalMgr;
/* Contains all of the discovered lookup services (ServiceRegistrar). */
final WriteLock proxyRegSetWrite;
final ReadLock proxyRegSetRead;
final Set proxyRegSet;
/* Random number generator for use in lookup. */
final Random random = new Random();
/* Contains all of the instances of LookupCache that are requested. */
final WriteLock cachesWrite;
final ReadLock cachesRead;
private final List caches;
/* Flag to indicate if the ServiceDiscoveryManager has been terminated. */
private boolean bTerminated = false; //sync on this
private final Thread terminatorThread;
private final LookupCacheTerminator terminator;
/* Flag to indicate LookupCacheTerminator has been started */
private boolean started = false; // sync on terminatorThread
/* Object used to obtain the configuration items for this utility. */
final Configuration thisConfig;
/* Preparer for the proxies to the lookup services that are discovered
* and used by this utility.
*/
private final 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 final ProxyPreparer eventLeasePreparer;
/* Wait value used when handling the "service discard problem". */
final long discardWait;
/* Listener class for lookup service discovery notification. */
private class DiscMgrListener implements DiscoveryListener {
/* New or previously discarded proxy has been discovered. */
@Override
public void discovered(DiscoveryEvent e) {
ServiceRegistrar[] proxys = e.getRegistrars();
for (int i = 0, l = proxys.length; i < l; i++) {
/* Prepare each lookup service proxy before using it. */
if ( !useInsecureLookup() &&
!(proxys[i] instanceof SafeServiceRegistrar)) continue;
try {
proxys[i]
= (ServiceRegistrar) registrarPreparer.prepareProxy(proxys[i]);
if (logger.isLoggable(Level.FINEST)){
log(Level.FINEST, "ServiceDiscoveryManager - "
+ "discovered lookup service proxy prepared: {0}",
new Object []{proxys[i]}
);
}
} catch (Exception e1) {
if (logger.isLoggable(Level.INFO))
log(Level.INFO,
"failure preparing discovered ServiceRegistrar "
+ "proxy, discarding the proxy",
e1);
discard(proxys[i]);
continue;
}
ProxyReg reg = new ProxyReg(proxys[i]);
boolean added;
// Changed to only add to newProxys if actually new 7th Jan 2014
proxyRegSetWrite.lock();
try{
added = proxyRegSet.add(reg);
} finally {
proxyRegSetWrite.unlock();
}
if (added) cacheAddProxy(reg);
}//end loop
}//end DiscMgrListener.discovered
/* Previously discovered proxy has been discarded. */
@Override
public void discarded(DiscoveryEvent e) {
ServiceRegistrar[] proxys = e.getRegistrars();
List drops = new LinkedList();
for (int i = 0, l = proxys.length; i < l; i++) {
ProxyReg reg = removeReg(proxys[i]);
if (reg != null) { // this check can be removed.
drops.add(reg);
} else {
//River-337
if (logger.isLoggable(Level.SEVERE))
log(Level.SEVERE, "discard error, proxy was null");
//throw new RuntimeException("discard error");
}//endif
}//end loop
Iterator iter = drops.iterator();
while (iter.hasNext()) {
dropProxy(iter.next());
}//end loop
}//end DiscMgrListener.discarded
/**
* Discards a ServiceRegistrar through the discovery manager.
*/
private void discard(ServiceRegistrar proxy) {
discMgr.discard(proxy);
}//end discard
}//end class ServiceDiscoveryManager.DiscMgrListener
/**
* Adds the given proxy to all the caches maintained by the SDM.
*/
private void cacheAddProxy(ProxyReg reg) {
cachesRead.lock();
try{
Iterator iter = caches.iterator();
while (iter.hasNext()) {
LookupCacheImpl cache = (LookupCacheImpl) iter.next();
cache.addProxyReg(reg);
}//end loop
} finally {
cachesRead.unlock();
}
}//end cacheAddProxy
/**
* Removes the given proxy from all the caches maintained by the SDM.
*/
private void dropProxy(ProxyReg reg) {
cachesRead.lock();
try {
Iterator iter = caches.iterator();
while (iter.hasNext()) {
LookupCacheImpl cache = (LookupCacheImpl) iter.next();
cache.removeProxyReg(reg);
}//end loop
} finally {
cachesRead.unlock();
}
}//end dropProxy
/**
* Constructs an instance of ServiceDiscoveryManager
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.
*
* As stated in the class description, this class has three usage patterns:
*
*
* - 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
*
- 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
*
- the entity uses the
ServiceDiscoveryManager
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
*
*
* Although the first two usage patterns emphasize the use of a cache
* object, that cache is acquired only through an instance of the
* ServiceDiscoveryManager
class.
*
* It is important to note that some of the methods of this class ({@link net.jini.lookup.ServiceDiscoveryManager#createLookupCache
* createLookupCache} and the blocking 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}.
*
* In order to facilitate the exportation of the remote listener just
* described, the ServiceDiscoveryManager
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, it is the entity's responsibility 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(TM) 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.
*
* The default exporter 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.
*
* 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:
*
*
*
* 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
*
*
*
* It is important to note that when the remote event listener is exported
* under JRMP, unlike Jini ERI, the JRMP remote communication framework does
* not 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.
*
* 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:
*
*
- store the necessary class files in a JAR file
*
- make the class files in the JAR file preferred
* (see package
net.jini.loader.pref
for details)
* - run an HTTP server to serve up the JAR file to any requesting lookup
* service
*
- advertise the location of that JAR file by setting the
*
java.rmi.server.codebase
property of the entity to "point"
* at the JAR file
*
*
* For example, suppose an application consists of an entity that intends to
* use the ServiceDiscoveryManager
will run on a host named
* myHost. And suppose that the down-loadable JAR file
* named sdm-dl.jar that is provided in the distribution is
* located in the directory /files/jini/lib, and will be
* served by an HTTP server listening on port
* 8082. If the application is run with its codebase property
* set to
* -Djava.rmi.server.codebase="http://myHost:8082/sdm-dl.jar"
,
* the lookup service(s) should then be able to access the remote listener
* exported under JRMP by the ServiceDiscoveryManager
on behalf
* of the entity.
*
* If a mechanism for lookup services to access the remote listener exported
* by the ServiceDiscoveryManager
is not provided (either by
* the remote communication framework itself, or by some other means), the
* remote methods of the ServiceDiscoveryManager
- the methods
* involved in the two most important usage patterns of that utility - will
* be of no use.
*
* This constructor takes two arguments: an object that implements the
* DiscoveryManagement
interface and a reference to a
* LeaseRenewalManager
object. The constructor throws an
* IOException
because construction of a
* ServiceDiscoveryManager
may initiate the multicast discovery
* process, a process that can throw an IOException
.
*
* @param discoveryMgr the DiscoveryManagement
implementation
* through which notifications that indicate a lookup service has been
* discovered or discarded will be received. If the value of the argument is
* null
, then an instance of the
* LookupDiscoveryManager
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 LeaseRenewalManager
to use. A value of
* null
may be passed as the LeaseRenewalManager
* argument. If the value of the argument is null
, an instance
* of the LeaseRenewalManager
class will be created, initially
* managing no Lease
objects.
*
* @throws IOException because construction of a
* ServiceDiscoveryManager
may initiate the multicast discovery
* process which can throw an IOException
.
*
* @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 {
this(initial(discoveryMgr, leaseMgr, EmptyConfiguration.INSTANCE));
}//end constructor
/**
* Constructs an instance of this class, which is configured using the items
* retrieved through the given Configuration
, 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.
*
* For a more details, refer to the description of the alternate constructor
* of this class.
*
* This constructor takes three arguments: an object that implements the
* DiscoveryManagement
interface, a reference to an instance of
* the LeaseRenewalManager
class, and a
* Configuration
object. The constructor throws an
* IOException
because construction of a
* ServiceDiscoveryManager
may initiate the multicast discovery
* process, a process that can throw an IOException
. The
* constructor also throws a ConfigurationException
when an
* exception occurs while retrieving an item from the given
* Configuration
*
* @param discoveryMgr the DiscoveryManagement
implementation
* through which notifications that indicate a lookup service has been
* discovered or discarded will be received. If the value of the argument is
* null
, then an instance of the
* LookupDiscoveryManager
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 LeaseRenewalManager
to use. A value of
* null
may be passed as the LeaseRenewalManager
* argument. If the value of the argument is null
, an instance
* of the LeaseRenewalManager
class will be created, initially
* managing no Lease
objects.
* @param config the Configuration
*
* @throws IOException because construction of a
* ServiceDiscoveryManager
may initiate the multicast discovery
* process which can throw an IOException
.
*
* @throws net.jini.config.ConfigurationException indicates an exception
* occurred while retrieving an item from the given
* Configuration
*
* @throws java.lang.NullPointerException if null
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 {
this(init(discoveryMgr, leaseMgr, config));
}//end constructor
private ServiceDiscoveryManager(Initializer init) {
// Key's added only if absent.
this.proxyRegSet = new HashSet();
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
proxyRegSetRead = rwl.readLock();
proxyRegSetWrite = rwl.writeLock();
this.caches = new ArrayList(32);
ReentrantReadWriteLock rwl2 = new ReentrantReadWriteLock();
cachesWrite = rwl2.writeLock();
cachesRead = rwl2.readLock();
thisConfig = init.thisConfig;
registrarPreparer = init.registrarPreparer;
eventLeasePreparer = init.eventLeasePreparer;
bootstrapProxyPreparer = init.bootstrapProxyPreparer;
useInsecureLookup = init.useInsecureLookup;
leaseRenewalMgr = init.leaseRenewalMgr;
discardWait = init.discardWait;
discMgr = init.discMgr;
discMgrInternal = init.discMgrInternal;
discMgrListener = new DiscMgrListener();
discMgr.addDiscoveryListener(discMgrListener);
terminator = new LookupCacheTerminator();
terminatorThread = new Thread(terminator, "SDM lookup cache terminator");
terminatorThread.setDaemon(false);
}
/**
* Returns array of ServiceRegistrar created from the proxyRegSet
*/
private ServiceRegistrar[] buildServiceRegistrar() {
List proxys = new LinkedList();
proxyRegSetRead.lock();
try {
Iterator iter = proxyRegSet.iterator();
while (iter.hasNext()) {
ProxyReg reg = iter.next();
proxys.add(reg.getProxy());
}//end loop
} finally {
proxyRegSetRead.unlock();
}
return proxys.toArray(new ServiceRegistrar[proxys.size()]);
}//end buildServiceRegistrar
/**
* Queries each available lookup service in the set of lookup services
* managed by the ServiceDiscoveryManager
(the managed
* set) 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 lookup
method provided by the
* ServiceRegistrar
interface; employing the same
* template-matching scheme. Additionally, this method allows any entity to
* supply an object referred to as a filter. Such an object is a
* non-remote object that defines additional matching criteria that the
* ServiceDiscoveryManager
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.
*
* 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).
*
* This version of lookup
returns a single instance of
* ServiceItem
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 a
* service is returned, not which service.
*
* Note that, unlike other versions of lookup
provided by the
* ServiceDiscoveryManager
, this version does not
* block. 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
* lookup
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 lookup
, 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
* null
. 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 lookup
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 ServiceTemplate
corresponding to
* the object to use for template-matching when searching for desired
* services. If null
is input to this parameter, this method
* will use a wildcarded
* 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 ServiceItemFilter
containing
* matching criteria that should be applied in addition to the
* template-matching employed when searching for desired services. If
* null
is input to this parameter, then only template-matching
* will be employed to find the desired services.
*
* @return a single instance of ServiceItem
corresponding to a
* reference to a service that matches the criteria represented in the input
* parameters; or null
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 = buildServiceRegistrar();
int len = proxys.length;
if (len == 0) {
return null;
}
int rand = random.nextInt(Integer.MAX_VALUE) % 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);
Object [] matches;
if (useInsecureLookup()){
ServiceMatches sm = proxy.lookup(tmpl, maxMatches);
matches = sm.items;
} else {
matches = ((SafeServiceRegistrar)proxy).lookUp(tmpl, maxMatches);
}
if (matches == null) continue;
sItem = getMatchedServiceItem(matches, filter);
} catch (Exception e) {
if (logger.isLoggable(Level.INFO))
log(Level.INFO,
"Exception occurred during query, discarding proxy",
e);
discard(proxy);
}
if (sItem != null) {
return sItem; //Don't need to clone
}
}//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 lookup
method provided by the
* ServiceRegistrar
interface; employing the same
* template-matching scheme. Additionally, this method allows any entity to
* supply an object referred to as a filter. Such an object is a
* non-remote object that defines additional matching criteria that the
* ServiceDiscoveryManager
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.
*
* This version of lookup
returns a single instance of
* ServiceItem
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 a
* service is returned, not which service.
*
* Note that this version of lookup
provides a
* blocking feature that is controlled through the
* waitDur
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 waitDur
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 Thread
class. The intent of
* this mechanism is to allow the entity to interrupt this method in the
* same way it would a sleeping thread.
*
* 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 ServiceTemplate
corresponding to
* the object to use for template-matching when searching for desired
* services. If null
is input to this parameter, this method
* will use a wildcarded
* 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 ServiceItemFilter
containing
* matching criteria that should be applied in addition to the
* template-matching employed when searching for desired services. If
* null
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 null
. 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 null
.
*
* @return a single instance of ServiceItem
corresponding to a
* reference to a service that matches the criteria represented in the input
* parameters; or null
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
* Thread
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 null; // Make it clear we're returning null.
} finally {
if (cache != null) {
terminator.terminate(cache);
}
}
}//end lookup
/**
* The createLookupCache
method allows the client-like entity
* to request that the ServiceDiscoveryManager
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.
*
* This method returns an object of type LookupCache
. 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.
*
* 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.
*
* Although not required, a common usage pattern for entities that wish to
* use the LookupCache
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 null
, it is the equivalent of passing
* a ServiceTemplate
constructed with all null
* arguments (all wildcards).
* @param filter used to apply additional matching criteria to any
* ServiceItem
found through template-matching. If this value
* is null
, 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 null
, 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 getDiscoveryManager
method will return an object that
* implements the DiscoveryManagement
interface. The object
* returned by this method provides the ServiceDiscoveryManager
* 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 getLeaseRenewalManager
method will return an instance of
* the LeaseRenewalManager
class. The object returned by this
* method manages the leases requested and held by the
* ServiceDiscoveryManager
. In general, these leases correspond
* to the registrations made by the ServiceDiscoveryManager
* with the event mechanism of each lookup service in the managed set.
*
* @return LeaseRenewalManager for this instance of the
* ServiceDiscoveryManager
.
* @see net.jini.lease.LeaseRenewalManager
*/
public LeaseRenewalManager getLeaseRenewalManager() {
checkTerminated();
return leaseRenewalMgr;
}//end getLeaseRenewalManager
/**
* The terminate
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 ServiceDiscoveryManager
.
*
* For each instance of LookupCache
created and managed by the
* ServiceDiscoveryManager
, the terminate
method
* will do the following:
*
* - Either remove all listener objects registered for receipt of
*
DiscoveryEvent
objects or, if the discovery manager employed
* by the ServiceDiscoveryManager
was created by the
* ServiceDiscoveryManager
itself, terminate all discovery
* processing being performed by that manager object on behalf of the
* entity.
*
*
- Cancel all event leases granted by each lookup service in the managed
* set of lookup services.
*
*
- Un-export all remote listener objects registered with each lookup
* service in the managed set.
*
*
- Terminate all threads involved in the process of retrieving and
* storing references to discovered services of interest.
*
* Calling any method after the termination will result in an
* IllegalStateException
.
*
* @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
terminatorThread.interrupt();
/* Terminate all caches: cancel event leases, un-export listeners */
List terminate;
cachesRead.lock();
try{
terminate = new ArrayList(caches);
} finally {
cachesRead.unlock();
}
Iterator iter = terminate.iterator();
while (iter.hasNext()) {
LookupCacheImpl cache = (LookupCacheImpl) iter.next();
cache.terminate();
}//end loop
leaseRenewalMgr.close();
}//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 lookup
method provided by the
* ServiceRegistrar
interface; employing the same
* template-matching scheme. Additionally, this method allows any entity to
* supply an object referred to as a filter. Such an object is a
* non-remote object that defines additional matching criteria that the
* ServiceDiscoveryManager
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.
*
* 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).
*
* This version of lookup
returns an array of instances
* of ServiceItem
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
* maxMatches
parameter, but may be less.
*
* Note that this version of lookup
does not provide a
* blocking feature. That is, this version will return immediately
* with whatever number of service references it can find, up to the number
* indicated in the maxMatches
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
* lookup
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
* lookup
, 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 discovered 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 lookup
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 ServiceTemplate
corresponding to
* the object to use for template-matching when searching for desired
* services. If null
is input to this parameter, this method
* will use a wildcarded 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 ServiceItemFilter
containing
* matching criteria that should be applied in addition to the
* template-matching employed when searching for desired services. If
* null
is input to this parameter, then only template-matching
* will be employed to find the desired services.
*
* @return an array of instances of ServiceItem
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 = buildServiceRegistrar();
int len = proxys.length;
List sItemSet = new ArrayList(len);
if (len > 0) {
/* loop thru the set of lookups, randomly selecting each lookup */
int rand = (random.nextInt(Integer.MAX_VALUE)) % 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). */
Object [] result;
if (useInsecureLookup()){
ServiceMatches matches = proxy.lookup(tmpl, max);
result = matches.items;
} else {
result = ((SafeServiceRegistrar)proxy).lookUp(tmpl, max);
}
if (result == null) continue;
int nItems = result.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 = (random.nextInt(Integer.MAX_VALUE)) % nItems;
for (int j = 0; j < nItems; j++) {
Object obj = result[(j + r) % nItems];
if (obj == null) continue;
ServiceItem sItem;
if (useInsecureLookup()){
sItem = (ServiceItem) obj;
if (!filterPassed(sItem, filter)) continue;
} else {
sItem = check(obj, filter, bootstrapProxyPreparer);
if (sItem == null) continue;
}
if (!isArrayContainsServiceItem(sItemSet, sItem)) {
sItemSet.add(sItem);
}
if (sItemSet.size() >= maxMatches) {
return sItemSet.toArray(new ServiceItem[sItemSet.size()]);
}
}
} catch (Exception e) {
if (logger.isLoggable(Level.INFO))
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 lookup
method provided by the
* ServiceRegistrar
interface; employing the same
* template-matching scheme. Additionally, this method allows any entity to
* supply an object referred to as a filter. Such an object is a
* non-remote object that defines additional matching criteria that the
* ServiceDiscoveryManager
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.
*
* This version of lookup
returns an array of instances
* of ServiceItem
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
* maxMatches
parameter, but may be less.
*
* Note that this version of lookup
provides a
* blocking feature that is controlled through the
* waitDur
parameter in conjunction with the
* minMatches
and the maxMatches
parameters. This
* method will not return until one of the following occurs:
*
*
* - the number of matching services found on the first attempt is
* greater than or equal to the value of the
minMatches
* parameter, in which case this method returns each of the services found
* up to the value of the maxMatches
parameter
* - the number of matching services found after the first attempt
* (that is, after the method enters the "wait state") is at least as great
* as the value of the
minMatches
parameter in which case this
* method returns each of the services found up to the value of the
* maxMatches
parameter
* - the amount of time that has passed since this method entered the
* wait state exceeds the value of the
waitDur
parameter, in
* which case this method returns all of the currently discovered services
*
*
* The purpose of the minMatches
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.
*
* 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 Thread
* class. The intent of this mechanism is to allow the entity to interrupt
* this method in the same way it would a sleeping thread.
*
* 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 ServiceTemplate
corresponding to
* the object to use for template-matching when searching for desired
* services. If null
is input to this parameter, this method
* will use a
* wildcarded 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 ServiceItemFilter
containing
* matching criteria that should be applied in addition to the
* template-matching employed when searching for desired services. If
* null
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 maxMatches
.
*
* @return an array of instances of ServiceItem
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
* Thread
class.
*
* @throws java.lang.IllegalArgumentException this exception occurs when one
* of the following conditions is satisfied:
*
*
- the
minMatches
parameter is non-positive
* - the
maxMatches
parameter is non-positive
* - the value of
maxMatches
is less than
* the value of minMatches
*
*
* @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");
}
long delay = System.currentTimeMillis();
ServiceItem[] sItems = lookup(tmpl, maxMatches, filter);
if (sItems.length >= minMatches) {
return sItems;
}
List sItemSet = new LinkedList();
for (int i = 0, l = sItems.length; i < l; i++) {
//if(!sItemSet.contains(sItems[i])
//sItemSet.add(sItems[i]);
if (!isArrayContainsServiceItem(sItemSet, sItems[i])) {
sItemSet.add(sItems[i]);
}//endif
}//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;
synchronized (cacheListener) { // uncontended lock.
delay = (System.currentTimeMillis() - delay) + 1; // Calculate initial time delay in ms.
cache = createLookupCache(tmpl, filter, cacheListener, waitDur);
long duration = cache.getLeaseDuration();
while (duration > delay) { // Some milli's to spare to ensure we return in reasonable time.
cacheListener.wait(duration - delay);
ServiceItem items[] = cacheListener.getServiceItem();
for (int i = 0, l = items.length; i < l; i++) {
if (!isArrayContainsServiceItem(sItemSet, items[i])) {
sItemSet.add(items[i]);
}//endif
}//end loop
if (sItemSet.size() >= minMatches) {//River-466
break;
}
duration = cache.getLeaseDuration();
}//end loop
}//end sync(cacheListener)
// Termination is now performed by a dedicated thread to ensure
// Remote method call doesn't take too long.
terminator.terminate(cache);
if (sItemSet.size() > maxMatches) {
// Discard some matches
ServiceItem[] r = new ServiceItem[maxMatches];
// Iterator is faster for LinkedList.
Iterator it = sItemSet.iterator();
for (int i = 0; it.hasNext() && i < maxMatches; i++) {
r[i] = it.next();
}
return r;
}
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(Object [] sm,
ServiceItemFilter filter)
{
int len = sm.length;
if (len > 0) {
int rand = random.nextInt(Integer.MAX_VALUE) % len;
for (int i = 0; i < len; i++) {
Object sItem = sm[(i + rand) % len];
if (sItem == null) continue;
ServiceItem item = null;
if (useInsecureLookup()){
item = (ServiceItem) sItem;
if (filterPassed(item, filter)) return item;
} else {
item = check(sItem, filter, bootstrapProxyPreparer);
if (item == null) continue;
return item;
}
}//end loop
}//endif
return null;
}//end getMatchedServiceItem
/**
*
* @param bootstrapProxy
* @param filter
* @param bootstrapPreparer
* @param serviceProxyPreparer
* @return a new ServiceItem if preparation and filter are successful, or
* null.
*/
private ServiceItem check(Object bootstrapProxy, ServiceItemFilter filter,
ProxyPreparer bootstrapPreparer)
{
try {
if (!(bootstrapProxy instanceof ServiceAttributesAccessor) &&
!(bootstrapProxy instanceof ServiceIDAccessor) &&
!(bootstrapProxy instanceof ServiceProxyAccessor)) return null;
// The bootstrap proxy preparer can authenticate, dynamically
// grant permission to download and deserialize the service proxy.
// The service proxy can be trusted, however no constraints have
// been applied to the service proxy yet.
Object preparedProxy = bootstrapPreparer.prepareProxy(bootstrapProxy);
// getServiceAttributes may try to download code, the bootstrapPreparer
// should have authenticated and authorised any code downloads.
Entry[] serviceAttributes =
((ServiceAttributesAccessor) preparedProxy).getServiceAttributes();
ServiceID serviceID = ((ServiceIDAccessor) preparedProxy).serviceID();
ServiceItem item = new ServiceItem(serviceID, bootstrapProxy, serviceAttributes);
try {
if (filter == null){ // No local filter, retrieve service proxy.
item.service =
((ServiceProxyAccessor) preparedProxy).getServiceProxy();
return item;
}
// The ServiceItemFilter should mutate the ServiceItem.service
// field after preparing the proxy and retrieving the
// service proxy using ServiceProxyAccessor.
if (filter.check(item)) return item;
} catch (SecurityException ex) {
if (logger.isLoggable(Level.FINE))
log(Level.FINE,
"Exception thrown while filtering ServiceItem containing bootstrap proxy, downloading service proxy and trying again, suggest rewriting your filter", ex);
// If ClassCastException, then filter has attempted to cast the
// bootstrap proxy to the service type, it is likely to be
// an older filter implementation that doesn't know about
// ServiceProxyAccessor.
// If SecurityException, then proxy preparation failed, which
// is probably due to the filter not expecting a bootstrap
// proxy and attempting to apply method constraints.
// SOLUTION: Download service proxy and retry filter.
item.service =
((ServiceProxyAccessor) preparedProxy).getServiceProxy();
if (filter.check(item)) return item;
} catch (ClassCastException ex){
if (logger.isLoggable(Level.FINE))
log(Level.FINE,
"Exception thrown while filtering ServiceItem containing bootstrap proxy, downloading service proxy and trying again, suggest rewriting your filter", ex);
// If ClassCastException, then filter has attempted to cast the
// bootstrap proxy to the service type, it is likely to be
// an older filter implementation that doesn't know about
// ServiceProxyAccessor.
// If SecurityException, then proxy preparation failed, which
// is probably due to the filter not expecting a bootstrap
// proxy and attempting to apply method constraints.
// SOLUTION: Download service proxy and retry filter.
item.service =
((ServiceProxyAccessor) preparedProxy).getServiceProxy();
if (filter.check(item)) return item;
}
return null;
} catch (IOException ex) {
if (logger.isLoggable(Level.FINE))
log(Level.FINE, "IOException thrown while checking bootstrapProxy, filtering and downloading service proxy", ex);
return null;
}
}
/**
* Creates a LookupCache with specific lease duration.
*/
private LookupCacheImpl createLookupCache(ServiceTemplate tmpl,
ServiceItemFilter filter,
ServiceDiscoveryListener listener,
long leaseDuration)
throws RemoteException {
/* Atomic start of terminator */
synchronized (terminatorThread) {
if (!started) {
terminatorThread.start();
}
started = true;
}
if (tmpl == null) {
tmpl = new ServiceTemplate(null, null, null);
}
LookupCacheImpl cache = new LookupCacheImpl(tmpl, filter, listener, leaseDuration, this, useInsecureLookup());
cache.initCache();
cachesWrite.lock();
try {
caches.add(cache);
} finally {
cachesWrite.unlock();
}
if (logger.isLoggable(Level.FINEST))
log(Level.FINE, "ServiceDiscoveryManager - LookupCache created");
return cache;
}//end createLookupCache
/**
* LookupCache removes itself once it has been terminated.
* @param cache
* @return true if removed.
*/
boolean removeLookupCache(LookupCache cache){
cachesWrite.lock();
try {
return caches.remove(cache);
} finally {
cachesWrite.unlock();
}
}
/**
* Removes and returns element from proxyRegSet that corresponds to the
* given proxy.
*/
ProxyReg removeReg(ServiceRegistrar proxy) {
ProxyReg pReg = new ProxyReg(proxy);
proxyRegSetWrite.lock();
try{
if(proxyRegSet.remove(pReg)) return pReg;
return null;
} finally {
proxyRegSetWrite.unlock();
}
}//end removeReg
/**
* 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 Throwable
;
* 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 IllegalStateException
. Since
* this method is called from within the tasks run by this utility, and
* since propagating an IllegalStateException
out into the
* ThreadGroup of those tasks is undesirable, this method does not propagate
* IllegalStateException
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.
*/
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))) {
logp(logLevel, sourceClass, sourceMethod, msg, e);
}//endif
try {
if (discardProxy) {
discard(proxy);
}
} catch (IllegalStateException e1) {
if (logger.isLoggable(logLevel)) {
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.
*/
void cancelLease(Lease lease) {
try {
leaseRenewalMgr.cancel(lease);
} catch (Exception e) {
if (logger.isLoggable(Level.FINER))
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,
* EventReg
.
*
* This method is called from the RegisterListenerTask
. If a
* RemoteException
occurs during the event registration
* attempt, this method discards the lookup service and returns
* null
.
*/
EventReg registerListener(ServiceRegistrar proxy,
ServiceTemplate tmpl,
RemoteEventListener listenerProxy,
long duration) throws RemoteException {
/* Register with the event mechanism of the given lookup service */
EventRegistration e;
int transition = (ServiceRegistrar.TRANSITION_NOMATCH_MATCH
| ServiceRegistrar.TRANSITION_MATCH_NOMATCH
| ServiceRegistrar.TRANSITION_MATCH_MATCH);
if (useInsecureLookup()){
e = proxy.notify(tmpl, transition, listenerProxy, null, duration);
} else {
e = ((SafeServiceRegistrar)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);
if (logger.isLoggable(Level.FINEST))
log(Level.FINEST,
"ServiceDiscoveryManager - proxy to event registration lease prepared: {0}",
new Object []{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.
*/
synchronized void checkTerminated() {
if (bTerminated) {
throw new IllegalStateException("service discovery manager was terminated");
}//endif
}//end checkTerminated
/**
* Determines if the given ServiceItem is an element of the given array.
*/
static private boolean isArrayContainsServiceItem(List 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
/**
* Initializer for ServiceDiscoveryManager
*/
private static class Initializer {
Configuration thisConfig;
ProxyPreparer registrarPreparer;
ProxyPreparer eventLeasePreparer;
ProxyPreparer bootstrapProxyPreparer;
LeaseRenewalManager leaseRenewalMgr;
long discardWait;
DiscoveryManagement discMgr;
boolean discMgrInternal;
boolean useInsecureLookup;
}
private static Initializer initial(
DiscoveryManagement discoveryMgr,
LeaseRenewalManager leaseMgr,
Configuration config)
throws IOException {
try {
return init(discoveryMgr, leaseMgr, config);
} catch (ConfigurationException e) {
/* This should never happen */
throw new IOException(e);
}
}
/* Convenience method that encapsulates the retrieval of the configurable
* items from the given Configuration
object.
*/
private static Initializer init(DiscoveryManagement discoveryMgr,
LeaseRenewalManager leaseMgr,
Configuration config)
throws IOException, ConfigurationException {
/* Retrieve configuration items if applicable */
if (config == null) {
throw new NullPointerException("config is null");
}
Initializer init = new Initializer();
init.thisConfig = config;
/* Proxy preparers */
init.registrarPreparer = init.thisConfig.getEntry(COMPONENT_NAME,
"registrarPreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
init.eventLeasePreparer = init.thisConfig.getEntry(COMPONENT_NAME,
"eventLeasePreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
init.bootstrapProxyPreparer = init.thisConfig.getEntry(COMPONENT_NAME,
"bootstrapPreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
/* Lease renewal manager */
init.leaseRenewalMgr = leaseMgr;
if (init.leaseRenewalMgr == null) {
try {
init.leaseRenewalMgr
= init.thisConfig.getEntry(COMPONENT_NAME,
"leaseManager",
LeaseRenewalManager.class);
} catch (NoSuchEntryException e) { /* use default */
init.leaseRenewalMgr = new LeaseRenewalManager(init.thisConfig);
}
}//endif
/* Wait value for the "service discard problem". */
init.discardWait = (init.thisConfig.getEntry(COMPONENT_NAME,
"discardWait",
long.class,
600000L));
/* Discovery manager */
init.discMgr = discoveryMgr;
if (init.discMgr == null) {
init.discMgrInternal = true;
try {
init.discMgr = init.thisConfig.getEntry(COMPONENT_NAME,
"discoveryManager",
DiscoveryManagement.class);
} catch (NoSuchEntryException e) { /* use default */
init.discMgr = new LookupDiscoveryManager(new String[]{""}, null, null, init.thisConfig);
}
}//endif
init.useInsecureLookup = (init.thisConfig.getEntry(COMPONENT_NAME,
"useInsecureLookup",
Boolean.class,
Boolean.FALSE));
return init;
}//end init
/**
* Applies the given filter
to the given item
, and
* returns true
if the filter
returns a
* pass
value; otherwise, returns false
.
*
* Note that as described in the specification of
* ServiceItemFilter
, when the item
passes the
* filter
, the service
field of the
* item
is replaced with the filtered form of the object
* previously contained in that field. Additionally, if the
* filter
returns indefinite
, then as specified,
* the service
field is replaced with null
(in
* which case, this method returns false
).
*
* This method is used by the non-blocking version(s) of the
* lookup
method of the ServiceDiscoveryManager
,
* as well as when second-stage filtering is performed in the
* LookupCache
.
*/
static boolean filterPassed(ServiceItem item, ServiceItemFilter filter) {
if ((item == null) || (item.service == null)) {
return false;
}
if (filter == null) {
return true;
}
return filter.check(item);
}//end filterPassFail
}//end class ServiceDiscoveryManager