/* * Copyright (c) 2002, 2012, 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 javax.management.remote.rmi; import com.sun.jmx.remote.security.MBeanServerFileAccessController; import com.sun.jmx.remote.internal.IIOPHelper; import com.sun.jmx.remote.util.ClassLogger; import com.sun.jmx.remote.util.EnvHelp; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.net.MalformedURLException; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; import javax.management.InstanceNotFoundException; import javax.management.MBeanServer; import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXServiceURL; import javax.management.remote.MBeanServerForwarder; import javax.naming.InitialContext; import javax.naming.NamingException; /** * <p>A JMX API connector server that creates RMI-based connections * from remote clients. Usually, such connector servers are made * using {@link javax.management.remote.JMXConnectorServerFactory * JMXConnectorServerFactory}. However, specialized applications can * use this class directly, for example with an {@link RMIServerImpl} * object.</p> * * @since 1.5 */ public class RMIConnectorServer extends JMXConnectorServer { /** * <p>Name of the attribute that specifies whether the {@link * RMIServer} stub that represents an RMI connector server should * override an existing stub at the same address. The value * associated with this attribute, if any, should be a string that * is equal, ignoring case, to <code>"true"</code> or * <code>"false"</code>. The default value is false.</p> */ public static final String JNDI_REBIND_ATTRIBUTE = "jmx.remote.jndi.rebind"; /** * <p>Name of the attribute that specifies the {@link * RMIClientSocketFactory} for the RMI objects created in * conjunction with this connector. The value associated with this * attribute must be of type <code>RMIClientSocketFactory</code> and can * only be specified in the <code>Map</code> argument supplied when * creating a connector server.</p> */ public static final String RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE = "jmx.remote.rmi.client.socket.factory"; /** * <p>Name of the attribute that specifies the {@link * RMIServerSocketFactory} for the RMI objects created in * conjunction with this connector. The value associated with this * attribute must be of type <code>RMIServerSocketFactory</code> and can * only be specified in the <code>Map</code> argument supplied when * creating a connector server.</p> */ public static final String RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE = "jmx.remote.rmi.server.socket.factory"; /** * <p>Makes an <code>RMIConnectorServer</code>. * This is equivalent to calling {@link #RMIConnectorServer( * JMXServiceURL,Map,RMIServerImpl,MBeanServer) * RMIConnectorServer(directoryURL,environment,null,null)}</p> * * @param url the URL defining how to create the connector server. * Cannot be null. * * @param environment attributes governing the creation and * storing of the RMI object. Can be null, which is equivalent to * an empty Map. * * @exception IllegalArgumentException if <code>url</code> is null. * * @exception MalformedURLException if <code>url</code> does not * conform to the syntax for an RMI connector, or if its protocol * is not recognized by this implementation. Only "rmi" and "iiop" * are valid when this constructor is used. * * @exception IOException if the connector server cannot be created * for some reason or if it is inevitable that its {@link #start() * start} method will fail. */ public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment) throws IOException { this(url, environment, (MBeanServer) null); } /** * <p>Makes an <code>RMIConnectorServer</code> for the given MBean * server. * This is equivalent to calling {@link #RMIConnectorServer( * JMXServiceURL,Map,RMIServerImpl,MBeanServer) * RMIConnectorServer(directoryURL,environment,null,mbeanServer)}</p> * * @param url the URL defining how to create the connector server. * Cannot be null. * * @param environment attributes governing the creation and * storing of the RMI object. Can be null, which is equivalent to * an empty Map. * * @param mbeanServer the MBean server to which the new connector * server is attached, or null if it will be attached by being * registered as an MBean in the MBean server. * * @exception IllegalArgumentException if <code>url</code> is null. * * @exception MalformedURLException if <code>url</code> does not * conform to the syntax for an RMI connector, or if its protocol * is not recognized by this implementation. Only "rmi" and "iiop" * are valid when this constructor is used. * * @exception IOException if the connector server cannot be created * for some reason or if it is inevitable that its {@link #start() * start} method will fail. */ public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, MBeanServer mbeanServer) throws IOException { this(url, environment, (RMIServerImpl) null, mbeanServer); } /** * <p>Makes an <code>RMIConnectorServer</code> for the given MBean * server.</p> * * @param url the URL defining how to create the connector server. * Cannot be null. * * @param environment attributes governing the creation and * storing of the RMI object. Can be null, which is equivalent to * an empty Map. * * @param rmiServerImpl An implementation of the RMIServer interface, * consistent with the protocol type specified in <var>url</var>. * If this parameter is non null, the protocol type specified by * <var>url</var> is not constrained, and is assumed to be valid. * Otherwise, only "rmi" and "iiop" will be recognized. * * @param mbeanServer the MBean server to which the new connector * server is attached, or null if it will be attached by being * registered as an MBean in the MBean server. * * @exception IllegalArgumentException if <code>url</code> is null. * * @exception MalformedURLException if <code>url</code> does not * conform to the syntax for an RMI connector, or if its protocol * is not recognized by this implementation. Only "rmi" and "iiop" * are recognized when <var>rmiServerImpl</var> is null. * * @exception IOException if the connector server cannot be created * for some reason or if it is inevitable that its {@link #start() * start} method will fail. * * @see #start */ public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment, RMIServerImpl rmiServerImpl, MBeanServer mbeanServer) throws IOException { super(mbeanServer); if (url == null) throw new IllegalArgumentException("Null JMXServiceURL"); if (rmiServerImpl == null) { final String prt = url.getProtocol(); if (prt == null || !(prt.equals("rmi") || prt.equals("iiop"))) { final String msg = "Invalid protocol type: " + prt; throw new MalformedURLException(msg); } final String urlPath = url.getURLPath(); if (!urlPath.equals("") && !urlPath.equals("/") && !urlPath.startsWith("/jndi/")) { final String msg = "URL path must be empty or start with " + "/jndi/"; throw new MalformedURLException(msg); } } if (environment == null) this.attributes = Collections.emptyMap(); else { EnvHelp.checkAttributes(environment); this.attributes = Collections.unmodifiableMap(environment); } this.address = url; this.rmiServerImpl = rmiServerImpl; } /** * <p>Returns a client stub for this connector server. A client * stub is a serializable object whose {@link * JMXConnector#connect(Map) connect} method can be used to make * one new connection to this connector server.</p> * * @param env client connection parameters of the same sort that * could be provided to {@link JMXConnector#connect(Map) * JMXConnector.connect(Map)}. Can be null, which is equivalent * to an empty map. * * @return a client stub that can be used to make a new connection * to this connector server. * * @exception UnsupportedOperationException if this connector * server does not support the generation of client stubs. * * @exception IllegalStateException if the JMXConnectorServer is * not started (see {@link #isActive()}). * * @exception IOException if a communications problem means that a * stub cannot be created. **/ public JMXConnector toJMXConnector(Map<String,?> env) throws IOException { // The serialized for of rmiServerImpl is automatically // a RMI server stub. if (!isActive()) throw new IllegalStateException("Connector is not active"); // Merge maps Map<String, Object> usemap = new HashMap<String, Object>( (this.attributes==null)?Collections.<String, Object>emptyMap(): this.attributes); if (env != null) { EnvHelp.checkAttributes(env); usemap.putAll(env); } usemap = EnvHelp.filterAttributes(usemap); final RMIServer stub=(RMIServer)rmiServerImpl.toStub(); return new RMIConnector(stub, usemap); } /** * <p>Activates the connector server, that is starts listening for * client connections. Calling this method when the connector * server is already active has no effect. Calling this method * when the connector server has been stopped will generate an * <code>IOException</code>.</p> * * <p>The behavior of this method when called for the first time * depends on the parameters that were supplied at construction, * as described below.</p> * * <p>First, an object of a subclass of {@link RMIServerImpl} is * required, to export the connector server through RMI:</p> * * <ul> * * <li>If an <code>RMIServerImpl</code> was supplied to the * constructor, it is used. * * <li>Otherwise, if the protocol part of the * <code>JMXServiceURL</code> supplied to the constructor was * <code>iiop</code>, an object of type {@link RMIIIOPServerImpl} * is created. * * <li>Otherwise, if the <code>JMXServiceURL</code> * was null, or its protocol part was <code>rmi</code>, an object * of type {@link RMIJRMPServerImpl} is created. * * <li>Otherwise, the implementation can create an * implementation-specific {@link RMIServerImpl} or it can throw * {@link MalformedURLException}. * * </ul> * * <p>If the given address includes a JNDI directory URL as * specified in the package documentation for {@link * javax.management.remote.rmi}, then this * <code>RMIConnectorServer</code> will bootstrap by binding the * <code>RMIServerImpl</code> to the given address.</p> * * <p>If the URL path part of the <code>JMXServiceURL</code> was * empty or a single slash (<code>/</code>), then the RMI object * will not be bound to a directory. Instead, a reference to it * will be encoded in the URL path of the RMIConnectorServer * address (returned by {@link #getAddress()}). The encodings for * <code>rmi</code> and <code>iiop</code> are described in the * package documentation for {@link * javax.management.remote.rmi}.</p> * * <p>The behavior when the URL path is neither empty nor a JNDI * directory URL, or when the protocol is neither <code>rmi</code> * nor <code>iiop</code>, is implementation defined, and may * include throwing {@link MalformedURLException} when the * connector server is created or when it is started.</p> * * @exception IllegalStateException if the connector server has * not been attached to an MBean server. * @exception IOException if the connector server cannot be * started, or in the case of the {@code iiop} protocol, that * RMI/IIOP is not supported. */ public synchronized void start() throws IOException { final boolean tracing = logger.traceOn(); if (state == STARTED) { if (tracing) logger.trace("start", "already started"); return; } else if (state == STOPPED) { if (tracing) logger.trace("start", "already stopped"); throw new IOException("The server has been stopped."); } if (getMBeanServer() == null) throw new IllegalStateException("This connector server is not " + "attached to an MBean server"); // Check the internal access file property to see // if an MBeanServerForwarder is to be provided // if (attributes != null) { // Check if access file property is specified // String accessFile = (String) attributes.get("jmx.remote.x.access.file"); if (accessFile != null) { // Access file property specified, create an instance // of the MBeanServerFileAccessController class // MBeanServerForwarder mbsf; try { mbsf = new MBeanServerFileAccessController(accessFile); } catch (IOException e) { throw EnvHelp.initCause( new IllegalArgumentException(e.getMessage()), e); } // Set the MBeanServerForwarder // setMBeanServerForwarder(mbsf); } } try { if (tracing) logger.trace("start", "setting default class loader"); defaultClassLoader = EnvHelp.resolveServerClassLoader( attributes, getMBeanServer()); } catch (InstanceNotFoundException infc) { IllegalArgumentException x = new IllegalArgumentException("ClassLoader not found: "+infc); throw EnvHelp.initCause(x,infc); } if (tracing) logger.trace("start", "setting RMIServer object"); final RMIServerImpl rmiServer; if (rmiServerImpl != null) rmiServer = rmiServerImpl; else rmiServer = newServer(); rmiServer.setMBeanServer(getMBeanServer()); rmiServer.setDefaultClassLoader(defaultClassLoader); rmiServer.setRMIConnectorServer(this); rmiServer.export(); try { if (tracing) logger.trace("start", "getting RMIServer object to export"); final RMIServer objref = objectToBind(rmiServer, attributes); if (address != null && address.getURLPath().startsWith("/jndi/")) { final String jndiUrl = address.getURLPath().substring(6); if (tracing) logger.trace("start", "Using external directory: " + jndiUrl); String stringBoolean = (String) attributes.get(JNDI_REBIND_ATTRIBUTE); final boolean rebind = EnvHelp.computeBooleanFromString( stringBoolean ); if (tracing) logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind); try { if (tracing) logger.trace("start", "binding to " + jndiUrl); final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes); bind(jndiUrl, usemap, objref, rebind); boundJndiUrl = jndiUrl; } catch (NamingException e) { // fit e in the nested exception if we are on 1.4 throw newIOException("Cannot bind to URL ["+jndiUrl+"]: " + e, e); } } else { // if jndiURL is null, we must encode the stub into the URL. if (tracing) logger.trace("start", "Encoding URL"); encodeStubInAddress(objref, attributes); if (tracing) logger.trace("start", "Encoded URL: " + this.address); } } catch (Exception e) { try { rmiServer.close(); } catch (Exception x) { // OK: we are already throwing another exception } if (e instanceof RuntimeException) throw (RuntimeException) e; else if (e instanceof IOException) throw (IOException) e; else throw newIOException("Got unexpected exception while " + "starting the connector server: " + e, e); } rmiServerImpl = rmiServer; synchronized(openedServers) { openedServers.add(this); } state = STARTED; if (tracing) { logger.trace("start", "Connector Server Address = " + address); logger.trace("start", "started."); } } /** * <p>Deactivates the connector server, that is, stops listening for * client connections. Calling this method will also close all * client connections that were made by this server. After this * method returns, whether normally or with an exception, the * connector server will not create any new client * connections.</p> * * <p>Once a connector server has been stopped, it cannot be started * again.</p> * * <p>Calling this method when the connector server has already * been stopped has no effect. Calling this method when the * connector server has not yet been started will disable the * connector server object permanently.</p> * * <p>If closing a client connection produces an exception, that * exception is not thrown from this method. A {@link * JMXConnectionNotification} is emitted from this MBean with the * connection ID of the connection that could not be closed.</p> * * <p>Closing a connector server is a potentially slow operation. * For example, if a client machine with an open connection has * crashed, the close operation might have to wait for a network * protocol timeout. Callers that do not want to block in a close * operation should do it in a separate thread.</p> * * <p>This method calls the method {@link RMIServerImpl#close() * close} on the connector server's <code>RMIServerImpl</code> * object.</p> * * <p>If the <code>RMIServerImpl</code> was bound to a JNDI * directory by the {@link #start() start} method, it is unbound * from the directory by this method.</p> * * @exception IOException if the server cannot be closed cleanly, * or if the <code>RMIServerImpl</code> cannot be unbound from the * directory. When this exception is thrown, the server has * already attempted to close all client connections, if * appropriate; to call {@link RMIServerImpl#close()}; and to * unbind the <code>RMIServerImpl</code> from its directory, if * appropriate. All client connections are closed except possibly * those that generated exceptions when the server attempted to * close them. */ public void stop() throws IOException { final boolean tracing = logger.traceOn(); synchronized (this) { if (state == STOPPED) { if (tracing) logger.trace("stop","already stopped."); return; } else if (state == CREATED) { if (tracing) logger.trace("stop","not started yet."); } if (tracing) logger.trace("stop", "stopping."); state = STOPPED; } synchronized(openedServers) { openedServers.remove(this); } IOException exception = null; // rmiServerImpl can be null if stop() called without start() if (rmiServerImpl != null) { try { if (tracing) logger.trace("stop", "closing RMI server."); rmiServerImpl.close(); } catch (IOException e) { if (tracing) logger.trace("stop", "failed to close RMI server: " + e); if (logger.debugOn()) logger.debug("stop",e); exception = e; } } if (boundJndiUrl != null) { try { if (tracing) logger.trace("stop", "unbind from external directory: " + boundJndiUrl); final Hashtable<?, ?> usemap = EnvHelp.mapToHashtable(attributes); InitialContext ctx = new InitialContext(usemap); ctx.unbind(boundJndiUrl); ctx.close(); } catch (NamingException e) { if (tracing) logger.trace("stop", "failed to unbind RMI server: "+e); if (logger.debugOn()) logger.debug("stop",e); // fit e in as the nested exception if we are on 1.4 if (exception == null) exception = newIOException("Cannot bind to URL: " + e, e); } } if (exception != null) throw exception; if (tracing) logger.trace("stop", "stopped"); } public synchronized boolean isActive() { return (state == STARTED); } public JMXServiceURL getAddress() { if (!isActive()) return null; return address; } public Map<String,?> getAttributes() { Map<String, ?> map = EnvHelp.filterAttributes(attributes); return Collections.unmodifiableMap(map); } @Override public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) { super.setMBeanServerForwarder(mbsf); if (rmiServerImpl != null) rmiServerImpl.setMBeanServer(getMBeanServer()); } /* We repeat the definitions of connection{Opened,Closed,Failed} here so that they are accessible to other classes in this package even though they have protected access. */ @Override protected void connectionOpened(String connectionId, String message, Object userData) { super.connectionOpened(connectionId, message, userData); } @Override protected void connectionClosed(String connectionId, String message, Object userData) { super.connectionClosed(connectionId, message, userData); } @Override protected void connectionFailed(String connectionId, String message, Object userData) { super.connectionFailed(connectionId, message, userData); } /** * Bind a stub to a registry. * @param jndiUrl URL of the stub in the registry, extracted * from the <code>JMXServiceURL</code>. * @param attributes A Hashtable containing environment parameters, * built from the Map specified at this object creation. * @param rmiServer The object to bind in the registry * @param rebind true if the object must be rebound. **/ void bind(String jndiUrl, Hashtable<?, ?> attributes, RMIServer rmiServer, boolean rebind) throws NamingException, MalformedURLException { // if jndiURL is not null, we nust bind the stub to a // directory. InitialContext ctx = new InitialContext(attributes); if (rebind) ctx.rebind(jndiUrl, rmiServer); else ctx.bind(jndiUrl, rmiServer); ctx.close(); } /** * Creates a new RMIServerImpl. **/ RMIServerImpl newServer() throws IOException { final boolean iiop = isIiopURL(address,true); final int port; if (address == null) port = 0; else port = address.getPort(); if (iiop) return newIIOPServer(attributes); else return newJRMPServer(attributes, port); } /** * Encode a stub into the JMXServiceURL. * @param rmiServer The stub object to encode in the URL * @param attributes A Map containing environment parameters, * built from the Map specified at this object creation. **/ private void encodeStubInAddress( RMIServer rmiServer, Map<String, ?> attributes) throws IOException { final String protocol, host; final int port; if (address == null) { if (IIOPHelper.isStub(rmiServer)) protocol = "iiop"; else protocol = "rmi"; host = null; // will default to local host name port = 0; } else { protocol = address.getProtocol(); host = (address.getHost().equals("")) ? null : address.getHost(); port = address.getPort(); } final String urlPath = encodeStub(rmiServer, attributes); address = new JMXServiceURL(protocol, host, port, urlPath); } static boolean isIiopURL(JMXServiceURL directoryURL, boolean strict) throws MalformedURLException { String protocol = directoryURL.getProtocol(); if (protocol.equals("rmi")) return false; else if (protocol.equals("iiop")) return true; else if (strict) { throw new MalformedURLException("URL must have protocol " + "\"rmi\" or \"iiop\": \"" + protocol + "\""); } return false; } /** * Returns the IOR of the given rmiServer. **/ static String encodeStub( RMIServer rmiServer, Map<String, ?> env) throws IOException { if (IIOPHelper.isStub(rmiServer)) return "/ior/" + encodeIIOPStub(rmiServer, env); else return "/stub/" + encodeJRMPStub(rmiServer, env); } static String encodeJRMPStub( RMIServer rmiServer, Map<String, ?> env) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oout = new ObjectOutputStream(bout); oout.writeObject(rmiServer); oout.close(); byte[] bytes = bout.toByteArray(); return byteArrayToBase64(bytes); } static String encodeIIOPStub( RMIServer rmiServer, Map<String, ?> env) throws IOException { try { Object orb = IIOPHelper.getOrb(rmiServer); return IIOPHelper.objectToString(orb, rmiServer); } catch (RuntimeException x) { throw newIOException(x.getMessage(), x); } } /** * Object that we will bind to the registry. * This object is a stub connected to our RMIServerImpl. **/ private static RMIServer objectToBind( RMIServerImpl rmiServer, Map<String, ?> env) throws IOException { return RMIConnector. connectStub((RMIServer)rmiServer.toStub(),env); } private static RMIServerImpl newJRMPServer(Map<String, ?> env, int port) throws IOException { RMIClientSocketFactory csf = (RMIClientSocketFactory) env.get(RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE); RMIServerSocketFactory ssf = (RMIServerSocketFactory) env.get(RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE); return new RMIJRMPServerImpl(port, csf, ssf, env); } private static RMIServerImpl newIIOPServer(Map<String, ?> env) throws IOException { return new RMIIIOPServerImpl(env); } private static String byteArrayToBase64(byte[] a) { int aLen = a.length; int numFullGroups = aLen/3; int numBytesInPartialGroup = aLen - 3*numFullGroups; int resultLen = 4*((aLen + 2)/3); final StringBuilder result = new StringBuilder(resultLen); // Translate all full groups from byte array elements to Base64 int inCursor = 0; for (int i=0; i<numFullGroups; i++) { int byte0 = a[inCursor++] & 0xff; int byte1 = a[inCursor++] & 0xff; int byte2 = a[inCursor++] & 0xff; result.append(intToAlpha[byte0 >> 2]); result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]); result.append(intToAlpha[byte2 & 0x3f]); } // Translate partial group if present if (numBytesInPartialGroup != 0) { int byte0 = a[inCursor++] & 0xff; result.append(intToAlpha[byte0 >> 2]); if (numBytesInPartialGroup == 1) { result.append(intToAlpha[(byte0 << 4) & 0x3f]); result.append("=="); } else { // assert numBytesInPartialGroup == 2; int byte1 = a[inCursor++] & 0xff; result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]); result.append(intToAlpha[(byte1 << 2)&0x3f]); result.append('='); } } // assert inCursor == a.length; // assert result.length() == resultLen; return result.toString(); } /** * This array is a lookup table that translates 6-bit positive integer * index values into their "Base64 Alphabet" equivalents as specified * in Table 1 of RFC 2045. */ private static final char intToAlpha[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; /** * Construct a new IOException with a nested exception. * The nested exception is set only if JDK >= 1.4 */ private static IOException newIOException(String message, Throwable cause) { final IOException x = new IOException(message); return EnvHelp.initCause(x,cause); } // Private variables // ----------------- private static ClassLogger logger = new ClassLogger("javax.management.remote.rmi", "RMIConnectorServer"); private JMXServiceURL address; private RMIServerImpl rmiServerImpl; private final Map<String, ?> attributes; private ClassLoader defaultClassLoader = null; private String boundJndiUrl; // state private static final int CREATED = 0; private static final int STARTED = 1; private static final int STOPPED = 2; private int state = CREATED; private final static Set<RMIConnectorServer> openedServers = new HashSet<RMIConnectorServer>(); }