/* * Copyright (c) 2002, 2015, 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.internal.ArrayNotificationBuffer; import com.sun.jmx.remote.internal.NotificationBuffer; import com.sun.jmx.remote.security.JMXPluggableAuthenticator; import com.sun.jmx.remote.util.ClassLogger; import java.io.Closeable; import java.io.IOException; import java.lang.ref.WeakReference; import java.rmi.Remote; import java.rmi.server.RemoteServer; import java.rmi.server.ServerNotActiveException; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.MBeanServer; import javax.management.remote.JMXAuthenticator; import javax.management.remote.JMXConnectorServer; import javax.security.auth.Subject; /** *

An RMI object representing a connector server. Remote clients * can make connections using the {@link #newClient(Object)} method. This * method returns an RMI object representing the connection.

* *

User code does not usually reference this class directly. * RMI connection servers are usually created with the class {@link * RMIConnectorServer}. Remote clients usually create connections * either with {@link javax.management.remote.JMXConnectorFactory} * or by instantiating {@link RMIConnector}.

* *

This is an abstract class. Concrete subclasses define the * details of the client connection objects.

* * @since 1.5 */ public abstract class RMIServerImpl implements Closeable, RMIServer { /** *

Constructs a new RMIServerImpl.

* * @param env the environment containing attributes for the new * RMIServerImpl. Can be null, which is equivalent * to an empty Map. */ public RMIServerImpl(Map env) { this.env = (env == null) ? Collections.emptyMap() : env; } void setRMIConnectorServer(RMIConnectorServer connServer) throws IOException { this.connServer = connServer; } /** *

Exports this RMI object.

* * @exception IOException if this RMI object cannot be exported. */ protected abstract void export() throws IOException; /** * Returns a remotable stub for this server object. * @return a remotable stub. * @exception IOException if the stub cannot be obtained - e.g the * RMIServerImpl has not been exported yet. **/ public abstract Remote toStub() throws IOException; /** *

Sets the default ClassLoader for this connector * server. New client connections will use this classloader. * Existing client connections are unaffected.

* * @param cl the new ClassLoader to be used by this * connector server. * * @see #getDefaultClassLoader */ public synchronized void setDefaultClassLoader(ClassLoader cl) { this.cl = cl; } /** *

Gets the default ClassLoader used by this connector * server.

* * @return the default ClassLoader used by this * connector server. * * @see #setDefaultClassLoader */ public synchronized ClassLoader getDefaultClassLoader() { return cl; } /** *

Sets the MBeanServer to which this connector * server is attached. New client connections will interact * with this MBeanServer. Existing client connections are * unaffected.

* * @param mbs the new MBeanServer. Can be null, but * new client connections will be refused as long as it is. * * @see #getMBeanServer */ public synchronized void setMBeanServer(MBeanServer mbs) { this.mbeanServer = mbs; } /** *

The MBeanServer to which this connector server * is attached. This is the last value passed to {@link * #setMBeanServer} on this object, or null if that method has * never been called.

* * @return the MBeanServer to which this connector * is attached. * * @see #setMBeanServer */ public synchronized MBeanServer getMBeanServer() { return mbeanServer; } public String getVersion() { // Expected format is: "protocol-version implementation-name" try { return "1.0 java_runtime_" + System.getProperty("java.runtime.version"); } catch (SecurityException e) { return "1.0 "; } } /** *

Creates a new client connection. This method calls {@link * #makeClient makeClient} and adds the returned client connection * object to an internal list. When this * RMIServerImpl is shut down via its {@link * #close()} method, the {@link RMIConnection#close() close()} * method of each object remaining in the list is called.

* *

The fact that a client connection object is in this internal * list does not prevent it from being garbage collected.

* * @param credentials this object specifies the user-defined * credentials to be passed in to the server in order to * authenticate the caller before creating the * RMIConnection. Can be null. * * @return the newly-created RMIConnection. This is * usually the object created by makeClient, though * an implementation may choose to wrap that object in another * object implementing RMIConnection. * * @exception IOException if the new client object cannot be * created or exported. * * @exception SecurityException if the given credentials do not allow * the server to authenticate the user successfully. * * @exception IllegalStateException if {@link #getMBeanServer()} * is null. */ public RMIConnection newClient(Object credentials) throws IOException { return doNewClient(credentials); } /** * This method could be overridden by subclasses defined in this package * to perform additional operations specific to the underlying transport * before creating the new client connection. */ RMIConnection doNewClient(Object credentials) throws IOException { final boolean tracing = logger.traceOn(); if (tracing) logger.trace("newClient","making new client"); if (getMBeanServer() == null) throw new IllegalStateException("Not attached to an MBean server"); Subject subject = null; JMXAuthenticator authenticator = (JMXAuthenticator) env.get(JMXConnectorServer.AUTHENTICATOR); if (authenticator == null) { /* * Create the JAAS-based authenticator only if authentication * has been enabled */ if (env.get("jmx.remote.x.password.file") != null || env.get("jmx.remote.x.login.config") != null) { authenticator = new JMXPluggableAuthenticator(env); } } if (authenticator != null) { if (tracing) logger.trace("newClient","got authenticator: " + authenticator.getClass().getName()); try { subject = authenticator.authenticate(credentials); } catch (SecurityException e) { logger.trace("newClient", "Authentication failed: " + e); throw e; } } if (tracing) { if (subject != null) logger.trace("newClient","subject is not null"); else logger.trace("newClient","no subject"); } final String connectionId = makeConnectionId(getProtocol(), subject); if (tracing) logger.trace("newClient","making new connection: " + connectionId); RMIConnection client = makeClient(connectionId, subject); dropDeadReferences(); WeakReference wr = new WeakReference(client); synchronized (clientList) { clientList.add(wr); } connServer.connectionOpened(connectionId, "Connection opened", null); synchronized (clientList) { if (!clientList.contains(wr)) { // can be removed only by a JMXConnectionNotification listener throw new IOException("The connection is refused."); } } if (tracing) logger.trace("newClient","new connection done: " + connectionId ); return client; } /** *

Creates a new client connection. This method is called by * the public method {@link #newClient(Object)}.

* * @param connectionId the ID of the new connection. Every * connection opened by this connector server will have a * different ID. The behavior is unspecified if this parameter is * null. * * @param subject the authenticated subject. Can be null. * * @return the newly-created RMIConnection. * * @exception IOException if the new client object cannot be * created or exported. */ protected abstract RMIConnection makeClient(String connectionId, Subject subject) throws IOException; /** *

Closes a client connection made by {@link #makeClient makeClient}. * * @param client a connection previously returned by * makeClient on which the closeClient * method has not previously been called. The behavior is * unspecified if these conditions are violated, including the * case where client is null. * * @exception IOException if the client connection cannot be * closed. */ protected abstract void closeClient(RMIConnection client) throws IOException; /** *

Returns the protocol string for this object. The string is * rmi for RMI/JRMP. * * @return the protocol string for this object. */ protected abstract String getProtocol(); /** *

Method called when a client connection created by {@link * #makeClient makeClient} is closed. A subclass that defines * makeClient must arrange for this method to be * called when the resultant object's {@link RMIConnection#close() * close} method is called. This enables it to be removed from * the RMIServerImpl's list of connections. It is * not an error for client not to be in that * list.

* *

After removing client from the list of * connections, this method calls {@link #closeClient * closeClient(client)}.

* * @param client the client connection that has been closed. * * @exception IOException if {@link #closeClient} throws this * exception. * * @exception NullPointerException if client is null. */ protected void clientClosed(RMIConnection client) throws IOException { final boolean debug = logger.debugOn(); if (debug) logger.trace("clientClosed","client="+client); if (client == null) throw new NullPointerException("Null client"); synchronized (clientList) { dropDeadReferences(); for (Iterator> it = clientList.iterator(); it.hasNext(); ) { WeakReference wr = it.next(); if (wr.get() == client) { it.remove(); break; } } /* It is not a bug for this loop not to find the client. In our close() method, we remove a client from the list before calling its close() method. */ } if (debug) logger.trace("clientClosed", "closing client."); closeClient(client); if (debug) logger.trace("clientClosed", "sending notif"); connServer.connectionClosed(client.getConnectionId(), "Client connection closed", null); if (debug) logger.trace("clientClosed","done"); } /** *

Closes this connection server. This method first calls the * {@link #closeServer()} method so that no new client connections * will be accepted. Then, for each remaining {@link * RMIConnection} object returned by {@link #makeClient * makeClient}, its {@link RMIConnection#close() close} method is * called.

* *

The behavior when this method is called more than once is * unspecified.

* *

If {@link #closeServer()} throws an * IOException, the individual connections are * nevertheless closed, and then the IOException is * thrown from this method.

* *

If {@link #closeServer()} returns normally but one or more * of the individual connections throws an * IOException, then, after closing all the * connections, one of those IOExceptions is thrown * from this method. If more than one connection throws an * IOException, it is unspecified which one is thrown * from this method.

* * @exception IOException if {@link #closeServer()} or one of the * {@link RMIConnection#close()} calls threw * IOException. */ public synchronized void close() throws IOException { final boolean tracing = logger.traceOn(); final boolean debug = logger.debugOn(); if (tracing) logger.trace("close","closing"); IOException ioException = null; try { if (debug) logger.debug("close","closing Server"); closeServer(); } catch (IOException e) { if (tracing) logger.trace("close","Failed to close server: " + e); if (debug) logger.debug("close",e); ioException = e; } if (debug) logger.debug("close","closing Clients"); // Loop to close all clients while (true) { synchronized (clientList) { if (debug) logger.debug("close","droping dead references"); dropDeadReferences(); if (debug) logger.debug("close","client count: "+clientList.size()); if (clientList.size() == 0) break; /* Loop until we find a non-null client. Because we called dropDeadReferences(), this will usually be the first element of the list, but a garbage collection could have happened in between. */ for (Iterator> it = clientList.iterator(); it.hasNext(); ) { WeakReference wr = it.next(); RMIConnection client = wr.get(); it.remove(); if (client != null) { try { client.close(); } catch (IOException e) { if (tracing) logger.trace("close","Failed to close client: " + e); if (debug) logger.debug("close",e); if (ioException == null) ioException = e; } break; } } } } if(notifBuffer != null) notifBuffer.dispose(); if (ioException != null) { if (tracing) logger.trace("close","close failed."); throw ioException; } if (tracing) logger.trace("close","closed."); } /** *

Called by {@link #close()} to close the connector server. * After returning from this method, the connector server must * not accept any new connections.

* * @exception IOException if the attempt to close the connector * server failed. */ protected abstract void closeServer() throws IOException; private static synchronized String makeConnectionId(String protocol, Subject subject) { connectionIdNumber++; String clientHost = ""; try { clientHost = RemoteServer.getClientHost(); /* * According to the rules specified in the javax.management.remote * package description, a numeric IPv6 address (detected by the * presence of otherwise forbidden ":" character) forming a part * of the connection id must be enclosed in square brackets. */ if (clientHost.contains(":")) { clientHost = "[" + clientHost + "]"; } } catch (ServerNotActiveException e) { logger.trace("makeConnectionId", "getClientHost", e); } final StringBuilder buf = new StringBuilder(); buf.append(protocol).append(":"); if (clientHost.length() > 0) buf.append("//").append(clientHost); buf.append(" "); if (subject != null) { Set principals = subject.getPrincipals(); String sep = ""; for (Iterator it = principals.iterator(); it.hasNext(); ) { Principal p = it.next(); String name = p.getName().replace(' ', '_').replace(';', ':'); buf.append(sep).append(name); sep = ";"; } } buf.append(" ").append(connectionIdNumber); if (logger.traceOn()) logger.trace("newConnectionId","connectionId="+buf); return buf.toString(); } private void dropDeadReferences() { synchronized (clientList) { for (Iterator> it = clientList.iterator(); it.hasNext(); ) { WeakReference wr = it.next(); if (wr.get() == null) it.remove(); } } } synchronized NotificationBuffer getNotifBuffer() { //Notification buffer is lazily created when the first client connects if(notifBuffer == null) notifBuffer = ArrayNotificationBuffer.getNotificationBuffer(mbeanServer, env); return notifBuffer; } private static final ClassLogger logger = new ClassLogger("javax.management.remote.rmi", "RMIServerImpl"); /** List of WeakReference values. Each one references an RMIConnection created by this object, or null if the RMIConnection has been garbage-collected. */ private final List> clientList = new ArrayList>(); private ClassLoader cl; private MBeanServer mbeanServer; private final Map env; private RMIConnectorServer connServer; private static int connectionIdNumber; private NotificationBuffer notifBuffer; }