| /* |
| * Copyright (c) 1999, 2007, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| |
| package com.sun.jmx.snmp.daemon; |
| |
| |
| |
| // java import |
| // |
| import java.io.ObjectInputStream; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.util.Enumeration; |
| import java.util.logging.Level; |
| import java.util.Vector; |
| import java.util.NoSuchElementException; |
| |
| // jmx import |
| // |
| import javax.management.MBeanServer; |
| import javax.management.MBeanRegistration; |
| import javax.management.ObjectName; |
| import javax.management.NotificationListener; |
| import javax.management.NotificationFilter; |
| import javax.management.NotificationBroadcaster; |
| import javax.management.NotificationBroadcasterSupport; |
| import javax.management.MBeanNotificationInfo; |
| import javax.management.AttributeChangeNotification; |
| import javax.management.ListenerNotFoundException; |
| import javax.management.loading.ClassLoaderRepository; |
| import javax.management.MBeanServerFactory; |
| |
| import static com.sun.jmx.defaults.JmxProperties.SNMP_ADAPTOR_LOGGER; |
| |
| // JSR 160 import |
| // |
| // XXX Revisit: |
| // used to import com.sun.jmx.snmp.MBeanServerForwarder |
| // Now using JSR 160 instead. => this is an additional |
| // dependency to JSR 160. |
| // |
| import javax.management.remote.MBeanServerForwarder; |
| |
| /** |
| * Defines generic behavior for the server part of a connector or an adaptor. |
| * Most connectors or adaptors extend <CODE>CommunicatorServer</CODE> |
| * and inherit this behavior. Connectors or adaptors that do not fit into |
| * this model do not extend <CODE>CommunicatorServer</CODE>. |
| * <p> |
| * A <CODE>CommunicatorServer</CODE> is an active object, it listens for |
| * client requests and processes them in its own thread. When necessary, a |
| * <CODE>CommunicatorServer</CODE> creates other threads to process multiple |
| * requests concurrently. |
| * <p> |
| * A <CODE>CommunicatorServer</CODE> object can be stopped by calling the |
| * <CODE>stop</CODE> method. When it is stopped, the |
| * <CODE>CommunicatorServer</CODE> no longer listens to client requests and |
| * no longer holds any thread or communication resources. |
| * It can be started again by calling the <CODE>start</CODE> method. |
| * <p> |
| * A <CODE>CommunicatorServer</CODE> has a <CODE>State</CODE> attribute |
| * which reflects its activity. |
| * <p> |
| * <TABLE> |
| * <TR><TH>CommunicatorServer</TH> <TH>State</TH></TR> |
| * <TR><TD><CODE>stopped</CODE></TD> <TD><CODE>OFFLINE</CODE></TD></TR> |
| * <TR><TD><CODE>starting</CODE></TD> <TD><CODE>STARTING</CODE></TD></TR> |
| * <TR><TD><CODE>running</CODE></TD> <TD><CODE>ONLINE</CODE></TD></TR> |
| * <TR><TD><CODE>stopping</CODE></TD> <TD><CODE>STOPPING</CODE></TD></TR> |
| * </TABLE> |
| * <p> |
| * The <CODE>STARTING</CODE> state marks the transition |
| * from <CODE>OFFLINE</CODE> to <CODE>ONLINE</CODE>. |
| * <p> |
| * The <CODE>STOPPING</CODE> state marks the transition from |
| * <CODE>ONLINE</CODE> to <CODE>OFFLINE</CODE>. This occurs when the |
| * <CODE>CommunicatorServer</CODE> is finishing or interrupting active |
| * requests. |
| * <p> |
| * When a <CODE>CommunicatorServer</CODE> is unregistered from the MBeanServer, |
| * it is stopped automatically. |
| * <p> |
| * When the value of the <CODE>State</CODE> attribute changes the |
| * <CODE>CommunicatorServer</CODE> sends a |
| * <tt>{@link javax.management.AttributeChangeNotification}</tt> to the |
| * registered listeners, if any. |
| * |
| * <p><b>This API is a Sun Microsystems internal API and is subject |
| * to change without notice.</b></p> |
| */ |
| |
| public abstract class CommunicatorServer |
| implements Runnable, MBeanRegistration, NotificationBroadcaster, |
| CommunicatorServerMBean { |
| |
| // |
| // States of a CommunicatorServer |
| // |
| |
| /** |
| * Represents an <CODE>ONLINE</CODE> state. |
| */ |
| public static final int ONLINE = 0 ; |
| |
| /** |
| * Represents an <CODE>OFFLINE</CODE> state. |
| */ |
| public static final int OFFLINE = 1 ; |
| |
| /** |
| * Represents a <CODE>STOPPING</CODE> state. |
| */ |
| public static final int STOPPING = 2 ; |
| |
| /** |
| * Represents a <CODE>STARTING</CODE> state. |
| */ |
| public static final int STARTING = 3 ; |
| |
| // |
| // Types of connectors. |
| // |
| |
| /** |
| * Indicates that it is an RMI connector type. |
| */ |
| //public static final int RMI_TYPE = 1 ; |
| |
| /** |
| * Indicates that it is an HTTP connector type. |
| */ |
| //public static final int HTTP_TYPE = 2 ; |
| |
| /** |
| * Indicates that it is an HTML connector type. |
| */ |
| //public static final int HTML_TYPE = 3 ; |
| |
| /** |
| * Indicates that it is an SNMP connector type. |
| */ |
| public static final int SNMP_TYPE = 4 ; |
| |
| /** |
| * Indicates that it is an HTTPS connector type. |
| */ |
| //public static final int HTTPS_TYPE = 5 ; |
| |
| // |
| // Package variables |
| // |
| |
| /** |
| * The state of the connector server. |
| */ |
| transient volatile int state = OFFLINE ; |
| |
| /** |
| * The object name of the connector server. |
| * @serial |
| */ |
| ObjectName objectName ; |
| |
| MBeanServer topMBS; |
| MBeanServer bottomMBS; |
| |
| /** |
| */ |
| transient String dbgTag = null ; |
| |
| /** |
| * The maximum number of clients that the CommunicatorServer can |
| * process concurrently. |
| * @serial |
| */ |
| int maxActiveClientCount = 1 ; |
| |
| /** |
| */ |
| transient int servedClientCount = 0 ; |
| |
| /** |
| * The host name used by this CommunicatorServer. |
| * @serial |
| */ |
| String host = null ; |
| |
| /** |
| * The port number used by this CommunicatorServer. |
| * @serial |
| */ |
| int port = -1 ; |
| |
| |
| // |
| // Private fields |
| // |
| |
| /* This object controls access to the "state" and "interrupted" variables. |
| If held at the same time as the lock on "this", the "this" lock must |
| be taken first. */ |
| private transient Object stateLock = new Object(); |
| |
| private transient Vector<ClientHandler> |
| clientHandlerVector = new Vector<ClientHandler>() ; |
| |
| private transient Thread fatherThread = Thread.currentThread() ; |
| private transient Thread mainThread = null ; |
| |
| private volatile boolean stopRequested = false ; |
| private boolean interrupted = false; |
| private transient Exception startException = null; |
| |
| // Notifs count, broadcaster and info |
| private transient long notifCount = 0; |
| private transient NotificationBroadcasterSupport notifBroadcaster = |
| new NotificationBroadcasterSupport(); |
| private transient MBeanNotificationInfo[] notifInfos = null; |
| |
| |
| /** |
| * Instantiates a <CODE>CommunicatorServer</CODE>. |
| * |
| * @param connectorType Indicates the connector type. Possible values are: |
| * SNMP_TYPE. |
| * |
| * @exception <CODE>java.lang.IllegalArgumentException</CODE> |
| * This connector type is not correct. |
| */ |
| public CommunicatorServer(int connectorType) |
| throws IllegalArgumentException { |
| switch (connectorType) { |
| case SNMP_TYPE : |
| //No op. int Type deciding debugging removed. |
| break; |
| default: |
| throw new IllegalArgumentException("Invalid connector Type") ; |
| } |
| dbgTag = makeDebugTag() ; |
| } |
| |
| protected Thread createMainThread() { |
| return new Thread (this, makeThreadName()); |
| } |
| |
| /** |
| * Starts this <CODE>CommunicatorServer</CODE>. |
| * <p> |
| * Has no effect if this <CODE>CommunicatorServer</CODE> is |
| * <CODE>ONLINE</CODE> or <CODE>STOPPING</CODE>. |
| * @param timeout Time in ms to wait for the connector to start. |
| * If <code>timeout</code> is positive, wait for at most |
| * the specified time. An infinite timeout can be specified |
| * by passing a <code>timeout</code> value equals |
| * <code>Long.MAX_VALUE</code>. In that case the method |
| * will wait until the connector starts or fails to start. |
| * If timeout is negative or zero, returns as soon as possible |
| * without waiting. |
| * @exception CommunicationException if the connectors fails to start. |
| * @exception InterruptedException if the thread is interrupted or the |
| * timeout expires. |
| */ |
| public void start(long timeout) |
| throws CommunicationException, InterruptedException { |
| boolean start; |
| |
| synchronized (stateLock) { |
| if (state == STOPPING) { |
| // Fix for bug 4352451: |
| // "java.net.BindException: Address in use". |
| waitState(OFFLINE, 60000); |
| } |
| start = (state == OFFLINE); |
| if (start) { |
| changeState(STARTING); |
| stopRequested = false; |
| interrupted = false; |
| startException = null; |
| } |
| } |
| |
| if (!start) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "start","Connector is not OFFLINE"); |
| } |
| return; |
| } |
| |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "start","--> Start connector "); |
| } |
| |
| mainThread = createMainThread(); |
| |
| mainThread.start() ; |
| |
| if (timeout > 0) waitForStart(timeout); |
| } |
| |
| /** |
| * Starts this <CODE>CommunicatorServer</CODE>. |
| * <p> |
| * Has no effect if this <CODE>CommunicatorServer</CODE> is |
| * <CODE>ONLINE</CODE> or <CODE>STOPPING</CODE>. |
| */ |
| public void start() { |
| try { |
| start(0); |
| } catch (InterruptedException x) { |
| // cannot happen because of `0' |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "start","interrupted", x); |
| } |
| } |
| } |
| |
| /** |
| * Stops this <CODE>CommunicatorServer</CODE>. |
| * <p> |
| * Has no effect if this <CODE>CommunicatorServer</CODE> is |
| * <CODE>OFFLINE</CODE> or <CODE>STOPPING</CODE>. |
| */ |
| public void stop() { |
| synchronized (stateLock) { |
| if (state == OFFLINE || state == STOPPING) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "stop","Connector is not ONLINE"); |
| } |
| return; |
| } |
| changeState(STOPPING); |
| // |
| // Stop the connector thread |
| // |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "stop","Interrupt main thread"); |
| } |
| stopRequested = true ; |
| if (!interrupted) { |
| interrupted = true; |
| mainThread.interrupt(); |
| } |
| } |
| |
| // |
| // Call terminate on each active client handler |
| // |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "stop","terminateAllClient"); |
| } |
| terminateAllClient() ; |
| |
| // ---------------------- |
| // changeState |
| // ---------------------- |
| synchronized (stateLock) { |
| if (state == STARTING) |
| changeState(OFFLINE); |
| } |
| } |
| |
| /** |
| * Tests whether the <CODE>CommunicatorServer</CODE> is active. |
| * |
| * @return True if connector is <CODE>ONLINE</CODE>; false otherwise. |
| */ |
| public boolean isActive() { |
| synchronized (stateLock) { |
| return (state == ONLINE); |
| } |
| } |
| |
| /** |
| * <p>Waits until either the State attribute of this MBean equals the |
| * specified <VAR>wantedState</VAR> parameter, |
| * or the specified <VAR>timeOut</VAR> has elapsed. |
| * The method <CODE>waitState</CODE> returns with a boolean value |
| * indicating whether the specified <VAR>wantedState</VAR> parameter |
| * equals the value of this MBean's State attribute at the time the method |
| * terminates.</p> |
| * |
| * <p>Two special cases for the <VAR>timeOut</VAR> parameter value are:</p> |
| * <UL><LI> if <VAR>timeOut</VAR> is negative then <CODE>waitState</CODE> |
| * returns immediately (i.e. does not wait at all),</LI> |
| * <LI> if <VAR>timeOut</VAR> equals zero then <CODE>waitState</CODE> |
| * waits untill the value of this MBean's State attribute |
| * is the same as the <VAR>wantedState</VAR> parameter (i.e. will wait |
| * indefinitely if this condition is never met).</LI></UL> |
| * |
| * @param wantedState The value of this MBean's State attribute to wait |
| * for. <VAR>wantedState</VAR> can be one of: |
| * <ul> |
| * <li><CODE>CommunicatorServer.OFFLINE</CODE>,</li> |
| * <li><CODE>CommunicatorServer.ONLINE</CODE>,</li> |
| * <li><CODE>CommunicatorServer.STARTING</CODE>,</li> |
| * <li><CODE>CommunicatorServer.STOPPING</CODE>.</li> |
| * </ul> |
| * @param timeOut The maximum time to wait for, in milliseconds, |
| * if positive. |
| * Infinite time out if 0, or no waiting at all if negative. |
| * |
| * @return true if the value of this MBean's State attribute is the |
| * same as the <VAR>wantedState</VAR> parameter; false otherwise. |
| */ |
| public boolean waitState(int wantedState, long timeOut) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitState", wantedState + "(0on,1off,2st) TO=" + timeOut + |
| " ; current state = " + getStateString()); |
| } |
| |
| long endTime = 0; |
| if (timeOut > 0) |
| endTime = System.currentTimeMillis() + timeOut; |
| |
| synchronized (stateLock) { |
| while (state != wantedState) { |
| if (timeOut < 0) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitState", "timeOut < 0, return without wait"); |
| } |
| return false; |
| } else { |
| try { |
| if (timeOut > 0) { |
| long toWait = endTime - System.currentTimeMillis(); |
| if (toWait <= 0) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitState", "timed out"); |
| } |
| return false; |
| } |
| stateLock.wait(toWait); |
| } else { // timeOut == 0 |
| stateLock.wait(); |
| } |
| } catch (InterruptedException e) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitState", "wait interrupted"); |
| } |
| return (state == wantedState); |
| } |
| } |
| } |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitState","returning in desired state"); |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * <p>Waits until the communicator is started or timeout expires. |
| * |
| * @param timeout Time in ms to wait for the connector to start. |
| * If <code>timeout</code> is positive, wait for at most |
| * the specified time. An infinite timeout can be specified |
| * by passing a <code>timeout</code> value equals |
| * <code>Long.MAX_VALUE</code>. In that case the method |
| * will wait until the connector starts or fails to start. |
| * If timeout is negative or zero, returns as soon as possible |
| * without waiting. |
| * |
| * @exception CommunicationException if the connectors fails to start. |
| * @exception InterruptedException if the thread is interrupted or the |
| * timeout expires. |
| * |
| */ |
| private void waitForStart(long timeout) |
| throws CommunicationException, InterruptedException { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitForStart", "Timeout=" + timeout + |
| " ; current state = " + getStateString()); |
| } |
| |
| final long startTime = System.currentTimeMillis(); |
| |
| synchronized (stateLock) { |
| while (state == STARTING) { |
| // Time elapsed since startTime... |
| // |
| final long elapsed = System.currentTimeMillis() - startTime; |
| |
| // wait for timeout - elapsed. |
| // A timeout of Long.MAX_VALUE is equivalent to something |
| // like 292271023 years - which is pretty close to |
| // forever as far as we are concerned ;-) |
| // |
| final long remainingTime = timeout-elapsed; |
| |
| // If remainingTime is negative, the timeout has elapsed. |
| // |
| if (remainingTime < 0) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitForStart", "timeout < 0, return without wait"); |
| } |
| throw new InterruptedException("Timeout expired"); |
| } |
| |
| // We're going to wait until someone notifies on the |
| // the stateLock object, or until the timeout expires, |
| // or until the thread is interrupted. |
| // |
| try { |
| stateLock.wait(remainingTime); |
| } catch (InterruptedException e) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitForStart", "wait interrupted"); |
| } |
| |
| // If we are now ONLINE, then no need to rethrow the |
| // exception... we're simply going to exit the while |
| // loop. Otherwise, throw the InterruptedException. |
| // |
| if (state != ONLINE) throw e; |
| } |
| } |
| |
| // We're no longer in STARTING state |
| // |
| if (state == ONLINE) { |
| // OK, we're started, everything went fine, just return |
| // |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitForStart", "started"); |
| } |
| return; |
| } else if (startException instanceof CommunicationException) { |
| // There was some exception during the starting phase. |
| // Cast and throw... |
| // |
| throw (CommunicationException)startException; |
| } else if (startException instanceof InterruptedException) { |
| // There was some exception during the starting phase. |
| // Cast and throw... |
| // |
| throw (InterruptedException)startException; |
| } else if (startException != null) { |
| // There was some exception during the starting phase. |
| // Wrap and throw... |
| // |
| throw new CommunicationException(startException, |
| "Failed to start: "+ |
| startException); |
| } else { |
| // We're not ONLINE, and there's no exception... |
| // Something went wrong but we don't know what... |
| // |
| throw new CommunicationException("Failed to start: state is "+ |
| getStringForState(state)); |
| } |
| } |
| } |
| |
| /** |
| * Gets the state of this <CODE>CommunicatorServer</CODE> as an integer. |
| * |
| * @return <CODE>ONLINE</CODE>, <CODE>OFFLINE</CODE>, |
| * <CODE>STARTING</CODE> or <CODE>STOPPING</CODE>. |
| */ |
| public int getState() { |
| synchronized (stateLock) { |
| return state ; |
| } |
| } |
| |
| /** |
| * Gets the state of this <CODE>CommunicatorServer</CODE> as a string. |
| * |
| * @return One of the strings "ONLINE", "OFFLINE", "STARTING" or |
| * "STOPPING". |
| */ |
| public String getStateString() { |
| return getStringForState(state) ; |
| } |
| |
| /** |
| * Gets the host name used by this <CODE>CommunicatorServer</CODE>. |
| * |
| * @return The host name used by this <CODE>CommunicatorServer</CODE>. |
| */ |
| public String getHost() { |
| try { |
| host = InetAddress.getLocalHost().getHostName(); |
| } catch (Exception e) { |
| host = "Unknown host"; |
| } |
| return host ; |
| } |
| |
| /** |
| * Gets the port number used by this <CODE>CommunicatorServer</CODE>. |
| * |
| * @return The port number used by this <CODE>CommunicatorServer</CODE>. |
| */ |
| public int getPort() { |
| synchronized (stateLock) { |
| return port ; |
| } |
| } |
| |
| /** |
| * Sets the port number used by this <CODE>CommunicatorServer</CODE>. |
| * |
| * @param port The port number used by this |
| * <CODE>CommunicatorServer</CODE>. |
| * |
| * @exception java.lang.IllegalStateException This method has been invoked |
| * while the communicator was ONLINE or STARTING. |
| */ |
| public void setPort(int port) throws java.lang.IllegalStateException { |
| synchronized (stateLock) { |
| if ((state == ONLINE) || (state == STARTING)) |
| throw new IllegalStateException("Stop server before " + |
| "carrying out this operation"); |
| this.port = port; |
| dbgTag = makeDebugTag(); |
| } |
| } |
| |
| /** |
| * Gets the protocol being used by this <CODE>CommunicatorServer</CODE>. |
| * @return The protocol as a string. |
| */ |
| public abstract String getProtocol() ; |
| |
| /** |
| * Gets the number of clients that have been processed by this |
| * <CODE>CommunicatorServer</CODE> since its creation. |
| * |
| * @return The number of clients handled by this |
| * <CODE>CommunicatorServer</CODE> |
| * since its creation. This counter is not reset by the |
| * <CODE>stop</CODE> method. |
| */ |
| int getServedClientCount() { |
| return servedClientCount ; |
| } |
| |
| /** |
| * Gets the number of clients currently being processed by this |
| * <CODE>CommunicatorServer</CODE>. |
| * |
| * @return The number of clients currently being processed by this |
| * <CODE>CommunicatorServer</CODE>. |
| */ |
| int getActiveClientCount() { |
| int result = clientHandlerVector.size() ; |
| return result ; |
| } |
| |
| /** |
| * Gets the maximum number of clients that this |
| * <CODE>CommunicatorServer</CODE> can process concurrently. |
| * |
| * @return The maximum number of clients that this |
| * <CODE>CommunicatorServer</CODE> can |
| * process concurrently. |
| */ |
| int getMaxActiveClientCount() { |
| return maxActiveClientCount ; |
| } |
| |
| /** |
| * Sets the maximum number of clients this |
| * <CODE>CommunicatorServer</CODE> can process concurrently. |
| * |
| * @param c The number of clients. |
| * |
| * @exception java.lang.IllegalStateException This method has been invoked |
| * while the communicator was ONLINE or STARTING. |
| */ |
| void setMaxActiveClientCount(int c) |
| throws java.lang.IllegalStateException { |
| synchronized (stateLock) { |
| if ((state == ONLINE) || (state == STARTING)) { |
| throw new IllegalStateException( |
| "Stop server before carrying out this operation"); |
| } |
| maxActiveClientCount = c ; |
| } |
| } |
| |
| /** |
| * For SNMP Runtime internal use only. |
| */ |
| void notifyClientHandlerCreated(ClientHandler h) { |
| clientHandlerVector.addElement(h) ; |
| } |
| |
| /** |
| * For SNMP Runtime internal use only. |
| */ |
| synchronized void notifyClientHandlerDeleted(ClientHandler h) { |
| clientHandlerVector.removeElement(h); |
| notifyAll(); |
| } |
| |
| /** |
| * The number of times the communicator server will attempt |
| * to bind before giving up. |
| **/ |
| protected int getBindTries() { |
| return 50; |
| } |
| |
| /** |
| * The delay, in ms, during which the communicator server will sleep before |
| * attempting to bind again. |
| **/ |
| protected long getBindSleepTime() { |
| return 100; |
| } |
| |
| /** |
| * For SNMP Runtime internal use only. |
| * <p> |
| * The <CODE>run</CODE> method executed by this connector's main thread. |
| */ |
| public void run() { |
| |
| // Fix jaw.00667.B |
| // It seems that the init of "i" and "success" |
| // need to be done outside the "try" clause... |
| // A bug in Java 2 production release ? |
| // |
| int i = 0; |
| boolean success = false; |
| |
| // ---------------------- |
| // Bind |
| // ---------------------- |
| try { |
| // Fix for bug 4352451: "java.net.BindException: Address in use". |
| // |
| final int bindRetries = getBindTries(); |
| final long sleepTime = getBindSleepTime(); |
| while (i < bindRetries && !success) { |
| try { |
| // Try socket connection. |
| // |
| doBind(); |
| success = true; |
| } catch (CommunicationException ce) { |
| i++; |
| try { |
| Thread.sleep(sleepTime); |
| } catch (InterruptedException ie) { |
| throw ie; |
| } |
| } |
| } |
| // Retry last time to get correct exception. |
| // |
| if (!success) { |
| // Try socket connection. |
| // |
| doBind(); |
| } |
| |
| } catch(Exception x) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "run", "Got unexpected exception", x); |
| } |
| synchronized(stateLock) { |
| startException = x; |
| changeState(OFFLINE); |
| } |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "run","State is OFFLINE"); |
| } |
| doError(x); |
| return; |
| } |
| |
| try { |
| // ---------------------- |
| // State change |
| // ---------------------- |
| changeState(ONLINE) ; |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "run","State is ONLINE"); |
| } |
| |
| // ---------------------- |
| // Main loop |
| // ---------------------- |
| while (!stopRequested) { |
| servedClientCount++; |
| doReceive() ; |
| waitIfTooManyClients() ; |
| doProcess() ; |
| } |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "run","Stop has been requested"); |
| } |
| |
| } catch(InterruptedException x) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "run","Interrupt caught"); |
| } |
| changeState(STOPPING); |
| } catch(Exception x) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "run","Got unexpected exception", x); |
| } |
| changeState(STOPPING); |
| } finally { |
| synchronized (stateLock) { |
| interrupted = true; |
| Thread.currentThread().interrupted(); |
| } |
| |
| // ---------------------- |
| // unBind |
| // ---------------------- |
| try { |
| doUnbind() ; |
| waitClientTermination() ; |
| changeState(OFFLINE); |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "run","State is OFFLINE"); |
| } |
| } catch(Exception x) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "run","Got unexpected exception", x); |
| } |
| changeState(OFFLINE); |
| } |
| |
| } |
| } |
| |
| /** |
| */ |
| protected abstract void doError(Exception e) throws CommunicationException; |
| |
| // |
| // To be defined by the subclass. |
| // |
| // Each method below is called by run() and must be subclassed. |
| // If the method sends an exception (Communication or Interrupt), this |
| // will end up the run() method and switch the connector offline. |
| // |
| // If it is a CommunicationException, run() will call |
| // Debug.printException(). |
| // |
| // All these methods should propagate the InterruptedException to inform |
| // run() that the connector must be switch OFFLINE. |
| // |
| // |
| // |
| // doBind() should do all what is needed before calling doReceive(). |
| // If doBind() throws an exception, doUnbind() is not to be called |
| // and run() ends up. |
| // |
| |
| /** |
| */ |
| protected abstract void doBind() |
| throws CommunicationException, InterruptedException ; |
| |
| /** |
| * <CODE>doReceive()</CODE> should block until a client is available. |
| * If this method throws an exception, <CODE>doProcess()</CODE> is not |
| * called but <CODE>doUnbind()</CODE> is called then <CODE>run()</CODE> |
| * stops. |
| */ |
| protected abstract void doReceive() |
| throws CommunicationException, InterruptedException ; |
| |
| /** |
| * <CODE>doProcess()</CODE> is called after <CODE>doReceive()</CODE>: |
| * it should process the requests of the incoming client. |
| * If it throws an exception, <CODE>doUnbind()</CODE> is called and |
| * <CODE>run()</CODE> stops. |
| */ |
| protected abstract void doProcess() |
| throws CommunicationException, InterruptedException ; |
| |
| /** |
| * <CODE>doUnbind()</CODE> is called whenever the connector goes |
| * <CODE>OFFLINE</CODE>, except if <CODE>doBind()</CODE> has thrown an |
| * exception. |
| */ |
| protected abstract void doUnbind() |
| throws CommunicationException, InterruptedException ; |
| |
| /** |
| * Get the <code>MBeanServer</code> object to which incoming requests are |
| * sent. This is either the MBean server in which this connector is |
| * registered, or an <code>MBeanServerForwarder</code> leading to that |
| * server. |
| */ |
| public synchronized MBeanServer getMBeanServer() { |
| return topMBS; |
| } |
| |
| /** |
| * Set the <code>MBeanServer</code> object to which incoming |
| * requests are sent. This must be either the MBean server in |
| * which this connector is registered, or an |
| * <code>MBeanServerForwarder</code> leading to that server. An |
| * <code>MBeanServerForwarder</code> <code>mbsf</code> leads to an |
| * MBean server <code>mbs</code> if |
| * <code>mbsf.getMBeanServer()</code> is either <code>mbs</code> |
| * or an <code>MBeanServerForwarder</code> leading to |
| * <code>mbs</code>. |
| * |
| * @exception IllegalArgumentException if <code>newMBS</code> is neither |
| * the MBean server in which this connector is registered nor an |
| * <code>MBeanServerForwarder</code> leading to that server. |
| * |
| * @exception IllegalStateException This method has been invoked |
| * while the communicator was ONLINE or STARTING. |
| */ |
| public synchronized void setMBeanServer(MBeanServer newMBS) |
| throws IllegalArgumentException, IllegalStateException { |
| synchronized (stateLock) { |
| if (state == ONLINE || state == STARTING) |
| throw new IllegalStateException("Stop server before " + |
| "carrying out this operation"); |
| } |
| final String error = |
| "MBeanServer argument must be MBean server where this " + |
| "server is registered, or an MBeanServerForwarder " + |
| "leading to that server"; |
| Vector<MBeanServer> seenMBS = new Vector<MBeanServer>(); |
| for (MBeanServer mbs = newMBS; |
| mbs != bottomMBS; |
| mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) { |
| if (!(mbs instanceof MBeanServerForwarder)) |
| throw new IllegalArgumentException(error); |
| if (seenMBS.contains(mbs)) |
| throw new IllegalArgumentException("MBeanServerForwarder " + |
| "loop"); |
| seenMBS.addElement(mbs); |
| } |
| topMBS = newMBS; |
| } |
| |
| // |
| // To be called by the subclass if needed |
| // |
| /** |
| * For internal use only. |
| */ |
| ObjectName getObjectName() { |
| return objectName ; |
| } |
| |
| /** |
| * For internal use only. |
| */ |
| void changeState(int newState) { |
| int oldState; |
| synchronized (stateLock) { |
| if (state == newState) |
| return; |
| oldState = state; |
| state = newState; |
| stateLock.notifyAll(); |
| } |
| sendStateChangeNotification(oldState, newState); |
| } |
| |
| /** |
| * Returns the string used in debug traces. |
| */ |
| String makeDebugTag() { |
| return "CommunicatorServer["+ getProtocol() + ":" + getPort() + "]" ; |
| } |
| |
| /** |
| * Returns the string used to name the connector thread. |
| */ |
| String makeThreadName() { |
| String result ; |
| |
| if (objectName == null) |
| result = "CommunicatorServer" ; |
| else |
| result = objectName.toString() ; |
| |
| return result ; |
| } |
| |
| /** |
| * This method blocks if there are too many active clients. |
| * Call to <CODE>wait()</CODE> is terminated when a client handler |
| * thread calls <CODE>notifyClientHandlerDeleted(this)</CODE> ; |
| */ |
| private synchronized void waitIfTooManyClients() |
| throws InterruptedException { |
| while (getActiveClientCount() >= maxActiveClientCount) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitIfTooManyClients","Waiting for a client to terminate"); |
| } |
| wait(); |
| } |
| } |
| |
| /** |
| * This method blocks until there is no more active client. |
| */ |
| private void waitClientTermination() { |
| int s = clientHandlerVector.size() ; |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| if (s >= 1) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitClientTermination","waiting for " + |
| s + " clients to terminate"); |
| } |
| } |
| |
| // The ClientHandler will remove themselves from the |
| // clientHandlerVector at the end of their run() method, by |
| // calling notifyClientHandlerDeleted(). |
| // Since the clientHandlerVector is modified by the ClientHandler |
| // threads we must avoid using Enumeration or Iterator to loop |
| // over this array. We must also take care of NoSuchElementException |
| // which could be thrown if the last ClientHandler removes itself |
| // between the call to clientHandlerVector.isEmpty() and the call |
| // to clientHandlerVector.firstElement(). |
| // What we *MUST NOT DO* is locking the clientHandlerVector, because |
| // this would most probably cause a deadlock. |
| // |
| while (! clientHandlerVector.isEmpty()) { |
| try { |
| clientHandlerVector.firstElement().join(); |
| } catch (NoSuchElementException x) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitClientTermination","No elements left", x); |
| } |
| } |
| } |
| |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| if (s >= 1) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "waitClientTermination","Ok, let's go..."); |
| } |
| } |
| } |
| |
| /** |
| * Call <CODE>interrupt()</CODE> on each pending client. |
| */ |
| private void terminateAllClient() { |
| final int s = clientHandlerVector.size() ; |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| if (s >= 1) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "terminateAllClient","Interrupting " + s + " clients"); |
| } |
| } |
| |
| // The ClientHandler will remove themselves from the |
| // clientHandlerVector at the end of their run() method, by |
| // calling notifyClientHandlerDeleted(). |
| // Since the clientHandlerVector is modified by the ClientHandler |
| // threads we must avoid using Enumeration or Iterator to loop |
| // over this array. |
| // We cannot use the same logic here than in waitClientTermination() |
| // because there is no guarantee that calling interrupt() on the |
| // ClientHandler will actually terminate the ClientHandler. |
| // Since we do not want to wait for the actual ClientHandler |
| // termination, we cannot simply loop over the array until it is |
| // empty (this might result in calling interrupt() endlessly on |
| // the same client handler. So what we do is simply take a snapshot |
| // copy of the vector and loop over the copy. |
| // What we *MUST NOT DO* is locking the clientHandlerVector, because |
| // this would most probably cause a deadlock. |
| // |
| final ClientHandler[] handlers = |
| clientHandlerVector.toArray(new ClientHandler[0]); |
| for (ClientHandler h : handlers) { |
| try { |
| h.interrupt() ; |
| } catch (Exception x) { |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag, |
| "terminateAllClient", |
| "Failed to interrupt pending request. " + |
| "Ignore the exception.", x); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Controls the way the CommunicatorServer service is deserialized. |
| */ |
| private void readObject(ObjectInputStream stream) |
| throws IOException, ClassNotFoundException { |
| |
| // Call the default deserialization of the object. |
| // |
| stream.defaultReadObject(); |
| |
| // Call the specific initialization for the CommunicatorServer service. |
| // This is for transient structures to be initialized to specific |
| // default values. |
| // |
| stateLock = new Object(); |
| state = OFFLINE; |
| stopRequested = false; |
| servedClientCount = 0; |
| clientHandlerVector = new Vector<ClientHandler>(); |
| fatherThread = Thread.currentThread(); |
| mainThread = null; |
| notifCount = 0; |
| notifInfos = null; |
| notifBroadcaster = new NotificationBroadcasterSupport(); |
| dbgTag = makeDebugTag(); |
| } |
| |
| |
| // |
| // NotificationBroadcaster |
| // |
| |
| /** |
| * Adds a listener for the notifications emitted by this |
| * CommunicatorServer. |
| * There is only one type of notifications sent by the CommunicatorServer: |
| * they are <tt>{@link javax.management.AttributeChangeNotification}</tt>, |
| * sent when the <tt>State</tt> attribute of this CommunicatorServer |
| * changes. |
| * |
| * @param listener The listener object which will handle the emitted |
| * notifications. |
| * @param filter The filter object. If filter is null, no filtering |
| * will be performed before handling notifications. |
| * @param handback An object which will be sent back unchanged to the |
| * listener when a notification is emitted. |
| * |
| * @exception IllegalArgumentException Listener parameter is null. |
| */ |
| public void addNotificationListener(NotificationListener listener, |
| NotificationFilter filter, |
| Object handback) |
| throws java.lang.IllegalArgumentException { |
| |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "addNotificationListener","Adding listener "+ listener + |
| " with filter "+ filter + " and handback "+ handback); |
| } |
| notifBroadcaster.addNotificationListener(listener, filter, handback); |
| } |
| |
| /** |
| * Removes the specified listener from this CommunicatorServer. |
| * Note that if the listener has been registered with different |
| * handback objects or notification filters, all entries corresponding |
| * to the listener will be removed. |
| * |
| * @param listener The listener object to be removed. |
| * |
| * @exception ListenerNotFoundException The listener is not registered. |
| */ |
| public void removeNotificationListener(NotificationListener listener) |
| throws ListenerNotFoundException { |
| |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "removeNotificationListener","Removing listener "+ listener); |
| } |
| notifBroadcaster.removeNotificationListener(listener); |
| } |
| |
| /** |
| * Returns an array of MBeanNotificationInfo objects describing |
| * the notification types sent by this CommunicatorServer. |
| * There is only one type of notifications sent by the CommunicatorServer: |
| * it is <tt>{@link javax.management.AttributeChangeNotification}</tt>, |
| * sent when the <tt>State</tt> attribute of this CommunicatorServer |
| * changes. |
| */ |
| public MBeanNotificationInfo[] getNotificationInfo() { |
| |
| // Initialize notifInfos on first call to getNotificationInfo() |
| // |
| if (notifInfos == null) { |
| notifInfos = new MBeanNotificationInfo[1]; |
| String[] notifTypes = { |
| AttributeChangeNotification.ATTRIBUTE_CHANGE}; |
| notifInfos[0] = new MBeanNotificationInfo( notifTypes, |
| AttributeChangeNotification.class.getName(), |
| "Sent to notify that the value of the State attribute "+ |
| "of this CommunicatorServer instance has changed."); |
| } |
| |
| return notifInfos; |
| } |
| |
| /** |
| * |
| */ |
| private void sendStateChangeNotification(int oldState, int newState) { |
| |
| String oldStateString = getStringForState(oldState); |
| String newStateString = getStringForState(newState); |
| String message = new StringBuffer().append(dbgTag) |
| .append(" The value of attribute State has changed from ") |
| .append(oldState).append(" (").append(oldStateString) |
| .append(") to ").append(newState).append(" (") |
| .append(newStateString).append(").").toString(); |
| |
| notifCount++; |
| AttributeChangeNotification notif = |
| new AttributeChangeNotification(this, // source |
| notifCount, // sequence number |
| System.currentTimeMillis(), // time stamp |
| message, // message |
| "State", // attribute name |
| "int", // attribute type |
| new Integer(oldState), // old value |
| new Integer(newState) ); // new value |
| if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) { |
| SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag, |
| "sendStateChangeNotification","Sending AttributeChangeNotification #" |
| + notifCount + " with message: "+ message); |
| } |
| notifBroadcaster.sendNotification(notif); |
| } |
| |
| /** |
| * |
| */ |
| private static String getStringForState(int s) { |
| switch (s) { |
| case ONLINE: return "ONLINE"; |
| case STARTING: return "STARTING"; |
| case OFFLINE: return "OFFLINE"; |
| case STOPPING: return "STOPPING"; |
| default: return "UNDEFINED"; |
| } |
| } |
| |
| |
| // |
| // MBeanRegistration |
| // |
| |
| /** |
| * Preregister method of connector. |
| * |
| *@param server The <CODE>MBeanServer</CODE> in which the MBean will |
| * be registered. |
| *@param name The object name of the MBean. |
| * |
| *@return The name of the MBean registered. |
| * |
| *@exception java.langException This exception should be caught by |
| * the <CODE>MBeanServer</CODE> and re-thrown |
| * as an <CODE>MBeanRegistrationException</CODE>. |
| */ |
| public ObjectName preRegister(MBeanServer server, ObjectName name) |
| throws java.lang.Exception { |
| objectName = name; |
| synchronized (this) { |
| if (bottomMBS != null) { |
| throw new IllegalArgumentException("connector already " + |
| "registered in an MBean " + |
| "server"); |
| } |
| topMBS = bottomMBS = server; |
| } |
| dbgTag = makeDebugTag(); |
| return name; |
| } |
| |
| /** |
| * |
| *@param registrationDone Indicates whether or not the MBean has been |
| * successfully registered in the <CODE>MBeanServer</CODE>. |
| * The value false means that the registration phase has failed. |
| */ |
| public void postRegister(Boolean registrationDone) { |
| if (!registrationDone.booleanValue()) { |
| synchronized (this) { |
| topMBS = bottomMBS = null; |
| } |
| } |
| } |
| |
| /** |
| * Stop the connector. |
| * |
| * @exception java.langException This exception should be caught by |
| * the <CODE>MBeanServer</CODE> and re-thrown |
| * as an <CODE>MBeanRegistrationException</CODE>. |
| */ |
| public void preDeregister() throws java.lang.Exception { |
| synchronized (this) { |
| topMBS = bottomMBS = null; |
| } |
| objectName = null ; |
| final int cstate = getState(); |
| if ((cstate == ONLINE) || ( cstate == STARTING)) { |
| stop() ; |
| } |
| } |
| |
| /** |
| * Do nothing. |
| */ |
| public void postDeregister(){ |
| } |
| |
| /** |
| * Load a class using the default loader repository |
| **/ |
| Class loadClass(String className) |
| throws ClassNotFoundException { |
| try { |
| return Class.forName(className); |
| } catch (ClassNotFoundException e) { |
| final ClassLoaderRepository clr = |
| MBeanServerFactory.getClassLoaderRepository(bottomMBS); |
| if (clr == null) throw new ClassNotFoundException(className); |
| return clr.loadClass(className); |
| } |
| } |
| |
| } |