/* * 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.discovery; import com.sun.jini.config.Config; import com.sun.jini.discovery.Discovery; import com.sun.jini.discovery.DiscoveryConstraints; import com.sun.jini.discovery.DiscoveryProtocolException; import com.sun.jini.discovery.EncodeIterator; import com.sun.jini.discovery.MulticastAnnouncement; import com.sun.jini.discovery.MulticastRequest; import com.sun.jini.discovery.UnicastResponse; import com.sun.jini.discovery.internal.MultiIPDiscovery; import com.sun.jini.logging.Levels; import com.sun.jini.logging.LogUtil; import com.sun.jini.thread.TaskManager; import com.sun.jini.thread.WakeupManager; import com.sun.jini.thread.WakeupManager.Ticket; import java.io.IOException; import java.io.InterruptedIOException; import java.net.DatagramPacket; import java.net.MulticastSocket; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.rmi.RemoteException; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.LogRecord; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.EmptyConfiguration; import net.jini.config.NoSuchEntryException; import net.jini.constraint.BasicMethodConstraints; import net.jini.core.constraint.InvocationConstraints; import net.jini.core.constraint.MethodConstraints; import net.jini.core.discovery.LookupLocator; import net.jini.core.lookup.ServiceID; import net.jini.core.lookup.ServiceRegistrar; import net.jini.io.UnsupportedConstraintException; import net.jini.security.BasicProxyPreparer; import net.jini.security.ProxyPreparer; import net.jini.security.Security; import net.jini.security.SecurityContext; /** * This class is a helper utility class that encapsulates the functionality * required of an entity that wishes to employ multicast discovery to * find lookup services located within the entity's "multicast radius" * (roughly, the number of hops beyond which neither the multicast requests * from the entity, nor the multicast announcements from the lookup service, * will propagate). This class helps make the process of acquiring references * to lookup services - based on no information other than lookup service * group membership - much simpler for both services and clients. * * @com.sun.jini.impl <!-- Implementation Specifics --> * * The following implementation-specific items are discussed below: * <ul><li> <a href="#ldConfigEntries">Configuring LookupDiscovery</a> * <li> <a href="#ldLogging">Logging</a> * </ul> * * <a name="ldConfigEntries"> * <p> * <b><font size="+1">Configuring LookupDiscovery</font></b> * <p> * </a> * * This implementation of <code>LookupDiscovery</code> supports the * following configuration entries; where each configuration entry name * is associated with the component name * <code>net.jini.discovery.LookupDiscovery</code>. Note that the * configuration entries specified here are specific to this implementation * of <code>LookupDiscovery</code>. Unless otherwise stated, each entry * is retrieved from the configuration only once per instance of this * utility, where each such retrieval is performed in the constructor. * * <a name="discoveryConstraints"> * <table summary="Describes the discoveryConstraints * 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>discoveryConstraints</code></font> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> {@link net.jini.core.constraint.MethodConstraints} * * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>null</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> Constraints to apply to the multicast request, multicast * announcement and unicast discovery protocols. Multicast * request constraints are derived by calling * {@link MethodConstraints#getConstraints getConstraints} on the * obtained <code>MethodConstraints</code> instance with a * <code>Method</code> object for the * {@link com.sun.jini.discovery.DiscoveryConstraints#multicastRequest * multicastRequest} method; multicast announcement and unicast * discovery constraints are similarly obtained by passing * <code>Method</code> objects for the * {@link com.sun.jini.discovery.DiscoveryConstraints#multicastAnnouncement * multicastAnnouncement} and * {@link com.sun.jini.discovery.DiscoveryConstraints#unicastDiscovery * unicastDiscovery} methods, respectively. A <code>null</code> * value is interpreted as mapping all methods to empty * constraints. * <p> * This class supports the use of the following constraint types * to control discovery behavior: * <ul> * <li> {@link com.sun.jini.discovery.DiscoveryProtocolVersion}: * this constraint can be used to control which version(s) * of the multicast request, multicast announcement and * unicast discovery protocols are used. * <li> {@link com.sun.jini.discovery.MulticastMaxPacketSize}: * this constraint can be used to control the maximum size * of multicast request packets to send; it can also be * used to specify the size of the buffer used to receive * incoming multicast announcement packets. * <li> {@link com.sun.jini.discovery.MulticastTimeToLive}: this * constraint can be used to control the time to live (TTL) * value set on outgoing multicast request packets. * <li> {@link com.sun.jini.discovery.UnicastSocketTimeout}: * this constraint can be used to control the read timeout * set on sockets over which unicast discovery is * performed. * <li> {@link net.jini.core.constraint.ConnectionRelativeTime}: * this constraint can be used to control the relative * connection timeout set on sockets over which unicast * discovery is performed. * <li> {@link net.jini.core.constraint.ConnectionAbsoluteTime}: * this constraint can be used to control the absolute * connection timeout set on sockets over which unicast * discovery is performed. * </ul> * Constraints other than those listed above are passed on to the * underlying implementations of versions 1 and 2 of the discovery * protocols. * </table> * * <a name="finalMulticastRequestInterval"> * <table summary="Describes the finalMulticastRequestInterval * 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>finalMulticastRequestInterval</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*60*1000 (2 minutes)</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> With respect to when this utility is started, as well * as when the set of groups to discover is changed, this * entry represents the number of milliseconds to wait * after sending the n-th multicast request where n is * equal to the value of the <a href="#multicastRequestMax"> * <code>multicastRequestMax</code></a> entry of this component. * </table> * <a name="initialMulticastRequestDelayRange"> * <table summary="Describes the initialMulticastRequestDelayRange * 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>initialMulticastRequestDelayRange</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>0 milliseconds</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> With respect to when this utility is started, this entry controls * how long to wait before sending out the first multicast request. * If the value is positive, the first request will be delayed by a random * value between <code>0</code> and * <code>initialMulticastRequestDelayRange</code> * milliseconds. Subsequent request intervals are controlled by the * <a href="#multicastRequestInterval"> * <code>multicastRequestInterval</code></a> entry. Note that this entry * only has effect when this utility is initialized. The first multicast * request is not delayed if the groups to discover are subsequently * changed. * </table> * <a name="multicastAnnouncementInterval"> * <table summary="Describes the multicastAnnouncementInterval * 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>multicastAnnouncementInterval</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*60*1000 (2 minutes)</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> A lookup service will send out multicast packets * announcing its existence every N milliseconds; for some * value of N. The value of this entry controls how often * this utility examines the multicast announcements from * previously discovered lookup services for <i>liveness</i>. * </table> * * <a name="multicastInterfaceRetryInterval"> * <table summary="Describes the multicastInterfaceRetryInterval * 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>multicastInterfaceRetryInterval</code></font> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>int</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>5*60*1000 (5 minutes)</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> With respect to any network interface this utility is configured * to use to send and receive multicast packets (see entry * <a href="#multicastInterfaces"> * <code>multicastInterfaces</code></a>), if failure is encountered * upon the initial attempt to set the interface or join the * desired multicast group, this utility will retry the failed * interface every <a href="#multicastInterfaceRetryInterval"> * <code>multicastInterfaceRetryInterval</code></a> milliseconds * until success is encountered. * </table> * * <a name="multicastInterfaces"> * <table summary="Describes the multicastInterfaces * 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>multicastInterfaces</code></font> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> {@link java.net.NetworkInterface NetworkInterface[]} * * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>new </code> * {@link java.net.NetworkInterface NetworkInterface[]} *   <code>{all currently supported interfaces}</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> Each network interface that is represented by an element * in the array corresponding to this configuration item * will be used to send and receive multicast packets when * this utility is participating in the multicast discovery * process. When not set, this utility will use all of the * network interfaces in the system. When this entry is set * to a zero length array, multicast discovery is effectively * <b><i>disabled</i></b>. And when set to <code>null</code>, * the interface to which the operating system defaults will be * used. * </table> * * <a name="multicastRequestHost"> * <table summary="Describes the multicastRequestHost * 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>multicastRequestHost</code></font> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>String</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> * <code>{@link java.net.InetAddress}.getLocalHost().getHostAddress()</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> This entry specifies the host name to include in multicast * requests if participating in <b><i>version 2</i></b> of the * multicast request protocol. The name cannot be <code>null</code>. * </table> * * <a name="multicastRequestInterval"> * <table summary="Describes the multicastRequestInterval * 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>multicastRequestInterval</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>5000</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> With respect to when this utility is started, as well as * when the set of groups to discover is changed, this entry * represents the number of milliseconds to wait after * sending the n-th multicast request, and before sending * the (n+1)-st request, where n is less than the value of the * <a href="#multicastRequestMax"><code>multicastRequestMax</code></a> * entry of this component. * </table> * * <a name="multicastRequestMax"> * <table summary="Describes the multicastRequestMax * 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>multicastRequestMax</code></font> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>int</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>7</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> The maximum number multicast requests to send when this * utility is started for the first time, and whenever the * groups to discover is changed. * </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. * <p> * This preparer should perform all operations required to use a * newly received proxy to a lookup service, which may including * verifying trust in the proxy, granting permissions, and setting * constraints. * <p> * The following methods of the * {@link net.jini.core.lookup.ServiceRegistrar ServiceRegistrar} * returned by this preparer are invoked by this implementation of * <code>LookupDiscovery</code>: * <ul> * <li>{@link net.jini.core.lookup.ServiceRegistrar#getServiceID * getServiceID} * <li>{@link net.jini.core.lookup.ServiceRegistrar#getGroups * getGroups} * <li>{@link net.jini.core.lookup.ServiceRegistrar#getLocator * getLocator} * </ul> * </table> * * <a name="taskManager"> * <table summary="Describes the taskManager configuration entry" * border="0" cellpadding="2"> * <tr valign="top"> * <th scope="col" summary="layout"> <font size="+1">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"> * <code>taskManager</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}(15, (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 this utility. The default manager creates a * maximum of 15 threads, waits 15 seconds before removing * idle threads, and uses a load factor of 1.0 when * determining whether to create a new thread. This object * should not be shared with other components in the * application that employs this utility. * </table> * <a name="unicastDelayRange"> * <table summary="Describes the unicastDelayRange * 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>unicastDelayRange</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>0 milliseconds</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> Controls how long this utility will wait before sending out * unicast discovery requests. If the value is positive, any * unicast discovery request that it initiates will be delayed by a * random value between <code>0</code> and * <code>unicastDelayRange</code> milliseconds. A typical use of this * entry would be to achieve a more uniform distribution of unicast * discovery requests to a lookup service, when a large number of * <code>LookupDiscovery</code> instances simultaneously receive multicast * announcements from the lookup service. * </table> * <a name="wakeupManager"> * <table summary="Describes the wakeupManager configuration entry" * border="0" cellpadding="2"> * <tr valign="top"> * <th scope="col" summary="layout"> <font size="+1">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"> * <code>wakeupManager</code></font> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> {@link com.sun.jini.thread.WakeupManager} * * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>new * {@link com.sun.jini.thread.WakeupManager#WakeupManager( * com.sun.jini.thread.WakeupManager.ThreadDesc) * WakeupManager}(new * {@link com.sun.jini.thread.WakeupManager.ThreadDesc}(null,true))</code> * * <tr valign="top"> <td>   <th scope="row" align="right"> * Description: * <td> Object used to schedule unicast discovery requests that are * delayed using the * <a href="#unicastDelayRange"><code>unicastDelayRange</code></a> * configuration entry of this utility. This entry is processed only * if <code>unicastDelayRange</code> has a positive value. * </table> * <a name="ldLogging"> * <p> * <b><font size="+1">Logging</font></b> * <p> * </a> * * This implementation of <code>LookupDiscovery</code> uses the {@link Logger} * named <code>net.jini.discovery.LookupDiscovery</code> to log information * at the following logging levels: <p> * * <table border="1" cellpadding="5" * summary="Describes the information logged by LookupDiscovery, and * the levels at which that information is logged"> * * <caption halign="center" valign="top"> * <b><code>net.jini.discovery.LookupDiscovery</code></b> * </caption> * * <tr> <th scope="col"> Level</th> * <th scope="col"> Description</th> * </tr> * * <tr> * <td>{@link java.util.logging.Level#SEVERE SEVERE}</td> * <td> * when this utility is configured to use either the default network * interface assigned by the system, or a specific list of network * interfaces, if one of those interfaces is bad or not configured for * multicast, or if a runtime exception occurs while either sending * multicast requests, or while configuring one of the interfaces to * receive multicast announcements, that fact will be logged at this * level * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#INFO INFO}</td> * <td> * when any exception other than an <code>InterruptedIOException</code>, * <code>InterruptedException</code> or * <code>UnsupportedConstraintException</code> occurs in a thread or task * while attempting to marshal an outgoing multicast request * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#INFO INFO}</td> * <td> * when any exception other than an <code>InterruptedIOException</code> or * <code>SocketTimeoutException</code> occurs in a non-interrupted thread * while attempting to receive an incoming multicast packet * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#INFO INFO}</td> * <td> * when any exception other than an <code>InterruptedIOException</code> * occurs while attempting unicast discovery * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#INFO INFO}</td> * <td> * when this utility is configured to use either the default network * interface assigned by the system, or a specific list of network * interfaces, with respect to any such interface, if failure is * encountered upon the initial attempt to set the interface or join * the desired multicast group, the interface will be periodically * retried, and successful recovery will be logged at this level * </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#CONFIG CONFIG}</td> * <td> * when the <code>multicastInterfaces</code> entry is configured to * be <code>null</code>, multicast packets will be sent and received * through the default network interface assigned by the system, and * that fact will be logged at this level * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#CONFIG CONFIG}</td> * <td> * when the <code>multicastInterfaces</code> entry is configured to * be a zero length array, multicast discovery will be disabled, and * and that fact will be logged at this level * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#CONFIG CONFIG}</td> * <td> * when the <code>multicastInterfaces</code> entry contains a specific * list of network interfaces, multicast packets will be sent and * received through only the network interfaces contained in that list, * and those interfaces will be logged at this level * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#CONFIG CONFIG}</td> * <td> * when the <code>multicastInterfaces</code> entry is excluded from * the configuration, multicast packets will be sent and received * through all interfaces in the system, and those interfaces will * be logged at this level * </td> * </tr> * <tr> * <td>{@link com.sun.jini.logging.Levels#FAILED FAILED}</td> * <td> * when an <code>UnknownHostException</code> occurs while determining * the <code>multicastRequestHost</code>, but the caller does not have * permissions to retrieve the local host name. The original * <code>UnknownHostException</code> with the host name information * is logged * </td> * <tr> * <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td> * <td> * when this utility is configured to use all network interfaces enabled * in the system, if one of those interfaces is bad or not configured for * multicast, or if a runtime exception occurs while either sending * multicast requests, or while configuring one of the interfaces to * receive multicast announcements, that fact will be logged at this * level * </td> * </tr> * <tr> * <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td> * <td> * when any exception occurs while attempting to unmarshal an incoming * multicast announcement * </td> * </tr> * <tr> * <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td> * <td> * when an <code>UnsupportedConstraintException</code> occurs while * marshalling an outgoing multicast request, indicating that the provider * that threw the exception will not be used for encoding that request * </td> * </tr> * <tr> * <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td> * <td> * when an <code>IOException</code> occurs upon attempting to close the * socket after the thread that listens for multicast responses is asked * to terminate * </td> * </tr> * <tr> * <td>{@link com.sun.jini.logging.Levels#HANDLED HANDLED}</td> * <td> * when an exception is handled during unicast discovery * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#FINE FINE}</td> * <td> * when this utility is configured to use all network interfaces enabled * in the system, with respect to any such interface, if failure is * encountered upon the initial attempt to set the interface or join * the desired multicast group, the interface will be periodically * retried, and successful recovery will be logged at this level * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#FINEST FINEST}</td> * <td>whenever any thread or task is started</td> * </tr> * <tr> * <td>{@link java.util.logging.Level#FINEST FINEST}</td> * <td> * whenever any thread (except the <code>Notifier</code> thread) or task * completes successfully * </td> * </tr> * <tr> * <td>{@link java.util.logging.Level#FINEST FINEST}</td> * <td>whenever a discovered, discarded, or changed event is sent</td> * </tr> * <tr> * <td>{@link java.util.logging.Level#FINEST FINEST}</td> * <td>whenever a proxy is prepared</td> * </tr> * </table> * <p> * * @author Sun Microsystems, Inc. * * @see net.jini.core.lookup.ServiceRegistrar * @see DiscoveryChangeListener * @see DiscoveryManagement * @see DiscoveryGroupManagement * @see DiscoveryListener * @see DiscoveryEvent * @see DiscoveryPermission */ public class LookupDiscovery implements DiscoveryManagement, DiscoveryGroupManagement { /* Name of this component; used in config entry retrieval and the logger.*/ private static final String COMPONENT_NAME = "net.jini.discovery.LookupDiscovery"; /* Logger used by this utility. */ private static final Logger logger = Logger.getLogger(COMPONENT_NAME); /** Convenience constant used to request that attempts be made to * discover all lookup services that are within range, and which * belong to any group. Must define this constant here as well as in * <code>DiscoveryGroupManagement</code> for compatibility with * earlier releases. */ public static final String[] ALL_GROUPS = DiscoveryGroupManagement.ALL_GROUPS; /** Convenience constant used to request that discovery by group * membership be halted (or not started, if the group discovery * mechanism is simply being instantiated). Must define this constant * here as well as in <code>DiscoveryGroupManagement</code> for * compatibility with earlier releases. */ public static final String[] NO_GROUPS = DiscoveryGroupManagement.NO_GROUPS; /** Maximum number of concurrent tasks that can be run in any task * manager created by this class. */ private static final int MAX_N_TASKS = 15; /** Default maximum size of multicast packets to send and receive. */ private static final int DEFAULT_MAX_PACKET_SIZE = 512; /** Default time to live value to use for sending multicast packets. */ private static final int DEFAULT_MULTICAST_TTL = 15; /** Default timeout to set on sockets used for unicast discovery. */ private static final int DEFAULT_SOCKET_TIMEOUT = 1*60*1000; /** Flag indicating whether or not this class is still functional. */ private boolean terminated = false; /** Set of listeners to be sent discovered/discarded/changed events. */ private ArrayList listeners = new ArrayList(1); /** The groups to discover. Empty set -- NO_GROUPS, null -- ALL_GROUPS */ private Set groups = null; /** Map from ServiceID to UnicastResponse. */ private Map registrars = new HashMap(11); /** * Set that takes one of the following: * <p><ul> * <li> Socket (discovery from multicast request/response exchange) * <li> LookupLocator (discovery from multicast announcement) * <li> CheckGroupsMarker (discarded/changed from announcement) * <li> CheckReachabilityMarker (announcements stopped, tests reachability) * </ul><p> * Each element of this set represents a potential (or pending) discovered, * discarded, or changed event. Instances of UnicastDiscoveryTask retrieve * the next available element from this set and, based on the object type * of the element, determines the processing to perform and what event * type to send to the registered listeners. */ private Set pendingDiscoveries = new HashSet(11); /** Thread that handles pending notifications. */ private Notifier notifierThread; /** Notifications to be sent to listeners. */ private LinkedList pendingNotifies = new LinkedList(); /** Task manager for running UnicastDiscoveryTasks and * DecodeAnnouncementTasks. */ private TaskManager taskManager; /* WakeupManager to delay tasks. */ private WakeupManager discoveryWakeupMgr = null; private boolean isDefaultWakeupMgr = false; /* Outstanding tickets - Access synchronized on pendingDiscoveries */ private List tickets; /** Thread that handles incoming multicast announcements. */ private AnnouncementListener announceeThread; /** Collection that contains instances of the Requestor Thread class, * each of which participate in multicast discovery by periodically * sending out multicast discovery requests for a finite period of time. */ private Collection requestors = new LinkedList(); /** Thread that manages incoming multicast responses. Runs only when * there are Requestor threads running. */ private ResponseListener respondeeThread = null; /** Security context that contains the access control context to restore * for callbacks, etc. */ private final SecurityContext securityContext = Security.getContext(); /** Map from ServiceID to multicast announcement time stamps; used by the * process that monitors multicast announcements from already-discovered * lookup services, and determines when those announcements have stopped. */ private HashMap regInfo = new HashMap(11); /** Thread that monitors multicast announcements from already-discovered * lookup services and, upon determining that those announcements have * stopped, queues a reachability test with the UnicastDiscoveryTask * which will ultimately result in the lookup service being discarded * if the reachability test indicates that the lookup service is * actually down. */ private AnnouncementTimerThread announcementTimerThread; /* Preparer for the proxies to the lookup services that are discovered * and used by this utility. */ private ProxyPreparer registrarPreparer; /* Utility for participating in version 2 of discovery protocols. */ private Discovery protocol2 = Discovery.getProtocol2(null); /* Maximum number multicast requests to send when this utility is started * for the first time, and whenever the groups to discover are changed. */ private int multicastRequestMax = 7; /* With respect to when this utility is started, as well as when the set * of groups to discover is changed, the value of this field represents * the number of milliseconds to wait after sending the n-th multicast * request, and before sending the (n+1)-st request, where n is less than * the value of <code)multicastRequestMax</code>. */ private long multicastRequestInterval = 5000L; /* With respect to when this utility is started, as well as when the set * of groups to discover is changed, the value of this field represents * the number of milliseconds to wait after sending the n-th multicast * request, where n is equal to the value of * <code)multicastRequestMax</code>. */ private long finalMulticastRequestInterval = 2*60*1000L; /* Name of requesting host to include in multicast request if * participating in version 2 of multicast request protocol. */ private String multicastRequestHost; /* Constraints specified for outgoing multicast requests. */ private DiscoveryConstraints multicastRequestConstraints; /* The network interfaces (NICs) through which multicast packets will * be sent. */ private NetworkInterface[] nics; /* NICs that initially failed are retried after this number of millisecs.*/ private int nicRetryInterval = 5*60*1000; /* Controls how often (in milliseconds) this utility examines the * multicast announcements from previously discovered lookup services * for "liveness". */ private long multicastAnnouncementInterval = 2*60*1000L; /* * Controls how long to wait before responding to multicast * announcements */ private long unicastDelayRange = 0; /* Controls how long to wait before sending out multicast requests */ private long initialMulticastRequestDelayRange = 0; /* * Flag which indicates that initial multicast request thread has been * started. */ private boolean initialRequestorStarted = false; /* Constraints specified for incoming multicast announcements. */ private DiscoveryConstraints multicastAnnouncementConstraints; /* Unprocessed constraints specified for unicast discovery. */ private InvocationConstraints rawUnicastDiscoveryConstraints; /** Constants used to tell the notifierThread the type of event to send */ private static final int DISCOVERED = 0; private static final int DISCARDED = 1; private static final int CHANGED = 2; /** Constants used to indicate the set of network interfaces being used */ private static final int NICS_USE_ALL = 0;//use all NICs in the system private static final int NICS_USE_SYS = 1;//use NIC assigned by the system private static final int NICS_USE_LIST = 2;//use list of NICs from config private static final int NICS_USE_NONE = 3;//multicast disabled /** Flag that indicates how the set of network interfaces was configured */ private int nicsToUse = NICS_USE_ALL; /** Data structure containing task data processed by the Notifier Thread */ private static class NotifyTask { /** The set of listeners to notify */ public final ArrayList listeners; /** Map of discovered registrars-to-groups in which each is a member */ public final Map groupsMap; /** The type of event to send: DISCOVERED, DISCARDED, CHANGED */ public final int eventType; public NotifyTask(ArrayList listeners, Map groupsMap, int eventType) { this.listeners = listeners; this.groupsMap = groupsMap; this.eventType = eventType; } }//end class NotifyTask /** Thread that retrieves data structures of type NotifyTask from a * queue and, based on the contents of the data structure, sends the * appropriate event (discovered/discarded/changed) to each registered * listener. * <p> * Only 1 instance of this thread is run. */ private class Notifier extends Thread { /** Create a daemon thread */ public Notifier() { super("event listener notification"); setDaemon(true); }//end constructor public void run() { logger.finest("LookupDiscovery - Notifier thread started"); while (true) { final NotifyTask task; synchronized (pendingNotifies) { if (pendingNotifies.isEmpty()) { notifierThread = null; return; }//endif task = (NotifyTask)pendingNotifies.removeFirst(); }//end sync /* The call to notify() on the registered listeners is * performed inside a doPrivileged block that restores the * access control context that was in place when this utility * was created. * * This is done because the notify() method called below * executes in the client. But the listener object that * defines that notify() method may have been obtained by * the client from some (possibly untrusted) 3rd party. With * respect to 3rd party code executing in the client, it is * not desirable to allow such code to execute with more * priviledges than the client that created this utility. * Therefore, before executing notify() on any of the * registered listeners, the client's Subject should be * restored, and the listner code should be restricted to * doing nothing more than the client itself is allowed to do. */ AccessController.doPrivileged (securityContext.wrap(new PrivilegedAction() { public Object run() { boolean firstListener = true; for (Iterator iter = task.listeners.iterator(); iter.hasNext(); ) { DiscoveryListener l = (DiscoveryListener)iter.next(); /* Always send discovered and discarded events */ if( (task.eventType == CHANGED) && !(l instanceof DiscoveryChangeListener) ) { continue; }//endif DiscoveryEvent e = new DiscoveryEvent ( LookupDiscovery.this, deepCopy((HashMap)task.groupsMap) ); /* Log the event info about the lookup(s) */ if( firstListener && (logger.isLoggable(Level.FINEST)) ) { String eType = new String[]{"discovered", "discarded", "changed"}[task.eventType]; ServiceRegistrar[] regs = e.getRegistrars(); logger.finest(eType+" event -- "+regs.length +" lookup(s)"); Map groupsMap = e.getGroups(); for(int i=0;i<regs.length;i++) { LookupLocator loc = null; try { loc = regs[i].getLocator(); } catch (Throwable ex) { /* ignore */ } String[] groups = (String[])groupsMap.get(regs[i]); logger.finest(" "+eType+" locator = " +loc); if(groups.length == 0) { logger.finest(" "+eType+" group " +"= NO_GROUPS"); } else { for(int j=0;j<groups.length;j++) { logger.finest(" "+eType +" group["+j+"] = " +groups[j]); }//end loop }//endif(groups.length) }//end loop }//endif(firstListener && isLoggable(Level.FINEST) switch(task.eventType) { case DISCOVERED: l.discovered(e); break; case DISCARDED: l.discarded(e); break; case CHANGED: ((DiscoveryChangeListener)l).changed(e); break; }//end switch(eventType) firstListener = false; }//end loop return null; }//end run }),//end PrivilegedAction and wrap securityContext.getAccessControlContext());//end doPrivileged }//end loop }//end run }//end class Notifier /** Thread that listens for multicast announcements from lookup services. * <p> * If the announcements are from a lookup service that has not already * been discovered, and if it is determined that the lookup service * belongs to at least one group of interest, a "pendingDiscovery" is * queued for the UnicastDiscoveryTask to process asynchronously, * completing the discovery process by performing unicast discovery. * <p> * If the announcements are from a lookup service that has already * been discovered, the lookup service's member groups - as indicated * in the announcements - are analyzed for changes that may result * in either the lookup service being discarded, or in a changed event * being sent. * <p> * Only 1 instance of this thread is run. */ private class AnnouncementListener extends Thread { /** Multicast socket for receiving packets */ private MulticastSocket sock; /* Set of interfaces whose elements also belong to the nics[] array, * which encountered failure when setting the interface or joining * the desired multicast group, and which will be retried periodically. */ private ArrayList retryNics = null; /** Create a daemon thread */ public AnnouncementListener() throws IOException { super("multicast discovery announcement listener"); setDaemon(true); sock = new MulticastSocket(Constants.discoveryPort); switch(nicsToUse) { case NICS_USE_ALL: /* Using all interfaces. Skip (but report) any interfaces * that are "bad" or not configured for multicast. */ for(int i=0;i<nics.length;i++) { try { sock.setNetworkInterface(nics[i]); sock.joinGroup(Constants.getAnnouncementAddress()); } catch(IOException e) { if(retryNics == null) { retryNics = new ArrayList(nics.length); }//endif retryNics.add(nics[i]); if( logger.isLoggable(Levels.HANDLED) ) { LogRecord logRec = new LogRecord(Levels.HANDLED, "network interface " +"is bad or not configured " +"for multicast: {0}"); logRec.setParameters(new Object[]{nics[i]}); logRec.setThrown(e); logger.log(logRec); }//endif } }//end loop break; case NICS_USE_LIST: /* Using a configured list of specific interfaces. Skip * (but report) any interfaces that are "bad" or not * configured for multicast. */ for(int i=0;i<nics.length;i++) { try { sock.setNetworkInterface(nics[i]); sock.joinGroup(Constants.getAnnouncementAddress()); } catch(IOException e) { if(retryNics == null) { retryNics = new ArrayList(nics.length); }//endif retryNics.add(nics[i]); if( logger.isLoggable(Level.SEVERE) ) { LogRecord logRec = new LogRecord(Level.SEVERE, "network interface is bad or " +"not configured for " +"multicast: {0}"); logRec.setParameters(new Object[]{nics[i]}); logRec.setThrown(e); logger.log(logRec); }//endif } }//end loop break; case NICS_USE_SYS: /* Using the system-dependent default interface. Don't * need to specifically set the interface. If that * interface is "bad" or not configured for multicast, * log it and try again later. */ try { sock.joinGroup(Constants.getAnnouncementAddress()); } catch(IOException e) { retryNics = new ArrayList(0); if( logger.isLoggable(Level.SEVERE) ) { logger.log(Level.SEVERE, "system default network " +"interface is bad or not configured " +"for multicast", e); }//endif } break; case NICS_USE_NONE: break;//multicast disabled, do nothing default: throw new AssertionError("nicsToUse flag out of range " +"(0-3): "+nicsToUse); }//end switch(nicsToUse) }//end constructor /** True if thread has been interrupted */ private volatile boolean interrupted = false; /* This is a workaround for Thread.interrupt not working on * MulticastSocket.receive on all platforms. */ public void interrupt() { interrupted = true; sock.close(); }//end interrupt /** Accessor method that returns the <code>interrupted</code> flag. */ public boolean isInterrupted() { return interrupted; }//end isInterrupted /** Convenience method that retries any previously failed interfaces.*/ private void retryBadNics() { if(retryNics == null) return;//no failed NICs to retry if( !retryNics.isEmpty() ) { String recoveredStr = "network interface has recovered " +"from previous failure: {0}"; ArrayList tmpList = (ArrayList)retryNics.clone(); retryNics.clear(); for(int i=0; i<tmpList.size(); i++) { NetworkInterface nic =(NetworkInterface)tmpList.get(i); try { sock.setNetworkInterface(nic); sock.joinGroup(Constants.getAnnouncementAddress()); if(nicsToUse == NICS_USE_LIST) { logger.log(Level.INFO, recoveredStr, nic); } else { logger.log(Level.FINE, recoveredStr, nic); }//endif } catch(IOException e1) { retryNics.add(nic);//put back for another retry later } }//end loop if(retryNics.isEmpty()) retryNics = null;//future retries off } else {//(retryNics.size() == 0) ==> sys default interface try { sock.joinGroup(Constants.getAnnouncementAddress()); retryNics = null; logger.log(Level.INFO, "system default network " +"interface has recovered from " +"previous failure"); } catch(IOException e1) { } }//endif(!retryNics.isEmpty()) }//end retryBadNics public void run() { logger.finest("LookupDiscovery - AnnouncementListener thread " +"started"); byte[] buf = new byte[ multicastAnnouncementConstraints.getMulticastMaxPacketSize( DEFAULT_MAX_PACKET_SIZE)]; DatagramPacket pkt = new DatagramPacket(buf, buf.length); long endTime = System.currentTimeMillis() + nicRetryInterval; while (!isInterrupted()) { try { int delta_t = 0; if(retryNics != null) {//bad NICs, retry when time's up delta_t = (int)(endTime - System.currentTimeMillis()); if( delta_t <= 0) { retryBadNics(); if(retryNics != null) {//still bad, reset timer delta_t = nicRetryInterval; endTime = System.currentTimeMillis() + delta_t; } else {//all NICs recovered, turn off timer delta_t = 0; }//endif }//endif }//endif sock.setSoTimeout(delta_t); pkt.setLength(buf.length); try { sock.receive(pkt); } catch (NullPointerException e) { break; // workaround for bug 4190513 } taskManager.add(new DecodeAnnouncementTask(pkt)); buf = new byte[buf.length]; pkt = new DatagramPacket(buf, buf.length); } catch (SocketTimeoutException e) {//continue/retry bad NICs } catch (InterruptedIOException e) { break; } catch (Exception e) {//ignore if( isInterrupted() ) break; logger.log(Level.INFO, "exception while listening for multicast " +"announcements", e); } }//end loop(!interrupted) sock.close(); sock = null; logger.finest("LookupDiscovery - AnnouncementListener thread " +"completed"); }//end run }//end class AnnouncementListener /** Thread that listens for multicast responses to the multicast requests * sent out by the Requestor Thread class. Upon receiving a multicast * response, the socket that accepted the connection request associated * with the the multicast response is added to the set of * pendingDiscoveries so that it (the socket) will be used by the * UnicastDiscoveryTask to complete the discovery process asynchronously. * <p> * Only 1 instance of this thread is run. */ private class ResponseListener extends Thread { /** Server socket for accepting connections */ public ServerSocket serv; /** Create a daemon thread */ public ResponseListener() throws IOException { super("multicast discovery response listener"); setDaemon(true); serv = new ServerSocket(0); }//end constructor /** True if thread has been interrupted */ private volatile boolean interrupted = false; /* This is a workaround for Thread.interrupt not working on * ServerSocket.accept on all platforms. ServerSocket.close * can't be used as a workaround, because it also doesn't work * on all platforms. */ public void interrupt() { interrupted = true; try { (new Socket(InetAddress.getLocalHost(), getPort())).close(); } catch (IOException e) { /* ignore */ } }//end interrupt /** Accessor method that returns the <code>interrupted</code> flag. */ public boolean isInterrupted() { return interrupted; }//end isInterrupt public void run() { logger.finest("LookupDiscovery - ResponseListener thread started"); while (!isInterrupted()) { try { Socket sock = serv.accept(); if (isInterrupted()) { try { sock.close(); } catch (IOException e) { } break; }//end if synchronized (pendingDiscoveries) { pendingDiscoveries.add(sock); taskManager.add(new UnicastDiscoveryTask(sock)); }//end sync } catch (InterruptedIOException e) { break; } catch (Exception e) {//ignore logger.log(Level.INFO, "exception while listening for multicast " +"response", e); } }//end loop(!isInterrupted) try { serv.close(); } catch (IOException e) {//ignore logger.log(Levels.HANDLED, "IOException while attempting a socket close", e); } logger.finest("LookupDiscovery - ResponseListener thread " +"completed"); }//end run /** Return the local port of the socket */ public int getPort() { return serv.getLocalPort(); }//end getPort }//end class ResponseListener /** Thread that periodically sends out multicast requests for a limited * period of time, and then exits. * <p> * An instance of this thread is run at startup, and each time the * set of groups to discover is changed. */ private class Requestor extends Thread { /** Multicast socket for sending packets */ private MulticastSocket sock; /** Unicast response port */ private int responsePort; /** Groups to request */ private String[] groups; private boolean delayFlag; /** Create a daemon thread */ public Requestor(String[] groups, int port, boolean delayFlag) throws IOException { super("multicast discovery request"); setDaemon(true); sock = new MulticastSocket(Constants.discoveryPort); sock.setTimeToLive( multicastRequestConstraints.getMulticastTimeToLive( DEFAULT_MULTICAST_TTL)); responsePort = port; this.groups = groups == null ? new String[0] : groups; this.delayFlag = delayFlag; }//end constructor /** This method sends out N (for small N) multicast requests. Until * the last request is sent out, this method sleeps for 5 seconds * after each request is sent. After the last request is sent, * this method sleeps for 2 minutes to allow the ResponseListener * time to receive and process any multicast responses sent in * reply to the multicast requests. Before sending a request, a * new multicast request is constructed so that updates * can be made to the set of service IDs of the lookup services * discovered due to previous requests. * <p> * After all requests have been sent, and the ResponseListener * has been given the appropriate time to receive and process * any multicast responses, if there are no more active instances * of this thread, this method terminates (interrupts) the * ResponseListener. Note that although it is more desirable to * have the ResponseListener set a timeout on the server socket * (using setSoTimeout) and then simply exit after a period of * time in which both the ResponseListener has been idle, and * there have been no active Requestor threads, using setSoTimeout * in this way can cause random hangs on the Solaris(TM) operating * system. */ public void run() { logger.finest("LookupDiscovery - Requestor thread started"); int count; // bug 4084783/4187594 try { if (delayFlag && (initialMulticastRequestDelayRange > 0) && (multicastRequestMax >= 0)) { Thread.sleep((long) (Math.random() * initialMulticastRequestDelayRange)); } for (count = multicastRequestMax; --count >= 0 && !isInterrupted(); ) { DatagramPacket[] reqs = encodeMulticastRequest (new MulticastRequest(multicastRequestHost, responsePort, groups, getServiceIDs())); sendPacketByNIC(sock, reqs); Thread.sleep(count > 0 ? multicastRequestInterval:finalMulticastRequestInterval); }//end loop } catch (InterruptedException e) {//terminate gracefully } catch (InterruptedIOException e) {//terminate gracefully } catch (Exception e) { logger.log(Level.INFO,"exception while marshalling outgoing " +"multicast request", e); } finally { synchronized (requestors) { requestors.remove(Thread.currentThread()); if (respondeeThread != null && requestors.isEmpty()) { respondeeThread.interrupt(); respondeeThread = null; } }//end sync sock.close(); logger.finest("LookupDiscovery - Requestor thread completed"); }//end try/catch/finally }//end run }//end class Requestor /** * This thread monitors the multicast announcements sent from the * lookup service(s) that have already been discovered by this class, * looking for indications that those announcements have terminated. * * The data structure used to map the discovered lookup services to * the time of arrival of the most recent multicast announcement from * each such lookup service is examined at regular intervals; dependent * on the system property <code>net.jini.discovery.announce</code>. * * If the difference between the current time and the last time of * arrival for any announcement exceeds a predetermined threshold, the * corresponding lookup is polled for its current set of member groups. * If that lookup service is unreachable, or if it is reachable but its * member groups have been replaced, the lookup service is discarded. */ private class AnnouncementTimerThread extends Thread { /* Number of interval to exceed for declaring announcements stopped */ private static final long N_INTERVALS = 3; /** Create a daemon thread */ public AnnouncementTimerThread() { super("multicast announcement timer"); setDaemon(true); } public synchronized void run() { long timeThreshold = N_INTERVALS*multicastAnnouncementInterval; try { while(!isInterrupted()) { wait(multicastAnnouncementInterval); long curTime = System.currentTimeMillis(); synchronized (registrars) { /* can't modify regInfo while iterating over it, * so clone it */ HashMap regInfoClone = (HashMap)(regInfo.clone()); Set eSet = regInfoClone.entrySet(); for(Iterator itr = eSet.iterator(); itr.hasNext(); ) { Map.Entry pair = (Map.Entry)itr.next(); ServiceID srvcID = (ServiceID)pair.getKey(); long tStamp = ((AnnouncementInfo)pair.getValue()).tStamp; long deltaT = curTime - tStamp; if(deltaT > timeThreshold) { /* announcements stopped, queue reachability * test and potential discarded event */ UnicastResponse resp = (UnicastResponse)registrars.get(srvcID); Object req = new CheckReachabilityMarker(resp); synchronized (pendingDiscoveries) { if(pendingDiscoveries.add(req)) { taskManager.add( new UnicastDiscoveryTask(req)); }//endif }//end sync }//end if }//end loop (itr) }//end sync }//end loop (!isInterrupted) } catch (InterruptedException e) { } }//end run }//end class AnnouncementTimerThread /** * Marker object placed in pendingDiscoveries set to indicate to * UnicastDiscoveryTask that the groups of the lookup service which sent * the contained announcement need to be verified. */ private static class CheckGroupsMarker { /** Announcement sent by lookup service to check groups of */ final MulticastAnnouncement announcement; CheckGroupsMarker(MulticastAnnouncement announcement) { this.announcement = announcement; } public int hashCode() { return announcement.getServiceID().hashCode(); } public boolean equals(Object obj) { return obj instanceof CheckGroupsMarker && announcement.getServiceID().equals( ((CheckGroupsMarker) obj).announcement.getServiceID()); } } /** * Marker object placed in pendingDiscoveries set to indicate to * UnicastDiscoveryTask that reachability of the lookup service which sent * the contained unicast response needs to be verified. */ private static class CheckReachabilityMarker { /** Response sent by lookup service to check reachability of */ final UnicastResponse response; CheckReachabilityMarker(UnicastResponse response) { this.response = response; } public int hashCode() { return response.getRegistrar().hashCode(); } public boolean equals(Object obj) { return obj instanceof CheckReachabilityMarker && response.getRegistrar().equals( ((CheckReachabilityMarker) obj).response.getRegistrar()); } } /** * Task which decodes received multicast announcement packets. This is * separated into a task to allow the AnnouncementListener thread to * quickly loop and receive new announcement packets; the act of decoding * packets may involve relatively slow cryptographic operations such as * signature verification, and would impede the packet receiving loop if it * were performed inline. */ private class DecodeAnnouncementTask implements TaskManager.Task { private final DatagramPacket datagram; /** * Creates a task for decoding the given multicast announcement packet. */ public DecodeAnnouncementTask(DatagramPacket datagram) { this.datagram = datagram; } /** * Decodes this task's multicast announcement packet. If the * constraints for decoding multicast announcements are satisfied and * the announcement merits further processing, an appropriate object is * added to the pendingDiscoveries set, and control is transferred to a * UnicastDiscoveryTask. */ public void run() { MulticastAnnouncement ann; try { ann = decodeMulticastAnnouncement(datagram); } catch (Exception e) { if (!(e instanceof InterruptedIOException)) { logger.log(Levels.HANDLED, "exception decoding multicast announcement", e); } return; } /* If the registrars map contains the service ID of the registrar * that sent the current announcement then that registrar has * already been discovered. * * Determine if the member groups of the already-discovered * registrar have been replaced by a set containing none of the * desired groups. If yes, then discard the registrar. * * If the registrar that sent the current announcement has not * already been discovered, then check to see if any of the * group(s) in which the registrar is a member are in the set of * desired groups to discover. If yes, then queue the registrar for * unicast discovery. */ Object pending = null; ServiceID srvcID = ann.getServiceID(); synchronized (registrars) { UnicastResponse resp = (UnicastResponse) registrars.get(srvcID); if (resp != null) { // already in discovered set, timestamp announcement AnnouncementInfo aInfo = (AnnouncementInfo) regInfo.get(srvcID); aInfo.tStamp = System.currentTimeMillis(); long currNum = ann.getSequenceNumber(); if ((newSeqNum(currNum, aInfo.seqNum)) && (!groupSetsEqual(resp.getGroups(), ann.getGroups()))) { /* Check if the groups have changed. In the case of * split announcement messages, eventually, group difference * will be seen for the given sequence number. This * check ignores other differences, such as port numbers, * but for the purposes of LookupDiscovery, this is not * important. */ pending = new CheckGroupsMarker(ann); } } else if (groupsOverlap(ann.getGroups())) { // newly discovered pending = new LookupLocator(ann.getHost(), ann.getPort()); } } if (pending != null) { try { ann.checkConstraints(); } catch (Exception e) { if (!(e instanceof InterruptedIOException)) { logger.log(Levels.HANDLED, "exception decoding multicast announcement", e); } return; } if (pending instanceof CheckGroupsMarker) { synchronized(registrars) { // Since this is a valid announcement, update the // sequence number. AnnouncementInfo aInfo = (AnnouncementInfo) regInfo.get(srvcID); aInfo.seqNum = ann.getSequenceNumber(); } } boolean added; // enqueue and handle pending action, if not already enqueued synchronized (pendingDiscoveries) { added = pendingDiscoveries.add(pending); } if (added) { if (unicastDelayRange <= 0) { new UnicastDiscoveryTask(pending).run(); } else { final UnicastDiscoveryTask ud = new UnicastDiscoveryTask(pending, true); final Ticket t = discoveryWakeupMgr.schedule( System.currentTimeMillis() + (long) (Math.random() * unicastDelayRange), new Runnable() { public void run() { taskManager.add(ud); } } ); synchronized (ud) { ud.ticket = t; ud.delayRun = false; synchronized (pendingDiscoveries) { tickets.add(t); } ud.notifyAll(); } } } } } /** Returns <code>true</code> if currentNum is a new sequence number * that needs to be inspected. A -1 occurs if the announcement had no * sequence number (for e.g. DiscoveryV1) or the service had been * discovered through unicast discovery. REMIND: Ideally the * message should have a flag which indicates no sequence number instead * of overloading the -1 value */ private boolean newSeqNum(long currentNum, long oldNum) { if (oldNum == -1) { // No sequence number information, so we guess that this is // a new announcement of interest. return true; } else if (currentNum > oldNum) { return true; } else { return false; } } /** No ordering */ public boolean runAfter(List tasks, int size) { return false; } } /** Task which retrieves elements from the set of pendingDiscoveries and * performs the appropriate processing based on the object type of * the element. * <p> * Each element of the set of pendingDiscoveries is one of the following * object types: Socket, LookupLocator, CheckGroupsMarker, * or CheckReachabilityMarker. * <p> * When the element to process is a Socket, the element was a result * of a multicast request/response exchange (see the Requestor and * ResponseListener Thread classes). In this case, this task completes * the discovery of the associated lookup service by performing the * final stage of unicast discovery, ultimately resulting in a discovered * event being sent to all registered listeners. * <p> * When the element to process is a LookupLocator, the element was a * result of a multicast announcement received from a lookup service - * belonging to at least one group of interest - which has not already * been discovered. In this case, this task also completes the discovery * of the lookup service referenced in the announcement by performing the * final stage of unicast discovery, ultimately resulting in a discovered * event being sent to all registered listeners. * <p> * When the element to process is a CheckGroupsMarker, the * element was a result of a multicast announcement received from an * already-discovered lookup service whose member groups have changed * in some way. In this case, this task determines how those member * groups have changed and, based on how they have changed, whether * (or not) to send a discarded event or a changed event to the * appropriate registered listeners. * <p> * When the element to process is a CheckReachabilityMarker, the * element was a result of a determination that the multicast * announcements from an already-discovered lookup service have * stopped being received (see the AnnouncementTimerThread class). * In this case, this task determines if the affected lookup service * is still available ("reachable"). If this task cannot communicate * with the lookup service, a discarded event is queued to be sent * to all registered listeners. * <p> * Rather than performing unicast discovery synchronously, after multicast * discovery has occurred in either the AnnouncementListener thread * (the multicast announcement protocol) or the ResponseListener thread * (the multicast request protocol), the unicast discovery processing * that is required to complete the discovery process is queued for * asynchronous execution in this task. Unicast discovery is performed * asynchronously because unicast discovery can take quite a while to * fail if a lookup service "disappears" (because the network or the * lookup service itself has crashed) between the time a multicast * announcement or response indicates the existence of a lookup service * eligible for unicast discovery, and the time unicast discovery * actually starts. If unicast discovery is performed synchronously * in the threads that implement the multicast announcement and multicast * request protocols, other multicast announcements (as well as other * unicast discoveries) will be missed whenever a lookup service * disappears prior to the commencement of the unicast discovery stage. */ private class UnicastDiscoveryTask implements TaskManager.Task { private Object req; private Ticket ticket = null; private boolean delayRun = false; UnicastDiscoveryTask(Object req) { this(req, false); } UnicastDiscoveryTask(Object req, boolean delayRun) { this.req = req; this.delayRun = delayRun; } public void run() { logger.finest("LookupDiscovery - UnicastDiscoveryTask started"); try { synchronized (this) { while (delayRun) { this.wait(); } synchronized (pendingDiscoveries) { // If this was run by a WakeupManager, remove its // ticket from the list of outstanding tickets. if (ticket != null) { tickets.remove(ticket); } } } Socket sock = null; MulticastAnnouncement announcement = null; UnicastResponse response = null; if (req instanceof Socket) { // Perform unicast discovery on the connected socket. DiscoveryConstraints unicastDiscoveryConstraints = DiscoveryConstraints.process( rawUnicastDiscoveryConstraints); sock = (Socket)req; UnicastResponse resp; try { prepareSocket(sock, unicastDiscoveryConstraints); resp = doUnicastDiscovery(sock, unicastDiscoveryConstraints); } finally { try { sock.close(); } catch (IOException e) { /* ignore */ } } maybeAddNewRegistrar(resp); } else if(req instanceof LookupLocator) { // Perform unicast discovery using the LookupLocator // host and port. LookupLocator loc = (LookupLocator)req; UnicastResponse resp = new MultiIPDiscovery() { protected UnicastResponse performDiscovery( Discovery disco, DiscoveryConstraints dc, Socket s) throws IOException, ClassNotFoundException { return doUnicastDiscovery(s, dc, disco); } protected void singleResponseException(Exception e, InetAddress addr, int port) { logger.log( Levels.HANDLED, "Exception occured during unicast discovery " + addr + ":" + port, e); } }.getResponse(loc.getHost(), loc.getPort(), rawUnicastDiscoveryConstraints); maybeAddNewRegistrar(resp); } else if(req instanceof CheckGroupsMarker) { // handle group changes announcement = ((CheckGroupsMarker)req).announcement; ServiceID srvcID = announcement.getServiceID(); UnicastResponse resp = null; synchronized (registrars) { resp = (UnicastResponse)registrars.get(srvcID); } if(resp != null) { maybeSendEvent(resp, announcement.getGroups()); }//endif } else if(req instanceof CheckReachabilityMarker) { // test reachability response = ((CheckReachabilityMarker)req).response; maybeSendEvent(response, null); }//endif } catch (InterruptedIOException e) { logger.log(Levels.HANDLED, "exception occurred during unicast discovery", e); } catch (Throwable e) { if (((req instanceof Socket) || (req instanceof LookupLocator)) && logger.isLoggable(Level.INFO)) { String logmsg = "exception occurred during unicast discovery to " + "{0}:{1,number,#} with constraints {2}"; String methodName = "run"; if (req instanceof Socket) { Socket sock = (Socket) req; LogUtil.logThrow(logger, Level.INFO, this.getClass(), methodName, logmsg, new Object[] { sock.getInetAddress().getHostName(), new Integer(sock.getPort()), rawUnicastDiscoveryConstraints }, e); } else { LookupLocator loc = (LookupLocator) req; LogUtil.logThrow(logger, Level.INFO, this.getClass(), methodName, logmsg, new Object[] { loc.getHost(), new Integer(loc.getPort()), rawUnicastDiscoveryConstraints }, e); } } else { logger.log(Level.INFO, "exception occurred during unicast discovery", e); } } finally { // Done with the request. Remove it regardless of // if we succeeded or failed. synchronized (pendingDiscoveries) { pendingDiscoveries.remove(req); } }//end try/catch logger.finest("LookupDiscovery - UnicastDiscoveryTask 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 UnicastDiscoveryTask /** * Construct a new lookup discovery object, set to discover the * given set of groups. The set is represented as an array of * strings. This array may be empty, which is taken as the empty * set, and discovery is not performed. The reference passed in * may be null, which is taken as no set, and in which case * discovery of all reachable lookup services is performed. * Otherwise, the array contains the names of groups to discover. * The caller must have DiscoveryPermission for each group (or * for all groups, if the array is null). * * @param groups the set of group names to discover (null for no * set, empty for no discovery) * * @throws java.lang.NullPointerException input array contains at least * one null element * * @throws java.io.IOException an exception occurred in starting discovery * * @see #NO_GROUPS * @see #ALL_GROUPS * @see #setGroups * @see DiscoveryPermission */ public LookupDiscovery(String[] groups) throws IOException { try { beginDiscovery(groups, EmptyConfiguration.INSTANCE); } catch(ConfigurationException e) { /* swallow this exception */ } }//end constructor /** * Constructs a new lookup discovery object, set to discover the * given set of groups, and having the given <code>Configuration</code>. * <p> * The set of groups to discover is represented as an array of * strings. This array may be empty, which is taken as the empty * set, and discovery is not performed. The reference passed in * may be <code>null</code>, which is taken as no set, and in which * case discovery of all reachable lookup services is performed. * Otherwise, the array contains the names of groups to discover. * The caller must have <code>DiscoveryPermission</code> for each * group (or for all groups, if the array is <code>null</code>). * * @param groups the set of group names to discover (null for no * set, empty for no discovery) * * @param config an instance of <code>Configuration</code>, used to * obtain the objects needed to configure the current * instance of this class * * @throws java.lang.NullPointerException input array contains at least * one <code>null</code> element or <code>null</code> is input * for the configuration * * @throws java.io.IOException an exception occurred in starting discovery * * @throws net.jini.config.ConfigurationException indicates an exception * occurred while retrieving an item from the given * <code>Configuration</code> * * @see #NO_GROUPS * @see #ALL_GROUPS * @see #setGroups * @see DiscoveryPermission * @see net.jini.config.Configuration * @see net.jini.config.ConfigurationException */ public LookupDiscovery(String[] groups, Configuration config) throws IOException, ConfigurationException { beginDiscovery(groups, config); }//end constructor /** * Register a listener as interested in receiving DiscoveryEvent * notifications. * * @param l the listener to register * * @throws java.lang.NullPointerException this exception occurs when * <code>null</code> is input to the listener parameter * <code>l</code>. * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @see DiscoveryEvent * @see #removeDiscoveryListener */ public void addDiscoveryListener(DiscoveryListener l) { if(l == null) { throw new NullPointerException("can't add null listener"); } synchronized (registrars) { if (terminated) { throw new IllegalStateException("discovery terminated"); } if (listeners.indexOf(l) >= 0) return; //already have this listener listeners.add(l); if (registrars.isEmpty()) return;//nothing to send the new listener HashMap groupsMap = new HashMap(registrars.size()); Iterator iter = registrars.values().iterator(); while (iter.hasNext()) { UnicastResponse resp = (UnicastResponse)iter.next(); groupsMap.put(resp.getRegistrar(),resp.getGroups()); } ArrayList list = new ArrayList(1); list.add(l); addNotify(list, groupsMap, DISCOVERED); } }//end addDiscoveryListener /** * Indicate that a listener is no longer interested in receiving * DiscoveryEvent notifications. * * @param l the listener to unregister * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @see #addDiscoveryListener */ public void removeDiscoveryListener(DiscoveryListener l) { synchronized (registrars) { if (terminated) { throw new IllegalStateException("discovery terminated"); } listeners.remove(l); } }//end removeDiscoveryListener /** * Returns an array of instances of <code>ServiceRegistrar</code>, each * corresponding to a proxy to one of the currently discovered lookup * services. For each invocation of this method, a new array is returned. * * @return array of instances of <code>ServiceRegistrar</code>, each * corresponding to a proxy to one of the currently discovered * lookup services * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @see net.jini.core.lookup.ServiceRegistrar * @see net.jini.discovery.DiscoveryManagement#removeDiscoveryListener */ public ServiceRegistrar[] getRegistrars() { synchronized (registrars) { if (terminated) { throw new IllegalStateException("discovery terminated"); } if (registrars.isEmpty()) { return new ServiceRegistrar[0]; } Iterator iter = registrars.values().iterator(); ServiceRegistrar[] regs = new ServiceRegistrar[registrars.size()]; for (int i=0;iter.hasNext();i++) { regs[i] = ((UnicastResponse)iter.next()).getRegistrar(); } return regs; } }//end getRegistrars /** * Discard a registrar from the set of registrars already * discovered. This does not prevent that registrar from being * rediscovered; it is intended to be used to clear unreachable * entries from the set. <p> * * If the registrar has been discovered using this LookupDiscovery * object, each listener registered with this object will have its * discarded method called with the given registrar as parameter. * * @param reg the registrar to discard * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @see DiscoveryListener#discarded */ public void discard(ServiceRegistrar reg) { synchronized (registrars) { if (terminated) { throw new IllegalStateException("discovery terminated"); } if(reg == null) return; sendDiscarded(reg,null); }//end sync }//end discard /** Terminate the discovery process. */ public void terminate() { synchronized (registrars) { if (terminated) return; terminated = true; } nukeThreads(); }//end terminate /** * Return the set of group names this LookupDiscovery instance is * trying to discover. If this method returns the empty array, * that value is guaranteed to be referentially equal to * LookupDiscovery.NO_GROUPS. * * @return the set of groups to be discovered (null for all, empty * for no discovery) * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @see #NO_GROUPS * @see #ALL_GROUPS * @see #setGroups */ public String[] getGroups() { synchronized (registrars) { if (terminated) { throw new IllegalStateException("discovery terminated"); } if (groups == null) return ALL_GROUPS; if (groups.isEmpty()) return NO_GROUPS; return collectionToStrings(groups); } }//end getGroups /** * Add a set of groups to the set to be discovered. * The caller must have DiscoveryPermission for each group. * * @param newGroups the groups to add * * @throws java.io.IOException the multicast request protocol failed * to start * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @throws java.lang.UnsupportedOperationException there is no set of * groups to add to * * @see DiscoveryPermission */ public void addGroups(String[] newGroups) throws IOException { testArrayForNullElement(newGroups); checkGroups(newGroups); synchronized (registrars) { if (terminated) throw new IllegalStateException("discovery terminated"); if (groups == null) throw new UnsupportedOperationException( "can't add to \"any groups\""); Collection req = new ArrayList(newGroups.length); for (int i = 0; i < newGroups.length; i++) { if (groups.add(newGroups[i])) req.add(newGroups[i]); } if (!req.isEmpty()) requestGroups(req); } }//end addGroups /** * Change the set of groups to be discovered to correspond to the * given set. The set is represented as an array of strings. * This array may be empty, which is taken as the empty set, and * discovery is not performed. The reference passed in may be * null, which is taken as no set, and in which case discovery of * all reachable lookup services is performed. Otherwise, the * array contains the names of groups to discover. * The caller must have DiscoveryPermission for each group (or * for all groups, if the array is null). * * @param newGroups the new set of groups to discover (null for * all, empty array for no discovery) * * @throws java.io.IOException an exception occurred when starting * multicast discovery * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @see #LookupDiscovery * @see #ALL_GROUPS * @see #NO_GROUPS * @see DiscoveryPermission * @see #getGroups */ public void setGroups(String[] newGroups) throws IOException { testArrayForNullElement(newGroups); checkGroups(newGroups); boolean maybeDiscard = false; Set newGrps = null; if (newGroups != null) { newGrps = new HashSet(newGroups.length * 2); for (int i = 0; i < newGroups.length; i++) { newGrps.add(newGroups[i]); } } synchronized (registrars) { if (terminated) throw new IllegalStateException("discovery terminated"); if (newGroups == null) { if (groups != null) { groups = null; requestGroups(null); } return; } if (groups == null) { groups = new HashSet(11); maybeDiscard = true; } Set toAdd = new HashSet(newGrps); toAdd.removeAll(groups); // Figure out which groups to get rid of. We start off // with the full set for which we are already listening, // and eliminate any that are in both the new set and the // current set. Collection toRemove = new HashSet(groups); toRemove.removeAll(newGrps); // Add new groups before we remove any old groups, because // removeGroups will start a new round of multicast requests // if the set of groups becomes empty, and we don't want it // to do so without reason. groups.addAll(toAdd); if (!toRemove.isEmpty()) maybeDiscard |= removeGroupsInt(collectionToStrings(toRemove)); if (!toAdd.isEmpty()) requestGroups(toAdd); } if (maybeDiscard) maybeDiscardRegistrars(); }//end setGroups /** * Remove a set of groups from the set to be discovered. * * @param oldGroups groups to remove * * @throws java.lang.IllegalStateException this exception occurs when * this method is called after the <code>terminate</code> * method has been called. * * @throws java.lang.UnsupportedOperationException there is no set of * groups from which to remove */ public void removeGroups(String[] oldGroups) { testArrayForNullElement(oldGroups); boolean maybeDiscard; synchronized (registrars) { if (terminated) throw new IllegalStateException("discovery terminated"); if (groups == null) throw new UnsupportedOperationException( "can't remove from \"any groups\""); maybeDiscard = removeGroupsInt(oldGroups); } if (maybeDiscard) maybeDiscardRegistrars(); }//end removeGroups /** * Sends the given packet data on the given <code>MulticastSocket</code> * through each of the network interfaces corresponding to elements of * the array configured when this utility was constructed. * * @param mcSocket the <code>MulticastSocket</code> on which the data * will be sent * @param packet <code>DatagramPacket</code> array whose elements are * the data to send * * @throws java.io.InterruptedIOException */ private void sendPacketByNIC(MulticastSocket mcSocket, DatagramPacket[] packet) throws InterruptedIOException { switch(nicsToUse) { case NICS_USE_ALL: /* Using all interfaces. Skip (but report) any interfaces * that are "bad" or not configured for multicast. */ for(int i=0;i<nics.length;i++) { try { mcSocket.setNetworkInterface(nics[i]); sendPacket(mcSocket,packet); } catch(InterruptedIOException e) { throw e;//to signal a graceful exit } catch(IOException e) { if( logger.isLoggable(Levels.HANDLED) ) { LogRecord logRec = new LogRecord(Levels.HANDLED, "network interface is " +"bad or not configured for " +"multicast: {0}"); logRec.setParameters(new Object[]{nics[i]}); logRec.setThrown(e); logger.log(logRec); }//endif } catch(Exception e) { if( logger.isLoggable(Levels.HANDLED) ) { LogRecord logRec = new LogRecord(Levels.HANDLED, "exception while " +"sending packet through network " +"interface: {0}"); logRec.setParameters(new Object[]{nics[i]}); logRec.setThrown(e); logger.log(logRec); }//endif } }//end loop break; case NICS_USE_LIST: /* Using a configured list of specific interfaces. Skip (but * always report) any interfaces that are "bad" or not * configured for multicast. */ for(int i=0;i<nics.length;i++) { try { mcSocket.setNetworkInterface(nics[i]); sendPacket(mcSocket,packet); } catch(InterruptedIOException e) { throw e;//to signal a graceful exit } catch(IOException e) { if( logger.isLoggable(Level.SEVERE) ) { LogRecord logRec = new LogRecord(Level.SEVERE,"network interface " +"is bad or not configured for " +"multicast: {0}"); logRec.setParameters(new Object[]{nics[i]}); logRec.setThrown(e); logger.log(logRec); }//endif } catch(Exception e) { if( logger.isLoggable(Level.SEVERE) ) { LogRecord logRec = new LogRecord(Level.SEVERE,"exception while " +"sending packet through network " +"interface: {0}"); logRec.setParameters(new Object[]{nics[i]}); logRec.setThrown(e); logger.log(logRec); }//endif } }//end loop break; case NICS_USE_SYS: /* Using the system-dependent default interface. Don't need * to specifically set the interface. If that interface is * "bad" or not configured for multicast, always report it. */ try { sendPacket(mcSocket,packet); } catch(InterruptedIOException e) { throw e;//to signal a graceful exit } catch(IOException e) { if( logger.isLoggable(Level.SEVERE) ) { logger.log(Level.SEVERE, "system default network " +"interface is bad or not configured " +"for multicast", e); }//endif } catch(Exception e) { if( logger.isLoggable(Level.SEVERE) ) { logger.log(Level.SEVERE, "exception while sending " +"packet through system default network " +"interface", e); }//endif } break; case NICS_USE_NONE: break;//multicast disabled, do nothing default: throw new AssertionError("nicsToUse flag out of range (0-3): " +nicsToUse); }//end switch(nicsToUse) }//end sendPacketByNIC /** * Sends the given packet data on the given <code>MulticastSocket</code> * through the network interface that is currently set. * * @param mcSocket the <code>MulticastSocket</code> on which the data * will be sent * @param packet <code>DatagramPacket</code> array whose elements are * the data to send * * @throws java.io.IOException */ private static void sendPacket(MulticastSocket mcSocket, DatagramPacket[] packet) throws IOException { for(int i=0;i<packet.length;i++) { mcSocket.send(packet[i]); }//end loop }//end sendPacket /** Returns the local host name. */ private static String getLocalHost() throws UnknownHostException { try { return ((InetAddress) Security.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws UnknownHostException { return InetAddress.getLocalHost(); } })).getHostAddress(); } catch (PrivilegedActionException e) { // Remove host information if caller does not have privileges // to see it. try { InetAddress.getLocalHost(); } catch (UnknownHostException uhe) { throw uhe; } logger.log(Levels.FAILED, "Unknown host exception", e.getCause()); throw new UnknownHostException("Host name cleared due to " + "insufficient caller permissions"); } } /** Determines if the caller has discovery permission for each group. */ private static void checkGroups(String[] groups) { SecurityManager sm = System.getSecurityManager(); if (sm == null) return; if (groups != null) { for (int i = 0; i < groups.length; i++) { sm.checkPermission(new DiscoveryPermission(groups[i])); }//end loop } else { sm.checkPermission(new DiscoveryPermission("*")); }//endif }//end checkGroups /** Converts a collection to an array of strings. */ private static final String[] collectionToStrings(Collection c) { return c == null ? null : (String[]) c.toArray(new String[c.size()]); }//end collectionToStrings /** Determines if two sets of registrar member groups have identical * contents. Assumes there are no duplicates, and the sets can never * be null. * * @param groupSet0 <code>String</code> array containing the group * names from the first set used in the comparison * @param groupSet1 <code>String</code> array containing the group * names from the second set used in the comparison * * @return <code>true</code> if the contents of each set is identical; * <code>false</code> otherwise */ private static boolean groupSetsEqual(String[] groupSet0, String[] groupSet1) { if(groupSet0.length != groupSet1.length) return false; /* is every element of one set contained in the other set? */ iLoop: for(int i=0;i<groupSet0.length;i++) { for(int j=0;j<groupSet1.length;j++) { if( groupSet0[i].equals(groupSet1[j]) ) { continue iLoop; } }//end loop(j) return false; }//end loop(i) return true; }//end groupSetsEqual /** Returns true if the registrars contained in the given (possibly null) * UnicastResponse instances are equals() to one another. */ private static boolean registrarsEqual(UnicastResponse resp1, UnicastResponse resp2) { return resp1 != null && resp2 != null && resp2.getRegistrar().equals(resp1.getRegistrar()); }//end registrarsEqual /** * Remove the specified groups from the set of groups to discover, and * return true if any were actually removed. */ private boolean removeGroupsInt(String[] oldGroups) { boolean removed = false; for (int i = 0; i < oldGroups.length; i++) { removed |= groups.remove(oldGroups[i]); } return removed; }//end removeGroupsInt /** Returns the service IDs of the lookup service(s) discovered to date. */ private ServiceID[] getServiceIDs() { synchronized (registrars) { return (ServiceID[]) registrars.keySet().toArray(new ServiceID[registrars.size()]); }//end sync }//end getServiceIDs /** * Indicate whether any of the group names in the given array match * any of the groups of interest. * * @param possibilities the set of group names to compare to the set * of groups to discover (must not be null) */ private boolean groupsOverlap(String[] possibilities) { /* Match if we're interested in any group, or if we're * interested in none and there are no possibilities. */ if (groups == null) return true; for (int i = 0; i < possibilities.length; i++) { if (groups.contains(possibilities[i])) return true; }//end loop return false; }//end groupsOverlap /** Called at startup and whenever the set of groups to discover is * changed. This method executes the multicast request protocol by * starting the ResponseListener thread to listen for multicast * responses; and starting a Requestor thread to send out multicast * requests for the set of groups contained in the given Collection. */ private void requestGroups(final Collection req) throws IOException { try { Security.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { Thread t; synchronized (requestors) { if (respondeeThread == null) { respondeeThread = new ResponseListener(); respondeeThread.start(); }//endif boolean delayFlag = false; if (!initialRequestorStarted) { // only delay the first time delayFlag = true; initialRequestorStarted = true; } t = new Requestor(collectionToStrings(req), respondeeThread.getPort(), delayFlag); requestors.add(t); }//end sync t.start(); return null; }//run });//end doPrivileged } catch (PrivilegedActionException e) { throw (IOException)e.getException(); }//end try/catch }//end requestGroups private static void prepareSocket(Socket s, DiscoveryConstraints dc) throws SocketException { try { s.setTcpNoDelay(true); } catch (SocketException e) { // ignore possible failures and proceed anyway } try { s.setKeepAlive(true); } catch (SocketException e) { // ignore possible failures and proceed anyway } s.setSoTimeout(dc.getUnicastSocketTimeout(DEFAULT_SOCKET_TIMEOUT)); } /** * If the lookup service associated with the given UnicastResponse * is not in the set of already-discovered lookup services, this method * adds it to that set, and each registered listener is notified. * * @param resp the UnicastResponse associated with the lookup * service to add */ private void maybeAddNewRegistrar(UnicastResponse resp) { /* If the group names contained in the given incoming unicast response * don't match any of the groups of interest, then don't waste time * performing an unnecessary proxy preparation; simply return. */ synchronized(registrars) { if( !groupsOverlap(resp.getGroups()) ) return; }//end sync(registrars) /* Proxy preparation - * * The given incoming unicast response contains a proxy to a lookup * service that belongs to at least one of the groups of interest. * Before adding that proxy to the managed set of discovered lookup * services, and before notifying any of the registered listeners, * that proxy should be prepared. This is necessary in this utility * because that lookup service may be tested for reachability at * some point. Since that test involves a remote call (to getGroups()) * through the proxy, the proxy should be prepared. * * The preparation of that proxy is performed inside a doPrivileged * block that restores the access control context that was in place * when this utility was created. In this way, any code that is * executed as a part of preparing the proxy will be executed with * no additional permissions beyond the permissions that were granted * to the client that created this utility. This is done because the * proxy preparer executed below is provided by the deployer and thus * can be viewed as an artifact of the client. Therefore, before * executing the preparer's code in this utility, the client's * Subject should be restored, and the preparer code should be * restricted to doing nothing more than the client itself is * allowed to do. * * Note that it's okay to modify the state of the given incoming * unicast response here because, prior to modification and storage * in the managed set of registrars in this method, it is assumed that * that object is not accessed by any other thread. */ try { final ServiceRegistrar srcReg = resp.getRegistrar(); ServiceRegistrar prepReg = (ServiceRegistrar)AccessController.doPrivileged ( securityContext.wrap( new PrivilegedExceptionAction() { public Object run() throws RemoteException { Object proxy = registrarPreparer.prepareProxy (srcReg); logger.log(Level.FINEST, "LookupDiscovery - " +"prepared lookup service proxy: {0}", proxy); return proxy; }//end run }),//end PrivilegedExceptionAction and wrap securityContext.getAccessControlContext());//end doPrivileged if (prepReg != srcReg) { resp = new UnicastResponse(resp.getHost(), resp.getPort(), resp.getGroups(), prepReg); } } catch (Exception e) { Exception e1 = ( (e instanceof PrivilegedActionException) ? ((PrivilegedActionException)e).getException() : e); logger.log(Level.INFO, "exception while preparing lookup service proxy", e1); return; } /* Add any newly discovered registrars to the managed set and notify * all listeners. */ synchronized (registrars) { if(groupsOverlap(resp.getGroups()) && !registrarsEqual(resp, (UnicastResponse) registrars.put (resp.getRegistrar().getServiceID(), resp))) { /* Time stamp the service ID and store its current sequence * number. The first time stamp associated * with the current service ID occurs here. All other time * stamps for that service ID will occur when multicast * announcements for that service ID arrive (in the * AnnouncementListener thread). * * Note that if the time stamp for the service ID were * initialized upon the arrival of the first announcement, * rather than here when it is first discovered, the * AnnouncementTimerThread would not be able to detect the * termination of announcements for the case where the * termination happens to occur between the time the lookup * is first discovered here, and the time the first * announcement was supposed to have arrived. This can * happen because a multicast request from the client can * cause the lookup to be discovered before the first * announcement arrives. */ regInfo.put(resp.getRegistrar().getServiceID(), new AnnouncementInfo(System.currentTimeMillis(), -1)); if(!listeners.isEmpty()) { addNotify((ArrayList)listeners.clone(), mapRegToGroups(resp.getRegistrar(), resp.getGroups()), DISCOVERED); }//endif }//endif }//end sync(registrars) }//end maybeAddNewRegistrar /** Determine if any of the already-discovered registrars are no longer * members of any of the groups to discover, and discard those registrars * that are no longer members of any of those groups. */ private void maybeDiscardRegistrars() { synchronized (registrars) { HashMap groupsMap = new HashMap(registrars.size()); for(Iterator iter=registrars.values().iterator();iter.hasNext(); ){ UnicastResponse ent = (UnicastResponse)iter.next(); if(!groupsOverlap(ent.getGroups())) { // not interested anymore groupsMap.put(ent.getRegistrar(),ent.getGroups()); regInfo.remove(ent.getRegistrar().getServiceID()); iter.remove(); // remove (srvcID,response) mapping }//endif }//end loop if( !groupsMap.isEmpty() && !listeners.isEmpty() ) { addNotify((ArrayList)listeners.clone(), groupsMap, DISCARDED); }//endif }//end sync }//end maybeDiscardRegistrars /** * Add a notification task to the pending queue, and start an instance of * the Notifier thread if one isn't already running. */ private void addNotify(ArrayList notifies, Map groupsMap, int eventType) { synchronized (pendingNotifies) { pendingNotifies.addLast(new NotifyTask(notifies, groupsMap, eventType)); if (notifierThread == null) { Security.doPrivileged(new PrivilegedAction() { public Object run() { notifierThread = new Notifier(); notifierThread.start(); return null; }//end run });//end doPrivileged }//endif }//end sync }//end addNotify /** Terminates (interrupts) all currently-running threads. */ private void nukeThreads() { Security.doPrivileged(new PrivilegedAction() { public Object run() { if(announcementTimerThread != null) { announcementTimerThread.interrupt(); }//endif synchronized (requestors) { for (Iterator iter = requestors.iterator(); iter.hasNext(); ) { Thread t = (Thread) iter.next(); t.interrupt(); } if (respondeeThread != null) respondeeThread.interrupt(); } if(announceeThread != null) { announceeThread.interrupt(); }//endif synchronized (pendingDiscoveries) { terminateTaskMgr(); Iterator i = tickets.iterator(); while (i.hasNext()) { Ticket t = (Ticket) i.next(); i.remove(); discoveryWakeupMgr.cancel(t); } if (isDefaultWakeupMgr) { // cancelAll should be a no-op in this case, // but just be sure. discoveryWakeupMgr.cancelAll(); discoveryWakeupMgr.stop(); } }//end sync return null; }//end run });//end doPrivileged }//end nukeThreads /** This method removes all pending and active tasks from the TaskManager * for this instance. It also clears the set of pendingDiscoveries, and * closes all associated sockets. */ private void terminateTaskMgr() { synchronized(taskManager) { /* Remove all pending tasks */ ArrayList pendingTasks = taskManager.getPending(); for(int i=0;i<pendingTasks.size();i++) { taskManager.remove((TaskManager.Task)pendingTasks.get(i)); }//end loop /* Clear pendingDiscoveries and close all associated sockets */ synchronized (pendingDiscoveries) { for(Iterator iter = pendingDiscoveries.iterator(); iter.hasNext();) { Object req = iter.next(); iter.remove(); if (req instanceof Socket) { try { ((Socket)req).close(); } catch (IOException e) { /* ignore */ } }//endif }//end loop }//end sync /* Interrupt active TaskThreads, prepare the taskManager for GC. */ taskManager.terminate(); }//end sync(taskManager) synchronized(pendingNotifies) { pendingNotifies.clear(); }//end sync }//end terminateTaskMgr /** After a possible change in the member groups of the * <code>ServiceRegistrar</code> corresponding to the given * <code>UnicastResponse</code> parameter, this method * determines whether or not the registrar's member groups have * changed in such a way that either a changed event or a discarded * event is warranted. * <p> * Note that even if the contents of the new set of groups initially * indicate that the corresponding registrar is a candidate for * a discarded or a changed event, further analysis must be performed. * This is because there is no guarantee that the new set of member * groups have not been "split" across the multicast announcements * sent by the lookup service; and so there is no guarantee that the * contents of the new group set actually reflect a change that warrants * an event. To guarantee that the new group set accurately reflects * the registrar's member groups, this method makes a remote call to * the registrar to retrieve its actual member groups. * <p> * There is one situation where it is not necessary to query the * registrar for its current member groups. That situation is * when the set of groups input to the <code>newGroups</code> parameter * is equivalent to NO_GROUPS. If that new group set is equivalent * to NO_GROUPS, it is guaranteed that the registrar's member groups * have not been split across the multicast announcements. * * @param response instance of <code>UnicastResponse</code> * corresponding to the registrar whose current and * previous member groups are to be compared * @param newGroups <code>String</code> array containing the new * member groups of the registrar corresponding to the * <code>response</code> parameter (just after a * possible change) */ private void maybeSendEvent(UnicastResponse response, String[] newGroups) { ServiceRegistrar reg = response.getRegistrar(); boolean getActual = true; if(newGroups == null) { // newGroups null means get actual groups now newGroups = getActualGroups(reg); if(newGroups == null) return; // if null, then it was discarded getActual = false; }//endif if(groupSetsEqual(response.getGroups(),newGroups)) return; String[] actualGroups = newGroups; if( getActual && (newGroups.length > 0) ) { actualGroups = getActualGroups(reg); if(actualGroups == null) return; // null ==> was already discarded }//endif synchronized (registrars) { // Other events may have occured to registrars while we were // making our remote call. UnicastResponse resp = (UnicastResponse) registrars.get(reg.getServiceID()); if (resp == null) { // The registrar was discarded in the meantime. Oh well. return; } notifyOnGroupChange(reg, resp.getGroups(), actualGroups); } }//end maybeSendEvent /** After a possible change in the member groups of the given * <code>ServiceRegistrar</code> parameter, this method compares * the registrar's original set of member groups to its new set * of member groups. * <p> * If the criteria shown below is satisfied, either a discarded event * or a changed event will be sent to any registered listeners. The * criteria is based on whether the old and new groups are equal, * and whether one or more elements of the new group set also belong * to the set of groups to discover (the new groups are "still of * interest"). The criteria is as follows: * <p> * if (old groups and new groups) * <p><ul> * <li> (not equal but stillInterested) --> send a changed event * <li> (!stillInterested) --> send a discarded event * </ul> * <p> * * @param reg instance of <code>ServiceRegistrar</code> * corresponding to the registrar whose current and * previous member groups are to be compared; and * whose corresponding service ID is used as the key * into the various data structures that contain * pertinent information about that registrar * @param oldGroups <code>String</code> array containing the member * groups of the <code>reg</code> parameter prior to * being changed * @param oldGroups <code>String</code> array containing the current * member groups of the <code>reg</code> parameter * (just after a possible change) */ private void notifyOnGroupChange(ServiceRegistrar reg, String[] oldGroups, String[] newGroups) { boolean equal = groupSetsEqual(oldGroups,newGroups); boolean stillInterested = groupsOverlap(newGroups); if(!equal && stillInterested) { sendChanged(reg,newGroups); } else if(!stillInterested) { sendDiscarded(reg,newGroups); }//endif }//end notifyOnGroupChange /** Convenience method that sends a discarded event containing only * one registrar to all registered listeners. This method must be * called from within a block that is synchronized on the registrars * map. * * @param reg instance of <code>ServiceRegistrar</code> * corresponding to the registrar to include in the * event * @param curGroups <code>String</code> array containing the current * member groups of the registrar referenced by the * <code>reg</code> parameter */ private void sendDiscarded(ServiceRegistrar reg, String[] curGroups) { ServiceID srvcID = reg.getServiceID(); if(curGroups == null) { // discard request is from external source UnicastResponse resp = (UnicastResponse)registrars.get(srvcID); if(resp == null) return; curGroups = resp.getGroups(); }//endif if( registrars.remove(srvcID) != null ) { regInfo.remove(srvcID); if( !listeners.isEmpty() ) { addNotify((ArrayList)listeners.clone(), mapRegToGroups(reg,curGroups), DISCARDED); }//endif }//endif }//end sendDiscarded /** Convenience method that sends a changed event containing only * one registrar to all registered listeners that are interested in * such events. This method must be called from within a block that * is synchronized on the registrars map. * * @param reg instance of <code>ServiceRegistrar</code> * corresponding to the registrar to include in the * event * @param curGroups <code>String</code> array containing the current * member groups of the registrar referenced by the * <code>reg</code> parameter */ private void sendChanged(ServiceRegistrar reg, String[] curGroups) { /* replace old groups with new; prevents repeated changed events */ UnicastResponse resp = (UnicastResponse)registrars.get(reg.getServiceID()); registrars.put(reg.getServiceID(), new UnicastResponse(resp.getHost(), resp.getPort(), curGroups, resp.getRegistrar())); if( !listeners.isEmpty() ) { addNotify((ArrayList)listeners.clone(), mapRegToGroups(reg,curGroups), CHANGED); }//endif }//end sendChanged /** Creates and returns a deep copy of the input parameter. This method * assumes the input map is a HashMap of the registrar-to-groups mapping; * and returns a clone not only of the map, but of each key-value pair * contained in the mapping. * * @param groupsMap mapping from a set of registrars to the member groups * of each registrar * * @return clone of the input map, and of each key-value pair contained * in the input map */ private Map deepCopy(HashMap groupsMap) { /* clone the input HashMap */ HashMap newMap = (HashMap)(groupsMap.clone()); /* clone the values of each mapping in place */ Set eSet = newMap.entrySet(); for(Iterator itr = eSet.iterator(); itr.hasNext(); ) { Map.Entry pair = (Map.Entry)itr.next(); /* only need to clone the value of the order pair */ pair.setValue( ((String[])pair.getValue()).clone() ); } return newMap; }//end deepCopy /** This method retrieves from the given <code>ServiceRegistrar</code>, * the current groups in which that registrar is a member. If the * registrar is un-reachable, then this method will discard the * registrar. * * @param reg instance of <code>ServiceRegistrar</code> referencing the * registrar whose member groups are to be retrieved and returned * * @return <code>String</code> array containing the current member groups * of the registrar referenced by the <code>reg</code> parameter */ private String[] getActualGroups(final ServiceRegistrar reg) { /* The retrieval of the member groups of the given ServiceRegistrar * is performed inside a doPrivileged block that restores the access * control context that was in place when this utility was created. * * This is done because the call to getGroups() below is executed * on a proxy to the given ServiceRegistrar; which may be downloaded * code supplied by a 3rd party. With respect to downloaded, 3rd party * code, it is not desirable to allow such code to execute with more * priviledges than the client that created this utility. Therefore, * before executing getGroups() on the given proxy, the client's * Subject should be restored, and the proxy code should be * restricted to doing nothing more than the client itself is * allowed to do. */ try { return (String[])AccessController.doPrivileged ( securityContext.wrap(new PrivilegedExceptionAction() { public Object run() throws RemoteException { return reg.getGroups(); }//end run }),//end PrivilegedExceptionAction and wrap securityContext.getAccessControlContext());//end doPriv } catch(Throwable e) { /* A RemoteException, wrapped in a PriviligedActionException, * occurred. This means that the reg is unreachable; discard it. */ discard(reg); return null; }//end try }//end getActualGroups /** Convenience method that creates and returns a mapping of a single * <code>ServiceRegistrar</code> instance to a set of groups. * * @param reg instance of <code>ServiceRegistrar</code> * corresponding to the registrar to use as the key * to the mapping * @param curGroups <code>String</code> array containing the current * member groups of the registrar referenced by the * <code>reg</code> parameter; and which is used * as the value of the mapping * * @return <code>Map</code> instance containing a single mapping from * a given registrar to its current member groups */ private Map mapRegToGroups(ServiceRegistrar reg, String[] curGroups) { HashMap groupsMap = new HashMap(1); groupsMap.put(reg,curGroups); return groupsMap; }//end mapRegToGroups /** * This method is used by the public methods of this class that are * specified to throw a <code>NullPointerException</code> when the given * array of group names contains one or more <code>null</code> elements; * in which case, this method throws a <code>NullPointerException</code> * which should be allowed to propagate outward. * * @throws java.lang.NullPointerException this exception occurs when * one or more of the elements of the <code>groupArray</code> * parameter is <code>null</code>. */ private void testArrayForNullElement(String[] groupArray) { if(groupArray == null) return; for(int i=0;i<groupArray.length;i++) { if(groupArray[i] == null) { throw new NullPointerException("null element in group array"); }//endif }//end loop }//end testArrayForNullElement /** * Using the given <code>Configuration</code>, initializes the current * instance of this utility, and initiates the discovery process for * the given set of groups. * * @param groups the set of group names to discover * * @param config an instance of <code>Configuration</code>, used to * obtain the objects needed to configure this utility * * @throws java.lang.NullPointerException input array contains at least * one <code>null</code> element or <code>null</code> is input * for the configuration * * @throws java.io.IOException an exception occurred when initiating * discovery processing * * @throws net.jini.config.ConfigurationException indicates an exception * occurred while retrieving an item from the given * <code>Configuration</code> */ private void beginDiscovery(String[] groups, Configuration config) throws IOException, ConfigurationException { testArrayForNullElement(groups); checkGroups(groups); if (groups != null) { this.groups = new HashSet(groups.length * 2); for (int i = 0; i < groups.length; i++) { this.groups.add(groups[i]); }//end loop }//endif init(config); if(nicsToUse == NICS_USE_NONE) return;//disable discovery try { Security.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { announceeThread = new AnnouncementListener(); announcementTimerThread = new AnnouncementTimerThread(); return null; }//end run });//end doPrivileged } catch (PrivilegedActionException e) { throw (IOException)e.getException(); } if (this.groups == null || !this.groups.isEmpty()) { requestGroups(this.groups); }//endif announceeThread.start(); announcementTimerThread.start(); }//end beginDiscovery /* Convenience method that encapsulates the retrieval of the configurable * items from the given <code>Configuration</code> object. */ private void init(Configuration config) throws IOException, ConfigurationException { if(config == null) throw new NullPointerException("config is null"); /* Lookup service proxy preparer */ registrarPreparer = (ProxyPreparer)config.getEntry (COMPONENT_NAME, "registrarPreparer", ProxyPreparer.class, new BasicProxyPreparer()); /* constraints */ MethodConstraints constraints = (MethodConstraints)config.getEntry (COMPONENT_NAME, "discoveryConstraints", MethodConstraints.class, null); if (constraints == null) { constraints = new BasicMethodConstraints(InvocationConstraints.EMPTY); } multicastRequestConstraints = DiscoveryConstraints.process( constraints.getConstraints( DiscoveryConstraints.multicastRequestMethod)); multicastAnnouncementConstraints = DiscoveryConstraints.process( constraints.getConstraints( DiscoveryConstraints.multicastAnnouncementMethod)); rawUnicastDiscoveryConstraints = constraints.getConstraints( DiscoveryConstraints.unicastDiscoveryMethod); /* Task manager */ try { taskManager = (TaskManager)config.getEntry(COMPONENT_NAME, "taskManager", TaskManager.class); } catch(NoSuchEntryException e) { /* use default */ taskManager = new TaskManager(MAX_N_TASKS,(15*1000),1.0f); } /* Multicast request-related configuration items */ multicastRequestMax = ( (Integer)config.getEntry (COMPONENT_NAME, "multicastRequestMax", int.class, new Integer(multicastRequestMax) ) ).intValue(); multicastRequestInterval = ( (Long)config.getEntry (COMPONENT_NAME, "multicastRequestInterval", long.class, new Long(multicastRequestInterval) ) ).longValue(); finalMulticastRequestInterval = ( (Long)config.getEntry (COMPONENT_NAME, "finalMulticastRequestInterval", long.class, new Long(finalMulticastRequestInterval) ) ).longValue(); try { multicastRequestHost = (String) Config.getNonNullEntry(config, COMPONENT_NAME, "multicastRequestHost", String.class); } catch (NoSuchEntryException nse) { multicastRequestHost = getLocalHost(); } /* Configuration items related to the network interface(s) */ try { nics = (NetworkInterface[])config.getEntry (COMPONENT_NAME, "multicastInterfaces", NetworkInterface[].class); if(nics == null) { nicsToUse = NICS_USE_SYS; logger.config("LookupDiscovery - using system default network " +"interface for multicast"); } else {//(nics != null) if( nics.length == 0 ) { nicsToUse = NICS_USE_NONE; logger.config("LookupDiscovery - MULTICAST DISABLED"); } else {//(nics.length > 0), use the given specific list nicsToUse = NICS_USE_LIST; if( logger.isLoggable(Level.CONFIG) ) { logger.log(Level.CONFIG, "LookupDiscovery - multicast network " +"interface(s): {0}", Arrays.asList(nics) ); }//endif }//endif }//endif } catch(NoSuchEntryException e) {// no config item, use default - all Enumeration en = NetworkInterface.getNetworkInterfaces(); List nicList = (en != null) ? Collections.list(en) : Collections.EMPTY_LIST; nics = (NetworkInterface[])(nicList.toArray (new NetworkInterface[nicList.size()]) ); nicsToUse = NICS_USE_ALL; if( logger.isLoggable(Level.CONFIG) ) { logger.log(Level.CONFIG,"LookupDiscovery - multicast network " +"interface(s): {0}", nicList); }//endif } nicRetryInterval = ( (Integer)config.getEntry (COMPONENT_NAME, "multicastInterfaceRetryInterval", int.class, new Integer(nicRetryInterval) ) ).intValue(); /* Multicast announcement-related configuration items */ multicastAnnouncementInterval = ( (Long)config.getEntry (COMPONENT_NAME, "multicastAnnouncementInterval", long.class, new Long(multicastAnnouncementInterval) ) ).longValue(); unicastDelayRange = Config.getLongEntry(config, COMPONENT_NAME, "unicastDelayRange", 0, 0, Long.MAX_VALUE); tickets = new ArrayList(); if (unicastDelayRange > 0) { /* Wakeup manager */ try { discoveryWakeupMgr = (WakeupManager)config.getEntry(COMPONENT_NAME, "wakeupManager", WakeupManager.class); } catch(NoSuchEntryException e) { /* use default */ discoveryWakeupMgr = new WakeupManager( new WakeupManager.ThreadDesc(null, true)); isDefaultWakeupMgr = true; } } initialMulticastRequestDelayRange = Config.getLongEntry(config, COMPONENT_NAME, "initialMulticastRequestDelayRange", 0, 0, Long.MAX_VALUE); }//end init /** * Decodes received multicast announcement packet. Constraint checking is * delayed. */ private MulticastAnnouncement decodeMulticastAnnouncement( final DatagramPacket pkt) throws IOException { // REMIND: cache recently received announcements to skip re-decoding? int pv; try { pv = ByteBuffer.wrap( pkt.getData(), pkt.getOffset(), pkt.getLength()).getInt(); } catch (BufferUnderflowException e) { throw new DiscoveryProtocolException(null, e); } multicastAnnouncementConstraints.checkProtocolVersion(pv); final Discovery disco = getDiscovery(pv); try { return (MulticastAnnouncement) AccessController.doPrivileged( securityContext.wrap(new PrivilegedExceptionAction() { public Object run() throws IOException { return disco.decodeMulticastAnnouncement( pkt, multicastAnnouncementConstraints. getUnfulfilledConstraints(), true); } }), securityContext.getAccessControlContext()); } catch (PrivilegedActionException e) { throw (IOException) e.getCause(); } } /** * Encodes outgoing multicast requests based on protocol in use, applying * configured security constraints (if any). */ private DatagramPacket[] encodeMulticastRequest(final MulticastRequest req) throws IOException { // REMIND: cache latest request to skip re-encoding final Discovery disco = getDiscovery( multicastRequestConstraints.chooseProtocolVersion()); final List packets = new ArrayList(); AccessController.doPrivileged( securityContext.wrap(new PrivilegedAction() { public Object run() { EncodeIterator ei = disco.encodeMulticastRequest( req, multicastRequestConstraints.getMulticastMaxPacketSize( DEFAULT_MAX_PACKET_SIZE), multicastRequestConstraints.getUnfulfilledConstraints() ); while (ei.hasNext()) { try { packets.addAll(Arrays.asList(ei.next())); } catch (Exception e) { logger.log( (e instanceof UnsupportedConstraintException) ? Levels.HANDLED : Level.INFO, "exception encoding multicast request", e); } } return null; } }), securityContext.getAccessControlContext()); if (packets.isEmpty()) { throw new DiscoveryProtocolException("no encoded requests"); } return (DatagramPacket[]) packets.toArray( new DatagramPacket[packets.size()]); } /** * Performs unicast discovery over given socket based on protocol in use, * applying configured security constraints (if any). */ private UnicastResponse doUnicastDiscovery( final Socket socket, final DiscoveryConstraints unicastDiscoveryConstraints, final Discovery disco) throws IOException, ClassNotFoundException { try { return (UnicastResponse) AccessController.doPrivileged( securityContext.wrap(new PrivilegedExceptionAction() { public Object run() throws Exception { return disco.doUnicastDiscovery( socket, unicastDiscoveryConstraints. getUnfulfilledConstraints(), null, null, null); } }), securityContext.getAccessControlContext()); } catch (PrivilegedActionException e) { Throwable t = e.getCause(); if (t instanceof IOException) { throw (IOException) t; } else if (t instanceof ClassNotFoundException) { throw (ClassNotFoundException) t; } else { throw new AssertionError(t); } } } private UnicastResponse doUnicastDiscovery( final Socket socket, final DiscoveryConstraints unicastDiscoveryConstraints) throws IOException, ClassNotFoundException { Discovery disco = getDiscovery(unicastDiscoveryConstraints.chooseProtocolVersion()); return doUnicastDiscovery(socket, unicastDiscoveryConstraints, disco); } /** * Returns Discovery instance for the given version, or throws * DiscoveryProtocolException if the version is unsupported. */ private Discovery getDiscovery(int version) throws DiscoveryProtocolException { switch (version) { case Discovery.PROTOCOL_VERSION_1: return Discovery.getProtocol1(); case Discovery.PROTOCOL_VERSION_2: return protocol2; default: throw new DiscoveryProtocolException( "unsupported protocol version: " + version); } } /** * Holder class for the time and sequence number of the last * received announcement. The regInfo map contains instances of this * class as values. */ private static class AnnouncementInfo { private long tStamp; private long seqNum; private AnnouncementInfo(long tStamp, long seqNum) { this.tStamp = tStamp; this.seqNum = seqNum; } } }//end class LookupDiscovery