Path: blob/master/src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIServerImpl.java
41162 views
/*1* Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.management.remote.rmi;2627import com.sun.jmx.remote.internal.ArrayNotificationBuffer;28import com.sun.jmx.remote.internal.NotificationBuffer;29import com.sun.jmx.remote.security.JMXPluggableAuthenticator;30import com.sun.jmx.remote.util.ClassLogger;3132import java.io.Closeable;33import java.io.IOException;34import java.lang.ref.WeakReference;35import java.rmi.Remote;36import java.rmi.server.RemoteServer;37import java.rmi.server.ServerNotActiveException;38import java.security.Principal;39import java.util.ArrayList;40import java.util.Collections;41import java.util.Iterator;42import java.util.List;43import java.util.Map;44import java.util.Set;4546import javax.management.MBeanServer;47import javax.management.remote.JMXAuthenticator;48import javax.management.remote.JMXConnectorServer;49import javax.security.auth.Subject;5051/**52* <p>An RMI object representing a connector server. Remote clients53* can make connections using the {@link #newClient(Object)} method. This54* method returns an RMI object representing the connection.</p>55*56* <p>User code does not usually reference this class directly.57* RMI connection servers are usually created with the class {@link58* RMIConnectorServer}. Remote clients usually create connections59* either with {@link javax.management.remote.JMXConnectorFactory}60* or by instantiating {@link RMIConnector}.</p>61*62* <p>This is an abstract class. Concrete subclasses define the63* details of the client connection objects.</p>64*65* @since 1.566*/67public abstract class RMIServerImpl implements Closeable, RMIServer {68/**69* <p>Constructs a new <code>RMIServerImpl</code>.</p>70*71* @param env the environment containing attributes for the new72* <code>RMIServerImpl</code>. Can be null, which is equivalent73* to an empty Map.74*/75public RMIServerImpl(Map<String,?> env) {76this.env = (env == null) ? Collections.<String,Object>emptyMap() : env;77}7879void setRMIConnectorServer(RMIConnectorServer connServer)80throws IOException {81this.connServer = connServer;82}8384/**85* <p>Exports this RMI object.</p>86*87* @exception IOException if this RMI object cannot be exported.88*/89protected abstract void export() throws IOException;9091/**92* Returns a remotable stub for this server object.93* @return a remotable stub.94* @exception IOException if the stub cannot be obtained - e.g the95* RMIServerImpl has not been exported yet.96**/97public abstract Remote toStub() throws IOException;9899/**100* <p>Sets the default <code>ClassLoader</code> for this connector101* server. New client connections will use this classloader.102* Existing client connections are unaffected.</p>103*104* @param cl the new <code>ClassLoader</code> to be used by this105* connector server.106*107* @see #getDefaultClassLoader108*/109public synchronized void setDefaultClassLoader(ClassLoader cl) {110this.cl = cl;111}112113/**114* <p>Gets the default <code>ClassLoader</code> used by this connector115* server.</p>116*117* @return the default <code>ClassLoader</code> used by this118* connector server.119*120* @see #setDefaultClassLoader121*/122public synchronized ClassLoader getDefaultClassLoader() {123return cl;124}125126/**127* <p>Sets the <code>MBeanServer</code> to which this connector128* server is attached. New client connections will interact129* with this <code>MBeanServer</code>. Existing client connections are130* unaffected.</p>131*132* @param mbs the new <code>MBeanServer</code>. Can be null, but133* new client connections will be refused as long as it is.134*135* @see #getMBeanServer136*/137public synchronized void setMBeanServer(MBeanServer mbs) {138this.mbeanServer = mbs;139}140141/**142* <p>The <code>MBeanServer</code> to which this connector server143* is attached. This is the last value passed to {@link144* #setMBeanServer} on this object, or null if that method has145* never been called.</p>146*147* @return the <code>MBeanServer</code> to which this connector148* is attached.149*150* @see #setMBeanServer151*/152public synchronized MBeanServer getMBeanServer() {153return mbeanServer;154}155156public String getVersion() {157// Expected format is: "protocol-version implementation-name"158try {159return "1.0 java_runtime_" +160System.getProperty("java.runtime.version");161} catch (SecurityException e) {162return "1.0 ";163}164}165166/**167* <p>Creates a new client connection. This method calls {@link168* #makeClient makeClient} and adds the returned client connection169* object to an internal list. When this170* <code>RMIServerImpl</code> is shut down via its {@link171* #close()} method, the {@link RMIConnection#close() close()}172* method of each object remaining in the list is called.</p>173*174* <p>The fact that a client connection object is in this internal175* list does not prevent it from being garbage collected.</p>176*177* @param credentials this object specifies the user-defined178* credentials to be passed in to the server in order to179* authenticate the caller before creating the180* <code>RMIConnection</code>. Can be null.181*182* @return the newly-created <code>RMIConnection</code>. This is183* usually the object created by <code>makeClient</code>, though184* an implementation may choose to wrap that object in another185* object implementing <code>RMIConnection</code>.186*187* @exception IOException if the new client object cannot be188* created or exported.189*190* @exception SecurityException if the given credentials do not allow191* the server to authenticate the user successfully.192*193* @exception IllegalStateException if {@link #getMBeanServer()}194* is null.195*/196public RMIConnection newClient(Object credentials) throws IOException {197return doNewClient(credentials);198}199200/**201* This method could be overridden by subclasses defined in this package202* to perform additional operations specific to the underlying transport203* before creating the new client connection.204*/205RMIConnection doNewClient(Object credentials) throws IOException {206final boolean tracing = logger.traceOn();207208if (tracing) logger.trace("newClient","making new client");209210if (getMBeanServer() == null)211throw new IllegalStateException("Not attached to an MBean server");212213Subject subject = null;214JMXAuthenticator authenticator =215(JMXAuthenticator) env.get(JMXConnectorServer.AUTHENTICATOR);216if (authenticator == null) {217/*218* Create the JAAS-based authenticator only if authentication219* has been enabled220*/221if (env.get("jmx.remote.x.password.file") != null ||222env.get("jmx.remote.x.login.config") != null) {223authenticator = new JMXPluggableAuthenticator(env);224}225}226if (authenticator != null) {227if (tracing) logger.trace("newClient","got authenticator: " +228authenticator.getClass().getName());229try {230subject = authenticator.authenticate(credentials);231} catch (SecurityException e) {232logger.trace("newClient", "Authentication failed: " + e);233throw e;234}235}236237if (tracing) {238if (subject != null)239logger.trace("newClient","subject is not null");240else logger.trace("newClient","no subject");241}242243final String connectionId = makeConnectionId(getProtocol(), subject);244245if (tracing)246logger.trace("newClient","making new connection: " + connectionId);247248RMIConnection client = makeClient(connectionId, subject);249250dropDeadReferences();251WeakReference<RMIConnection> wr = new WeakReference<RMIConnection>(client);252synchronized (clientList) {253clientList.add(wr);254}255256connServer.connectionOpened(connectionId, "Connection opened", null);257258synchronized (clientList) {259if (!clientList.contains(wr)) {260// can be removed only by a JMXConnectionNotification listener261throw new IOException("The connection is refused.");262}263}264265if (tracing)266logger.trace("newClient","new connection done: " + connectionId );267268return client;269}270271/**272* <p>Creates a new client connection. This method is called by273* the public method {@link #newClient(Object)}.</p>274*275* @param connectionId the ID of the new connection. Every276* connection opened by this connector server will have a277* different ID. The behavior is unspecified if this parameter is278* null.279*280* @param subject the authenticated subject. Can be null.281*282* @return the newly-created <code>RMIConnection</code>.283*284* @exception IOException if the new client object cannot be285* created or exported.286*/287protected abstract RMIConnection makeClient(String connectionId,288Subject subject)289throws IOException;290291/**292* <p>Closes a client connection made by {@link #makeClient makeClient}.293*294* @param client a connection previously returned by295* <code>makeClient</code> on which the <code>closeClient</code>296* method has not previously been called. The behavior is297* unspecified if these conditions are violated, including the298* case where <code>client</code> is null.299*300* @exception IOException if the client connection cannot be301* closed.302*/303protected abstract void closeClient(RMIConnection client)304throws IOException;305306/**307* <p>Returns the protocol string for this object. The string is308* <code>rmi</code> for RMI/JRMP.309*310* @return the protocol string for this object.311*/312protected abstract String getProtocol();313314/**315* <p>Method called when a client connection created by {@link316* #makeClient makeClient} is closed. A subclass that defines317* <code>makeClient</code> must arrange for this method to be318* called when the resultant object's {@link RMIConnection#close()319* close} method is called. This enables it to be removed from320* the <code>RMIServerImpl</code>'s list of connections. It is321* not an error for <code>client</code> not to be in that322* list.</p>323*324* <p>After removing <code>client</code> from the list of325* connections, this method calls {@link #closeClient326* closeClient(client)}.</p>327*328* @param client the client connection that has been closed.329*330* @exception IOException if {@link #closeClient} throws this331* exception.332*333* @exception NullPointerException if <code>client</code> is null.334*/335protected void clientClosed(RMIConnection client) throws IOException {336final boolean debug = logger.debugOn();337338if (debug) logger.trace("clientClosed","client="+client);339340if (client == null)341throw new NullPointerException("Null client");342343synchronized (clientList) {344dropDeadReferences();345for (Iterator<WeakReference<RMIConnection>> it = clientList.iterator();346it.hasNext(); ) {347WeakReference<RMIConnection> wr = it.next();348if (wr.get() == client) {349it.remove();350break;351}352}353/* It is not a bug for this loop not to find the client. In354our close() method, we remove a client from the list before355calling its close() method. */356}357358if (debug) logger.trace("clientClosed", "closing client.");359closeClient(client);360361if (debug) logger.trace("clientClosed", "sending notif");362connServer.connectionClosed(client.getConnectionId(),363"Client connection closed", null);364365if (debug) logger.trace("clientClosed","done");366}367368/**369* <p>Closes this connection server. This method first calls the370* {@link #closeServer()} method so that no new client connections371* will be accepted. Then, for each remaining {@link372* RMIConnection} object returned by {@link #makeClient373* makeClient}, its {@link RMIConnection#close() close} method is374* called.</p>375*376* <p>The behavior when this method is called more than once is377* unspecified.</p>378*379* <p>If {@link #closeServer()} throws an380* <code>IOException</code>, the individual connections are381* nevertheless closed, and then the <code>IOException</code> is382* thrown from this method.</p>383*384* <p>If {@link #closeServer()} returns normally but one or more385* of the individual connections throws an386* <code>IOException</code>, then, after closing all the387* connections, one of those <code>IOException</code>s is thrown388* from this method. If more than one connection throws an389* <code>IOException</code>, it is unspecified which one is thrown390* from this method.</p>391*392* @exception IOException if {@link #closeServer()} or one of the393* {@link RMIConnection#close()} calls threw394* <code>IOException</code>.395*/396public synchronized void close() throws IOException {397final boolean tracing = logger.traceOn();398final boolean debug = logger.debugOn();399400if (tracing) logger.trace("close","closing");401402IOException ioException = null;403try {404if (debug) logger.debug("close","closing Server");405closeServer();406} catch (IOException e) {407if (tracing) logger.trace("close","Failed to close server: " + e);408if (debug) logger.debug("close",e);409ioException = e;410}411412if (debug) logger.debug("close","closing Clients");413// Loop to close all clients414while (true) {415synchronized (clientList) {416if (debug) logger.debug("close","droping dead references");417dropDeadReferences();418419if (debug) logger.debug("close","client count: "+clientList.size());420if (clientList.size() == 0)421break;422/* Loop until we find a non-null client. Because we called423dropDeadReferences(), this will usually be the first424element of the list, but a garbage collection could have425happened in between. */426for (Iterator<WeakReference<RMIConnection>> it = clientList.iterator();427it.hasNext(); ) {428WeakReference<RMIConnection> wr = it.next();429RMIConnection client = wr.get();430it.remove();431if (client != null) {432try {433client.close();434} catch (IOException e) {435if (tracing)436logger.trace("close","Failed to close client: " + e);437if (debug) logger.debug("close",e);438if (ioException == null)439ioException = e;440}441break;442}443}444}445}446447if(notifBuffer != null)448notifBuffer.dispose();449450if (ioException != null) {451if (tracing) logger.trace("close","close failed.");452throw ioException;453}454455if (tracing) logger.trace("close","closed.");456}457458/**459* <p>Called by {@link #close()} to close the connector server.460* After returning from this method, the connector server must461* not accept any new connections.</p>462*463* @exception IOException if the attempt to close the connector464* server failed.465*/466protected abstract void closeServer() throws IOException;467468private static synchronized String makeConnectionId(String protocol,469Subject subject) {470connectionIdNumber++;471472String clientHost = "";473try {474clientHost = RemoteServer.getClientHost();475/*476* According to the rules specified in the javax.management.remote477* package description, a numeric IPv6 address (detected by the478* presence of otherwise forbidden ":" character) forming a part479* of the connection id must be enclosed in square brackets.480*/481if (clientHost.contains(":")) {482clientHost = "[" + clientHost + "]";483}484} catch (ServerNotActiveException e) {485logger.trace("makeConnectionId", "getClientHost", e);486}487488final StringBuilder buf = new StringBuilder();489buf.append(protocol).append(":");490if (clientHost.length() > 0)491buf.append("//").append(clientHost);492buf.append(" ");493if (subject != null) {494Set<Principal> principals = subject.getPrincipals();495String sep = "";496for (Iterator<Principal> it = principals.iterator(); it.hasNext(); ) {497Principal p = it.next();498String name = p.getName().replace(' ', '_').replace(';', ':');499buf.append(sep).append(name);500sep = ";";501}502}503buf.append(" ").append(connectionIdNumber);504if (logger.traceOn())505logger.trace("newConnectionId","connectionId="+buf);506return buf.toString();507}508509private void dropDeadReferences() {510synchronized (clientList) {511for (Iterator<WeakReference<RMIConnection>> it = clientList.iterator();512it.hasNext(); ) {513WeakReference<RMIConnection> wr = it.next();514if (wr.get() == null)515it.remove();516}517}518}519520synchronized NotificationBuffer getNotifBuffer() {521//Notification buffer is lazily created when the first client connects522if(notifBuffer == null)523notifBuffer =524ArrayNotificationBuffer.getNotificationBuffer(mbeanServer,525env);526return notifBuffer;527}528529private static final ClassLogger logger =530new ClassLogger("javax.management.remote.rmi", "RMIServerImpl");531532/** List of WeakReference values. Each one references an533RMIConnection created by this object, or null if the534RMIConnection has been garbage-collected. */535private final List<WeakReference<RMIConnection>> clientList =536new ArrayList<WeakReference<RMIConnection>>();537538private ClassLoader cl;539540private MBeanServer mbeanServer;541542private final Map<String, ?> env;543544private RMIConnectorServer connServer;545546private static int connectionIdNumber;547548private NotificationBuffer notifBuffer;549}550551552