diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIConnector.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIConnector.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,2318 @@
+/*
+ * Copyright (c) 2002, 2017, 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.ClientCommunicatorAdmin;
+import com.sun.jmx.remote.internal.ClientListenerInfo;
+import com.sun.jmx.remote.internal.ClientNotifForwarder;
+import com.sun.jmx.remote.internal.rmi.ProxyRef;
+import com.sun.jmx.remote.util.ClassLogger;
+import com.sun.jmx.remote.util.EnvHelp;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.io.Serializable;
+import java.lang.module.ModuleDescriptor;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.net.MalformedURLException;
+import java.rmi.MarshalledObject;
+import java.rmi.NoSuchObjectException;
+import java.rmi.Remote;
+import java.rmi.ServerException;
+import java.rmi.UnmarshalException;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.RemoteObjectInvocationHandler;
+import java.rmi.server.RemoteRef;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerNotification;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationFilterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.remote.JMXConnectionNotification;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.JMXAddressable;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+import javax.security.auth.Subject;
+import jdk.internal.module.Modules;
+import sun.reflect.misc.ReflectUtil;
+import sun.rmi.server.UnicastRef2;
+import sun.rmi.transport.LiveRef;
+import java.io.NotSerializableException;
+
+import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
+
+/**
+ *
A connection to a remote RMI connector. Usually, such
+ * connections are made using {@link
+ * javax.management.remote.JMXConnectorFactory JMXConnectorFactory}.
+ * However, specialized applications can use this class directly, for
+ * example with an {@link RMIServer} stub obtained without going
+ * through JNDI.
+ *
+ * @since 1.5
+ */
+public class RMIConnector implements JMXConnector, Serializable, JMXAddressable {
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.remote.rmi", "RMIConnector");
+
+ private static final long serialVersionUID = 817323035842634473L;
+
+ static final class Util {
+ private Util() {}
+
+ /* This method can be used by code that is deliberately violating the
+ * allowed checked casts. Rather than marking the whole method containing
+ * the code with @SuppressWarnings, you can use a call to this method for
+ * the exact place where you need to escape the constraints. Typically
+ * you will "import static" this method and then write either
+ * X x = cast(y);
+ * or, if that doesn't work (e.g. X is a type variable)
+ * Util.cast(y);
+ */
+ @SuppressWarnings("unchecked")
+ public static T cast(Object x) {
+ return (T) x;
+ }
+ }
+
+ private RMIConnector(RMIServer rmiServer, JMXServiceURL address,
+ Map environment) {
+ if (rmiServer == null && address == null) throw new
+ IllegalArgumentException("rmiServer and jmxServiceURL both null");
+ initTransients();
+
+ this.rmiServer = rmiServer;
+ this.jmxServiceURL = address;
+ if (environment == null) {
+ this.env = Collections.emptyMap();
+ } else {
+ EnvHelp.checkAttributes(environment);
+ this.env = Collections.unmodifiableMap(environment);
+ }
+ }
+
+ /**
+ *
Constructs an {@code RMIConnector} that will connect
+ * the RMI connector server with the given address.
+ *
+ *
The address can refer directly to the connector server,
+ * using the following syntax:
+ *
+ * @param url the address of the RMI connector server.
+ *
+ * @param environment additional attributes specifying how to make
+ * the connection. For JNDI-based addresses, these attributes can
+ * usefully include JNDI attributes recognized by {@link
+ * InitialContext#InitialContext(Hashtable) InitialContext}. This
+ * parameter can be null, which is equivalent to an empty Map.
+ *
+ * @exception IllegalArgumentException if {@code url}
+ * is null.
+ */
+ public RMIConnector(JMXServiceURL url, Map environment) {
+ this(null, url, environment);
+ }
+
+ /**
+ *
Constructs an {@code RMIConnector} using the given RMI stub.
+ *
+ * @param rmiServer an RMI stub representing the RMI connector server.
+ * @param environment additional attributes specifying how to make
+ * the connection. This parameter can be null, which is
+ * equivalent to an empty Map.
+ *
+ * @exception IllegalArgumentException if {@code rmiServer}
+ * is null.
+ */
+ public RMIConnector(RMIServer rmiServer, Map environment) {
+ this(rmiServer, null, environment);
+ }
+
+ /**
+ *
Returns a string representation of this object. In general,
+ * the {@code toString} method returns a string that
+ * "textually represents" this object. The result should be a
+ * concise but informative representation that is easy for a
+ * person to read.
+ *
+ * @return a String representation of this object.
+ **/
+ @Override
+ public String toString() {
+ final StringBuilder b = new StringBuilder(this.getClass().getName());
+ b.append(":");
+ if (rmiServer != null) {
+ b.append(" rmiServer=").append(rmiServer.toString());
+ }
+ if (jmxServiceURL != null) {
+ if (rmiServer!=null) b.append(",");
+ b.append(" jmxServiceURL=").append(jmxServiceURL.toString());
+ }
+ return b.toString();
+ }
+
+ /**
+ *
The address of this connector.
+ *
+ * @return the address of this connector, or null if it
+ * does not have one.
+ *
+ * @since 1.6
+ */
+ public JMXServiceURL getAddress() {
+ return jmxServiceURL;
+ }
+
+ //--------------------------------------------------------------------
+ // implements JMXConnector interface
+ //--------------------------------------------------------------------
+
+ /**
+ * @throws IOException if the connection could not be made because of a
+ * communication problem
+ */
+ public void connect() throws IOException {
+ connect(null);
+ }
+
+ /**
+ * @throws IOException if the connection could not be made because of a
+ * communication problem
+ */
+ public synchronized void connect(Map environment)
+ throws IOException {
+ final boolean tracing = logger.traceOn();
+ String idstr = (tracing?"["+this.toString()+"]":null);
+
+ if (terminated) {
+ logger.trace("connect",idstr + " already closed.");
+ throw new IOException("Connector closed");
+ }
+ if (connected) {
+ logger.trace("connect",idstr + " already connected.");
+ return;
+ }
+
+ try {
+ if (tracing) logger.trace("connect",idstr + " connecting...");
+
+ final Map usemap =
+ new HashMap((this.env==null) ?
+ Collections.emptyMap() : this.env);
+
+
+ if (environment != null) {
+ EnvHelp.checkAttributes(environment);
+ usemap.putAll(environment);
+ }
+
+ // Get RMIServer stub from directory or URL encoding if needed.
+ if (tracing) logger.trace("connect",idstr + " finding stub...");
+ RMIServer stub = (rmiServer!=null)?rmiServer:
+ findRMIServer(jmxServiceURL, usemap);
+
+ // Check for secure RMIServer stub if the corresponding
+ // client-side environment property is set to "true".
+ //
+ String stringBoolean = (String) usemap.get("jmx.remote.x.check.stub");
+ boolean checkStub = EnvHelp.computeBooleanFromString(stringBoolean);
+
+ if (checkStub) checkStub(stub, rmiServerImplStubClass);
+
+ if (tracing) logger.trace("connect",idstr + " connecting stub...");
+ idstr = (tracing?"["+this.toString()+"]":null);
+
+ // Calling newClient on the RMIServer stub.
+ if (tracing)
+ logger.trace("connect",idstr + " getting connection...");
+ Object credentials = usemap.get(CREDENTIALS);
+
+ try {
+ connection = getConnection(stub, credentials, checkStub);
+ } catch (java.rmi.RemoteException re) {
+ throw re;
+ }
+
+ // Always use one of:
+ // ClassLoader provided in Map at connect time,
+ // or contextClassLoader at connect time.
+ if (tracing)
+ logger.trace("connect",idstr + " getting class loader...");
+ defaultClassLoader = EnvHelp.resolveClientClassLoader(usemap);
+
+ usemap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,
+ defaultClassLoader);
+
+ rmiNotifClient = new RMINotifClient(defaultClassLoader, usemap);
+
+ env = usemap;
+ final long checkPeriod = EnvHelp.getConnectionCheckPeriod(usemap);
+ communicatorAdmin = new RMIClientCommunicatorAdmin(checkPeriod);
+
+ connected = true;
+
+ // The connectionId variable is used in doStart(), when
+ // reconnecting, to identify the "old" connection.
+ //
+ connectionId = getConnectionId();
+
+ Notification connectedNotif =
+ new JMXConnectionNotification(JMXConnectionNotification.OPENED,
+ this,
+ connectionId,
+ clientNotifSeqNo++,
+ "Successful connection",
+ null);
+ sendNotification(connectedNotif);
+
+ if (tracing) logger.trace("connect",idstr + " done...");
+ } catch (IOException e) {
+ if (tracing)
+ logger.trace("connect",idstr + " failed to connect: " + e);
+ throw e;
+ } catch (RuntimeException e) {
+ if (tracing)
+ logger.trace("connect",idstr + " failed to connect: " + e);
+ throw e;
+ } catch (NamingException e) {
+ final String msg = "Failed to retrieve RMIServer stub: " + e;
+ if (tracing) logger.trace("connect",idstr + " " + msg);
+ throw EnvHelp.initCause(new IOException(msg),e);
+ }
+ }
+
+ public synchronized String getConnectionId() throws IOException {
+ if (terminated || !connected) {
+ if (logger.traceOn())
+ logger.trace("getConnectionId","["+this.toString()+
+ "] not connected.");
+
+ throw new IOException("Not connected");
+ }
+
+ // we do a remote call to have an IOException if the connection is broken.
+ // see the bug 4939578
+ return connection.getConnectionId();
+ }
+
+ public synchronized MBeanServerConnection getMBeanServerConnection()
+ throws IOException {
+ return getMBeanServerConnection(null);
+ }
+
+ public synchronized MBeanServerConnection
+ getMBeanServerConnection(Subject delegationSubject)
+ throws IOException {
+
+ if (terminated) {
+ if (logger.traceOn())
+ logger.trace("getMBeanServerConnection","[" + this.toString() +
+ "] already closed.");
+ throw new IOException("Connection closed");
+ } else if (!connected) {
+ if (logger.traceOn())
+ logger.trace("getMBeanServerConnection","[" + this.toString() +
+ "] is not connected.");
+ throw new IOException("Not connected");
+ }
+
+ return getConnectionWithSubject(delegationSubject);
+ }
+
+ public void
+ addConnectionNotificationListener(NotificationListener listener,
+ NotificationFilter filter,
+ Object handback) {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ connectionBroadcaster.addNotificationListener(listener, filter,
+ handback);
+ }
+
+ public void
+ removeConnectionNotificationListener(NotificationListener listener)
+ throws ListenerNotFoundException {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ connectionBroadcaster.removeNotificationListener(listener);
+ }
+
+ public void
+ removeConnectionNotificationListener(NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws ListenerNotFoundException {
+ if (listener == null)
+ throw new NullPointerException("listener");
+ connectionBroadcaster.removeNotificationListener(listener, filter,
+ handback);
+ }
+
+ private void sendNotification(Notification n) {
+ connectionBroadcaster.sendNotification(n);
+ }
+
+ public synchronized void close() throws IOException {
+ close(false);
+ }
+
+ // allows to do close after setting the flag "terminated" to true.
+ // It is necessary to avoid a deadlock, see 6296324
+ private synchronized void close(boolean intern) throws IOException {
+ final boolean tracing = logger.traceOn();
+ final boolean debug = logger.debugOn();
+ final String idstr = (tracing?"["+this.toString()+"]":null);
+
+ if (!intern) {
+ // Return if already cleanly closed.
+ //
+ if (terminated) {
+ if (closeException == null) {
+ if (tracing) logger.trace("close",idstr + " already closed.");
+ return;
+ }
+ } else {
+ terminated = true;
+ }
+ }
+
+ if (closeException != null && tracing) {
+ // Already closed, but not cleanly. Attempt again.
+ //
+ if (tracing) {
+ logger.trace("close",idstr + " had failed: " + closeException);
+ logger.trace("close",idstr + " attempting to close again.");
+ }
+ }
+
+ String savedConnectionId = null;
+ if (connected) {
+ savedConnectionId = connectionId;
+ }
+
+ closeException = null;
+
+ if (tracing) logger.trace("close",idstr + " closing.");
+
+ if (communicatorAdmin != null) {
+ communicatorAdmin.terminate();
+ }
+
+ if (rmiNotifClient != null) {
+ try {
+ rmiNotifClient.terminate();
+ if (tracing) logger.trace("close",idstr +
+ " RMI Notification client terminated.");
+ } catch (RuntimeException x) {
+ closeException = x;
+ if (tracing) logger.trace("close",idstr +
+ " Failed to terminate RMI Notification client: " + x);
+ if (debug) logger.debug("close",x);
+ }
+ }
+
+ if (connection != null) {
+ try {
+ connection.close();
+ if (tracing) logger.trace("close",idstr + " closed.");
+ } catch (NoSuchObjectException nse) {
+ // OK, the server maybe closed itself.
+ } catch (IOException e) {
+ closeException = e;
+ if (tracing) logger.trace("close",idstr +
+ " Failed to close RMIServer: " + e);
+ if (debug) logger.debug("close",e);
+ }
+ }
+
+ // Clean up MBeanServerConnection table
+ //
+ rmbscMap.clear();
+
+ /* Send notification of closure. We don't do this if the user
+ * never called connect() on the connector, because there's no
+ * connection id in that case. */
+
+ if (savedConnectionId != null) {
+ Notification closedNotif =
+ new JMXConnectionNotification(JMXConnectionNotification.CLOSED,
+ this,
+ savedConnectionId,
+ clientNotifSeqNo++,
+ "Client has been closed",
+ null);
+ sendNotification(closedNotif);
+ }
+
+ // throw exception if needed
+ //
+ if (closeException != null) {
+ if (tracing) logger.trace("close",idstr + " failed to close: " +
+ closeException);
+ if (closeException instanceof IOException)
+ throw (IOException) closeException;
+ if (closeException instanceof RuntimeException)
+ throw (RuntimeException) closeException;
+ final IOException x =
+ new IOException("Failed to close: " + closeException);
+ throw EnvHelp.initCause(x,closeException);
+ }
+ }
+
+ // added for re-connection
+ private Integer addListenerWithSubject(ObjectName name,
+ MarshalledObject filter,
+ Subject delegationSubject,
+ boolean reconnect)
+ throws InstanceNotFoundException, IOException {
+
+ final boolean debug = logger.debugOn();
+ if (debug)
+ logger.debug("addListenerWithSubject",
+ "(ObjectName,MarshalledObject,Subject)");
+
+ final ObjectName[] names = new ObjectName[] {name};
+ final MarshalledObject[] filters =
+ Util.cast(new MarshalledObject>[] {filter});
+ final Subject[] delegationSubjects = new Subject[] {
+ delegationSubject
+ };
+
+ final Integer[] listenerIDs =
+ addListenersWithSubjects(names,filters,delegationSubjects,
+ reconnect);
+
+ if (debug) logger.debug("addListenerWithSubject","listenerID="
+ + listenerIDs[0]);
+ return listenerIDs[0];
+ }
+
+ // added for re-connection
+ private Integer[] addListenersWithSubjects(ObjectName[] names,
+ MarshalledObject[] filters,
+ Subject[] delegationSubjects,
+ boolean reconnect)
+ throws InstanceNotFoundException, IOException {
+
+ final boolean debug = logger.debugOn();
+ if (debug)
+ logger.debug("addListenersWithSubjects",
+ "(ObjectName[],MarshalledObject[],Subject[])");
+
+ final ClassLoader old = pushDefaultClassLoader();
+ Integer[] listenerIDs = null;
+
+ try {
+ listenerIDs = connection.addNotificationListeners(names,
+ filters,
+ delegationSubjects);
+ } catch (NoSuchObjectException noe) {
+ // maybe reconnect
+ if (reconnect) {
+ communicatorAdmin.gotIOException(noe);
+
+ listenerIDs = connection.addNotificationListeners(names,
+ filters,
+ delegationSubjects);
+ } else {
+ throw noe;
+ }
+ } catch (IOException ioe) {
+ // send a failed notif if necessary
+ communicatorAdmin.gotIOException(ioe);
+ } finally {
+ popDefaultClassLoader(old);
+ }
+
+ if (debug) logger.debug("addListenersWithSubjects","registered "
+ + ((listenerIDs==null)?0:listenerIDs.length)
+ + " listener(s)");
+ return listenerIDs;
+ }
+
+ //--------------------------------------------------------------------
+ // Implementation of MBeanServerConnection
+ //--------------------------------------------------------------------
+ private class RemoteMBeanServerConnection implements MBeanServerConnection {
+ private Subject delegationSubject;
+
+ public RemoteMBeanServerConnection() {
+ this(null);
+ }
+
+ public RemoteMBeanServerConnection(Subject delegationSubject) {
+ this.delegationSubject = delegationSubject;
+ }
+
+ public ObjectInstance createMBean(String className,
+ ObjectName name)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ IOException {
+ if (logger.debugOn())
+ logger.debug("createMBean(String,ObjectName)",
+ "className=" + className + ", name=" +
+ name);
+
+ final ClassLoader old = pushDefaultClassLoader();
+ try {
+ return connection.createMBean(className,
+ name,
+ delegationSubject);
+ } catch (IOException ioe) {
+ communicatorAdmin.gotIOException(ioe);
+
+ return connection.createMBean(className,
+ name,
+ delegationSubject);
+ } finally {
+ popDefaultClassLoader(old);
+ }
+ }
+
+ public ObjectInstance createMBean(String className,
+ ObjectName name,
+ ObjectName loaderName)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ InstanceNotFoundException,
+ IOException {
+
+ if (logger.debugOn())
+ logger.debug("createMBean(String,ObjectName,ObjectName)",
+ "className=" + className + ", name="
+ + name + ", loaderName="
+ + loaderName + ")");
+
+ final ClassLoader old = pushDefaultClassLoader();
+ try {
+ return connection.createMBean(className,
+ name,
+ loaderName,
+ delegationSubject);
+
+ } catch (IOException ioe) {
+ communicatorAdmin.gotIOException(ioe);
+
+ return connection.createMBean(className,
+ name,
+ loaderName,
+ delegationSubject);
+
+ } finally {
+ popDefaultClassLoader(old);
+ }
+ }
+
+ public ObjectInstance createMBean(String className,
+ ObjectName name,
+ Object params[],
+ String signature[])
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ IOException {
+ if (logger.debugOn())
+ logger.debug("createMBean(String,ObjectName,Object[],String[])",
+ "className=" + className + ", name="
+ + name + ", signature=" + strings(signature));
+
+ final MarshalledObject