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:

+ * + *
+     * service:jmx:rmi://[host[:port]]/stub/encoded-stub
+     * 
+ * + *

(Here, the square brackets {@code []} are not part of the + * address but indicate that the host and port are optional.)

+ * + *

The address can instead indicate where to find an RMI stub + * through JNDI, using the following syntax:

+ * + *
+     * service:jmx:rmi://[host[:port]]/jndi/jndi-name
+     * 
+ * + *

An implementation may also recognize additional address + * syntaxes, for example:

+ * + *
+     * service:jmx:iiop://[host[:port]]/stub/encoded-stub
+     * 
+ * + * @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 sParams = + new MarshalledObject(params); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.createMBean(className, + name, + sParams, + signature, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.createMBean(className, + name, + sParams, + signature, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public ObjectInstance createMBean(String className, + ObjectName name, + ObjectName loaderName, + Object params[], + String signature[]) + throws ReflectionException, + InstanceAlreadyExistsException, + MBeanRegistrationException, + MBeanException, + NotCompliantMBeanException, + InstanceNotFoundException, + IOException { + if (logger.debugOn()) logger.debug( + "createMBean(String,ObjectName,ObjectName,Object[],String[])", + "className=" + className + ", name=" + name + ", loaderName=" + + loaderName + ", signature=" + strings(signature)); + + final MarshalledObject sParams = + new MarshalledObject(params); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.createMBean(className, + name, + loaderName, + sParams, + signature, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.createMBean(className, + name, + loaderName, + sParams, + signature, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public void unregisterMBean(ObjectName name) + throws InstanceNotFoundException, + MBeanRegistrationException, + IOException { + if (logger.debugOn()) + logger.debug("unregisterMBean", "name=" + name); + + final ClassLoader old = pushDefaultClassLoader(); + try { + connection.unregisterMBean(name, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.unregisterMBean(name, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public ObjectInstance getObjectInstance(ObjectName name) + throws InstanceNotFoundException, + IOException { + if (logger.debugOn()) + logger.debug("getObjectInstance", "name=" + name); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getObjectInstance(name, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getObjectInstance(name, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public Set queryMBeans(ObjectName name, + QueryExp query) + throws IOException { + if (logger.debugOn()) logger.debug("queryMBeans", + "name=" + name + ", query=" + query); + + final MarshalledObject sQuery = + new MarshalledObject(query); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.queryMBeans(name, sQuery, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.queryMBeans(name, sQuery, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public Set queryNames(ObjectName name, + QueryExp query) + throws IOException { + if (logger.debugOn()) logger.debug("queryNames", + "name=" + name + ", query=" + query); + + final MarshalledObject sQuery = + new MarshalledObject(query); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.queryNames(name, sQuery, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.queryNames(name, sQuery, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public boolean isRegistered(ObjectName name) + throws IOException { + if (logger.debugOn()) + logger.debug("isRegistered", "name=" + name); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.isRegistered(name, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.isRegistered(name, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public Integer getMBeanCount() + throws IOException { + if (logger.debugOn()) logger.debug("getMBeanCount", ""); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getMBeanCount(delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getMBeanCount(delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public Object getAttribute(ObjectName name, + String attribute) + throws MBeanException, + AttributeNotFoundException, + InstanceNotFoundException, + ReflectionException, + IOException { + if (logger.debugOn()) logger.debug("getAttribute", + "name=" + name + ", attribute=" + + attribute); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getAttribute(name, + attribute, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getAttribute(name, + attribute, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public AttributeList getAttributes(ObjectName name, + String[] attributes) + throws InstanceNotFoundException, + ReflectionException, + IOException { + if (logger.debugOn()) logger.debug("getAttributes", + "name=" + name + ", attributes=" + + strings(attributes)); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getAttributes(name, + attributes, + delegationSubject); + + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getAttributes(name, + attributes, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + + public void setAttribute(ObjectName name, + Attribute attribute) + throws InstanceNotFoundException, + AttributeNotFoundException, + InvalidAttributeValueException, + MBeanException, + ReflectionException, + IOException { + + if (logger.debugOn()) logger.debug("setAttribute", + "name=" + name + ", attribute name=" + + attribute.getName()); + + final MarshalledObject sAttribute = + new MarshalledObject(attribute); + final ClassLoader old = pushDefaultClassLoader(); + try { + connection.setAttribute(name, sAttribute, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.setAttribute(name, sAttribute, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public AttributeList setAttributes(ObjectName name, + AttributeList attributes) + throws InstanceNotFoundException, + ReflectionException, + IOException { + + if (logger.debugOn()) { + logger.debug("setAttributes", + "name=" + name + ", attribute names=" + + getAttributesNames(attributes)); + } + + final MarshalledObject sAttributes = + new MarshalledObject(attributes); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.setAttributes(name, + sAttributes, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.setAttributes(name, + sAttributes, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + + public Object invoke(ObjectName name, + String operationName, + Object params[], + String signature[]) + throws InstanceNotFoundException, + MBeanException, + ReflectionException, + IOException { + + if (logger.debugOn()) logger.debug("invoke", + "name=" + name + + ", operationName=" + operationName + + ", signature=" + strings(signature)); + + final MarshalledObject sParams = + new MarshalledObject(params); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.invoke(name, + operationName, + sParams, + signature, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.invoke(name, + operationName, + sParams, + signature, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + + public String getDefaultDomain() + throws IOException { + if (logger.debugOn()) logger.debug("getDefaultDomain", ""); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getDefaultDomain(delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getDefaultDomain(delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public String[] getDomains() throws IOException { + if (logger.debugOn()) logger.debug("getDomains", ""); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getDomains(delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getDomains(delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public MBeanInfo getMBeanInfo(ObjectName name) + throws InstanceNotFoundException, + IntrospectionException, + ReflectionException, + IOException { + + if (logger.debugOn()) logger.debug("getMBeanInfo", "name=" + name); + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.getMBeanInfo(name, delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.getMBeanInfo(name, delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + + public boolean isInstanceOf(ObjectName name, + String className) + throws InstanceNotFoundException, + IOException { + if (logger.debugOn()) + logger.debug("isInstanceOf", "name=" + name + + ", className=" + className); + + final ClassLoader old = pushDefaultClassLoader(); + try { + return connection.isInstanceOf(name, + className, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + return connection.isInstanceOf(name, + className, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public void addNotificationListener(ObjectName name, + ObjectName listener, + NotificationFilter filter, + Object handback) + throws InstanceNotFoundException, + IOException { + + if (logger.debugOn()) + logger.debug("addNotificationListener" + + "(ObjectName,ObjectName,NotificationFilter,Object)", + "name=" + name + ", listener=" + listener + + ", filter=" + filter + ", handback=" + handback); + + final MarshalledObject sFilter = + new MarshalledObject(filter); + final MarshalledObject sHandback = + new MarshalledObject(handback); + final ClassLoader old = pushDefaultClassLoader(); + try { + connection.addNotificationListener(name, + listener, + sFilter, + sHandback, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.addNotificationListener(name, + listener, + sFilter, + sHandback, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public void removeNotificationListener(ObjectName name, + ObjectName listener) + throws InstanceNotFoundException, + ListenerNotFoundException, + IOException { + + if (logger.debugOn()) logger.debug("removeNotificationListener" + + "(ObjectName,ObjectName)", + "name=" + name + + ", listener=" + listener); + + final ClassLoader old = pushDefaultClassLoader(); + try { + connection.removeNotificationListener(name, + listener, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.removeNotificationListener(name, + listener, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + public void removeNotificationListener(ObjectName name, + ObjectName listener, + NotificationFilter filter, + Object handback) + throws InstanceNotFoundException, + ListenerNotFoundException, + IOException { + if (logger.debugOn()) + logger.debug("removeNotificationListener" + + "(ObjectName,ObjectName,NotificationFilter,Object)", + "name=" + name + + ", listener=" + listener + + ", filter=" + filter + + ", handback=" + handback); + + final MarshalledObject sFilter = + new MarshalledObject(filter); + final MarshalledObject sHandback = + new MarshalledObject(handback); + final ClassLoader old = pushDefaultClassLoader(); + try { + connection.removeNotificationListener(name, + listener, + sFilter, + sHandback, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.removeNotificationListener(name, + listener, + sFilter, + sHandback, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + } + + // Specific Notification Handle ---------------------------------- + + public void addNotificationListener(ObjectName name, + NotificationListener listener, + NotificationFilter filter, + Object handback) + throws InstanceNotFoundException, + IOException { + + final boolean debug = logger.debugOn(); + + if (debug) + logger.debug("addNotificationListener" + + "(ObjectName,NotificationListener,"+ + "NotificationFilter,Object)", + "name=" + name + + ", listener=" + listener + + ", filter=" + filter + + ", handback=" + handback); + + final Integer listenerID = + addListenerWithSubject(name, + new MarshalledObject(filter), + delegationSubject,true); + rmiNotifClient.addNotificationListener(listenerID, name, listener, + filter, handback, + delegationSubject); + } + + public void removeNotificationListener(ObjectName name, + NotificationListener listener) + throws InstanceNotFoundException, + ListenerNotFoundException, + IOException { + + final boolean debug = logger.debugOn(); + + if (debug) logger.debug("removeNotificationListener"+ + "(ObjectName,NotificationListener)", + "name=" + name + + ", listener=" + listener); + + final Integer[] ret = + rmiNotifClient.getListenerIds(name, listener); + + if (debug) logger.debug("removeNotificationListener", + "listenerIDs=" + objects(ret)); + + final ClassLoader old = pushDefaultClassLoader(); + + try { + connection.removeNotificationListeners(name, + ret, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.removeNotificationListeners(name, + ret, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + rmiNotifClient.removeNotificationListener(name, listener); + } + + public void removeNotificationListener(ObjectName name, + NotificationListener listener, + NotificationFilter filter, + Object handback) + throws InstanceNotFoundException, + ListenerNotFoundException, + IOException { + final boolean debug = logger.debugOn(); + + if (debug) + logger.debug("removeNotificationListener"+ + "(ObjectName,NotificationListener,"+ + "NotificationFilter,Object)", + "name=" + name + + ", listener=" + listener + + ", filter=" + filter + + ", handback=" + handback); + + final Integer ret = + rmiNotifClient.getListenerId(name, listener, + filter, handback); + + if (debug) logger.debug("removeNotificationListener", + "listenerID=" + ret); + + final ClassLoader old = pushDefaultClassLoader(); + try { + connection.removeNotificationListeners(name, + new Integer[] {ret}, + delegationSubject); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.removeNotificationListeners(name, + new Integer[] {ret}, + delegationSubject); + } finally { + popDefaultClassLoader(old); + } + rmiNotifClient.removeNotificationListener(name, listener, + filter, handback); + } + } + + //-------------------------------------------------------------------- + private class RMINotifClient extends ClientNotifForwarder { + public RMINotifClient(ClassLoader cl, Map env) { + super(cl, env); + } + + protected NotificationResult fetchNotifs(long clientSequenceNumber, + int maxNotifications, + long timeout) + throws IOException, ClassNotFoundException { + + boolean retried = false; + while (true) { // used for a successful re-connection + // or a transient network problem + try { + return connection.fetchNotifications(clientSequenceNumber, + maxNotifications, + timeout); // return normally + } catch (IOException ioe) { + // Examine the chain of exceptions to determine whether this + // is a deserialization issue. If so - we propagate the + // appropriate exception to the caller, who will then + // proceed with fetching notifications one by one + rethrowDeserializationException(ioe); + + try { + communicatorAdmin.gotIOException(ioe); + // reconnection OK, back to "while" to do again + } catch (IOException ee) { + boolean toClose = false; + + synchronized (this) { + if (terminated) { + // the connection is closed. + throw ioe; + } else if (retried) { + toClose = true; + } + } + + if (toClose) { + // JDK-8049303 + // We received an IOException - but the communicatorAdmin + // did not close the connection - possibly because + // the original exception was raised by a transient network + // problem? + // We already know that this exception is not due to a deserialization + // issue as we already took care of that before involving the + // communicatorAdmin. Moreover - we already made one retry attempt + // at fetching the same batch of notifications - and the + // problem persisted. + // Since trying again doesn't seem to solve the issue, we will now + // close the connection. Doing otherwise might cause the + // NotifFetcher thread to die silently. + final Notification failedNotif = + new JMXConnectionNotification( + JMXConnectionNotification.FAILED, + this, + connectionId, + clientNotifSeqNo++, + "Failed to communicate with the server: " + ioe.toString(), + ioe); + + sendNotification(failedNotif); + + try { + close(true); + } catch (Exception e) { + // OK. + // We are closing + } + throw ioe; // the connection is closed here. + } else { + // JDK-8049303 possible transient network problem, + // let's try one more time + retried = true; + } + } + } + } + } + + private void rethrowDeserializationException(IOException ioe) + throws ClassNotFoundException, IOException { + // specially treating for an UnmarshalException + if (ioe instanceof UnmarshalException) { + NotSerializableException nse = new NotSerializableException(); + nse.initCause(ioe); + throw nse; // the fix of 6937053 made ClientNotifForwarder.fetchNotifs + // fetch one by one with UnmarshalException + } + + // Not serialization problem, return. + } + + protected Integer addListenerForMBeanRemovedNotif() + throws IOException, InstanceNotFoundException { + NotificationFilterSupport clientFilter = + new NotificationFilterSupport(); + clientFilter.enableType( + MBeanServerNotification.UNREGISTRATION_NOTIFICATION); + MarshalledObject sFilter = + new MarshalledObject(clientFilter); + + Integer[] listenerIDs; + final ObjectName[] names = + new ObjectName[] {MBeanServerDelegate.DELEGATE_NAME}; + final MarshalledObject[] filters = + Util.cast(new MarshalledObject[] {sFilter}); + final Subject[] subjects = new Subject[] {null}; + try { + listenerIDs = + connection.addNotificationListeners(names, + filters, + subjects); + + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + listenerIDs = + connection.addNotificationListeners(names, + filters, + subjects); + } + return listenerIDs[0]; + } + + protected void removeListenerForMBeanRemovedNotif(Integer id) + throws IOException, InstanceNotFoundException, + ListenerNotFoundException { + try { + connection.removeNotificationListeners( + MBeanServerDelegate.DELEGATE_NAME, + new Integer[] {id}, + null); + } catch (IOException ioe) { + communicatorAdmin.gotIOException(ioe); + + connection.removeNotificationListeners( + MBeanServerDelegate.DELEGATE_NAME, + new Integer[] {id}, + null); + } + + } + + protected void lostNotifs(String message, long number) { + final String notifType = JMXConnectionNotification.NOTIFS_LOST; + + final JMXConnectionNotification n = + new JMXConnectionNotification(notifType, + RMIConnector.this, + connectionId, + clientNotifCounter++, + message, + Long.valueOf(number)); + sendNotification(n); + } + } + + private class RMIClientCommunicatorAdmin extends ClientCommunicatorAdmin { + public RMIClientCommunicatorAdmin(long period) { + super(period); + } + + @Override + public void gotIOException(IOException ioe) throws IOException { + if (ioe instanceof NoSuchObjectException) { + // need to restart + super.gotIOException(ioe); + + return; + } + + // check if the connection is broken + try { + connection.getDefaultDomain(null); + } catch (IOException ioexc) { + boolean toClose = false; + + synchronized(this) { + if (!terminated) { + terminated = true; + + toClose = true; + } + } + + if (toClose) { + // we should close the connection, + // but send a failed notif at first + final Notification failedNotif = + new JMXConnectionNotification( + JMXConnectionNotification.FAILED, + this, + connectionId, + clientNotifSeqNo++, + "Failed to communicate with the server: "+ioe.toString(), + ioe); + + sendNotification(failedNotif); + + try { + close(true); + } catch (Exception e) { + // OK. + // We are closing + } + } + } + + // forward the exception + if (ioe instanceof ServerException) { + /* Need to unwrap the exception. + Some user-thrown exception at server side will be wrapped by + rmi into a ServerException. + For example, a RMIConnnectorServer will wrap a + ClassNotFoundException into a UnmarshalException, and rmi + will throw a ServerException at client side which wraps this + UnmarshalException. + No failed notif here. + */ + Throwable tt = ((ServerException)ioe).detail; + + if (tt instanceof IOException) { + throw (IOException)tt; + } else if (tt instanceof RuntimeException) { + throw (RuntimeException)tt; + } + } + + throw ioe; + } + + public void reconnectNotificationListeners(ClientListenerInfo[] old) throws IOException { + final int len = old.length; + int i; + + ClientListenerInfo[] clis = new ClientListenerInfo[len]; + + final Subject[] subjects = new Subject[len]; + final ObjectName[] names = new ObjectName[len]; + final NotificationListener[] listeners = new NotificationListener[len]; + final NotificationFilter[] filters = new NotificationFilter[len]; + final MarshalledObject[] mFilters = + Util.cast(new MarshalledObject[len]); + final Object[] handbacks = new Object[len]; + + for (i=0;i(filters[i]); + handbacks[i] = old[i].getHandback(); + } + + try { + Integer[] ids = addListenersWithSubjects(names,mFilters,subjects,false); + + for (i=0;i(filters[i]), + subjects[i], + false); + + clis[j++] = new ClientListenerInfo(id, + names[i], + listeners[i], + filters[i], + handbacks[i], + subjects[i]); + } catch (InstanceNotFoundException infe) { + logger.warning("reconnectNotificationListeners", + "Can't reconnect listener for " + + names[i]); + } + } + + if (j != len) { + ClientListenerInfo[] tmp = clis; + clis = new ClientListenerInfo[j]; + System.arraycopy(tmp, 0, clis, 0, j); + } + + rmiNotifClient.postReconnection(clis); + } + + protected void checkConnection() throws IOException { + if (logger.debugOn()) + logger.debug("RMIClientCommunicatorAdmin-checkConnection", + "Calling the method getDefaultDomain."); + + connection.getDefaultDomain(null); + } + + protected void doStart() throws IOException { + // Get RMIServer stub from directory or URL encoding if needed. + RMIServer stub; + try { + stub = (rmiServer!=null)?rmiServer: + findRMIServer(jmxServiceURL, env); + } catch (NamingException ne) { + throw new IOException("Failed to get a RMI stub: "+ne); + } + + // Calling newClient on the RMIServer stub. + Object credentials = env.get(CREDENTIALS); + connection = stub.newClient(credentials); + + // notif issues + final ClientListenerInfo[] old = rmiNotifClient.preReconnection(); + + reconnectNotificationListeners(old); + + connectionId = getConnectionId(); + + Notification reconnectedNotif = + new JMXConnectionNotification(JMXConnectionNotification.OPENED, + this, + connectionId, + clientNotifSeqNo++, + "Reconnected to server", + null); + sendNotification(reconnectedNotif); + + } + + protected void doStop() { + try { + close(); + } catch (IOException ioe) { + logger.warning("RMIClientCommunicatorAdmin-doStop", + "Failed to call the method close():" + ioe); + logger.debug("RMIClientCommunicatorAdmin-doStop",ioe); + } + } + } + + //-------------------------------------------------------------------- + // Private stuff - Serialization + //-------------------------------------------------------------------- + /** + * Read RMIConnector fields from an {@link java.io.ObjectInputStream + * ObjectInputStream}. + * Calls {@code s.defaultReadObject()} and then initializes + * all transient variables that need initializing. + * @param s The ObjectInputStream to read from. + * @exception InvalidObjectException if none of rmiServer stub + * or jmxServiceURL are set. + * @see #RMIConnector(JMXServiceURL,Map) + * @see #RMIConnector(RMIServer,Map) + **/ + private void readObject(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + + if (rmiServer == null && jmxServiceURL == null) throw new + InvalidObjectException("rmiServer and jmxServiceURL both null"); + + initTransients(); + } + + /** + * Writes the RMIConnector fields to an {@link java.io.ObjectOutputStream + * ObjectOutputStream}. + *

Connects the underlying RMIServer stub to an ORB, if needed, + * before serializing it. This is done using the environment + * map that was provided to the constructor, if any, and as documented + * in {@link javax.management.remote.rmi}.

+ *

This method then calls {@code s.defaultWriteObject()}. + * Usually, rmiServer is null if this object + * was constructed with a JMXServiceURL, and jmxServiceURL + * is null if this object is constructed with a RMIServer stub. + *

Note that the environment Map is not serialized, since the objects + * it contains are assumed to be contextual and relevant only + * with respect to the local environment (class loader, ORB, etc...).

+ *

After an RMIConnector is deserialized, it is assumed that the + * user will call {@link #connect(Map)}, providing a new Map that + * can contain values which are contextually relevant to the new + * local environment.

+ *

Since connection to the ORB is needed prior to serializing, and + * since the ORB to connect to is one of those contextual parameters, + * it is not recommended to re-serialize a just de-serialized object - + * as the de-serialized object has no map. Thus, when an RMIConnector + * object is needed for serialization or transmission to a remote + * application, it is recommended to obtain a new RMIConnector stub + * by calling {@link RMIConnectorServer#toJMXConnector(Map)}.

+ * @param s The ObjectOutputStream to write to. + * @exception InvalidObjectException if none of rmiServer stub + * or jmxServiceURL are set. + * @see #RMIConnector(JMXServiceURL,Map) + * @see #RMIConnector(RMIServer,Map) + **/ + private void writeObject(java.io.ObjectOutputStream s) + throws IOException { + if (rmiServer == null && jmxServiceURL == null) throw new + InvalidObjectException("rmiServer and jmxServiceURL both null."); + s.defaultWriteObject(); + } + + // Initialization of transient variables. + private void initTransients() { + rmbscMap = new WeakHashMap>(); + connected = false; + terminated = false; + + connectionBroadcaster = new NotificationBroadcasterSupport(); + } + + //-------------------------------------------------------------------- + // Private stuff - Check if stub can be trusted. + //-------------------------------------------------------------------- + + private static void checkStub(Remote stub, + Class stubClass) { + + // Check remote stub is from the expected class. + // + if (stub.getClass() != stubClass) { + if (!Proxy.isProxyClass(stub.getClass())) { + throw new SecurityException( + "Expecting a " + stubClass.getName() + " stub!"); + } else { + InvocationHandler handler = Proxy.getInvocationHandler(stub); + if (handler.getClass() != RemoteObjectInvocationHandler.class) + throw new SecurityException( + "Expecting a dynamic proxy instance with a " + + RemoteObjectInvocationHandler.class.getName() + + " invocation handler!"); + else + stub = (Remote) handler; + } + } + + // Check RemoteRef in stub is from the expected class + // "sun.rmi.server.UnicastRef2". + // + RemoteRef ref = ((RemoteObject)stub).getRef(); + if (ref.getClass() != UnicastRef2.class) + throw new SecurityException( + "Expecting a " + UnicastRef2.class.getName() + + " remote reference in stub!"); + + // Check RMIClientSocketFactory in stub is from the expected class + // "javax.rmi.ssl.SslRMIClientSocketFactory". + // + LiveRef liveRef = ((UnicastRef2)ref).getLiveRef(); + RMIClientSocketFactory csf = liveRef.getClientSocketFactory(); + if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) + throw new SecurityException( + "Expecting a " + SslRMIClientSocketFactory.class.getName() + + " RMI client socket factory in stub!"); + } + + //-------------------------------------------------------------------- + // Private stuff - RMIServer creation + //-------------------------------------------------------------------- + + private RMIServer findRMIServer(JMXServiceURL directoryURL, + Map environment) + throws NamingException, IOException { + + String path = directoryURL.getURLPath(); + int end = path.indexOf(';'); + if (end < 0) end = path.length(); + if (path.startsWith("/jndi/")) + return findRMIServerJNDI(path.substring(6,end), environment); + else if (path.startsWith("/stub/")) + return findRMIServerJRMP(path.substring(6,end), environment); + else { + final String msg = "URL path must begin with /jndi/ or /stub/ " + + "or /ior/: " + path; + throw new MalformedURLException(msg); + } + } + + /** + * Lookup the RMIServer stub in a directory. + * @param jndiURL A JNDI URL indicating the location of the Stub + * (see {@link javax.management.remote.rmi}), e.g.: + *
  • {@code rmi://registry-host:port/rmi-stub-name}
  • + *
  • or {@code ldap://ldap-host:port/java-container-dn}
  • + *
+ * @param env the environment Map passed to the connector. + * @return The retrieved RMIServer stub. + * @exception NamingException if the stub couldn't be found. + **/ + private RMIServer findRMIServerJNDI(String jndiURL, Map env) + throws NamingException { + + InitialContext ctx = new InitialContext(EnvHelp.mapToHashtable(env)); + + Object objref = ctx.lookup(jndiURL); + ctx.close(); + + return narrowJRMPServer(objref); + } + + private static RMIServer narrowJRMPServer(Object objref) { + + return (RMIServer) objref; + } + + private RMIServer findRMIServerJRMP(String base64, Map env) + throws IOException { + final byte[] serialized; + try { + serialized = base64ToByteArray(base64); + } catch (IllegalArgumentException e) { + throw new MalformedURLException("Bad BASE64 encoding: " + + e.getMessage()); + } + final ByteArrayInputStream bin = new ByteArrayInputStream(serialized); + + final ClassLoader loader = EnvHelp.resolveClientClassLoader(env); + final ObjectInputStream oin = + (loader == null) ? + new ObjectInputStream(bin) : + new ObjectInputStreamWithLoader(bin, loader); + final Object stub; + try { + stub = oin.readObject(); + } catch (ClassNotFoundException e) { + throw new MalformedURLException("Class not found: " + e); + } + return (RMIServer)stub; + } + + private static final class ObjectInputStreamWithLoader + extends ObjectInputStream { + ObjectInputStreamWithLoader(InputStream in, ClassLoader cl) + throws IOException, IllegalArgumentException { + super(in); + if (cl == null ) { + throw new IllegalArgumentException("class loader is null"); + } + this.loader = cl; + } + + @Override + protected Class resolveClass(ObjectStreamClass classDesc) + throws IOException, ClassNotFoundException { + String name = classDesc.getName(); + ReflectUtil.checkPackageAccess(name); + return Class.forName(name, false, Objects.requireNonNull(loader)); + } + + private final ClassLoader loader; + } + + private MBeanServerConnection getConnectionWithSubject(Subject delegationSubject) { + MBeanServerConnection conn = null; + + if (delegationSubject == null) { + if (nullSubjectConnRef == null + || (conn = nullSubjectConnRef.get()) == null) { + conn = new RemoteMBeanServerConnection(null); + nullSubjectConnRef = new WeakReference(conn); + } + } else { + WeakReference wr = rmbscMap.get(delegationSubject); + if (wr == null || (conn = wr.get()) == null) { + conn = new RemoteMBeanServerConnection(delegationSubject); + rmbscMap.put(delegationSubject, new WeakReference(conn)); + } + } + return conn; + } + + /* + The following section of code avoids a class loading problem + with RMI. The problem is that an RMI stub, when deserializing + a remote method return value or exception, will first of all + consult the first non-bootstrap class loader it finds in the + call stack. This can lead to behavior that is not portable + between implementations of the JMX Remote API. Notably, an + implementation on J2SE 1.4 will find the RMI stub's loader on + the stack. But in J2SE 5, this stub is loaded by the + bootstrap loader, so RMI will find the loader of the user code + that called an MBeanServerConnection method. + + To avoid this problem, we take advantage of what the RMI stub + is doing internally. Each remote call will end up calling + ref.invoke(...), where ref is the RemoteRef parameter given to + the RMI stub's constructor. It is within this call that the + deserialization will happen. So we fabricate our own RemoteRef + that delegates everything to the "real" one but that is loaded + by a class loader that knows no other classes. The class + loader NoCallStackClassLoader does this: the RemoteRef is an + instance of the class named by proxyRefClassName, which is + fabricated by the class loader using byte code that is defined + by the string below. + + The call stack when the deserialization happens is thus this: + MBeanServerConnection.getAttribute (or whatever) + -> RMIConnectionImpl_Stub.getAttribute + -> ProxyRef.invoke(...getAttribute...) + -> UnicastRef.invoke(...getAttribute...) + -> internal RMI stuff + + Here UnicastRef is the RemoteRef created when the stub was + deserialized (which is of some RMI internal class). It and the + "internal RMI stuff" are loaded by the bootstrap loader, so are + transparent to the stack search. The first non-bootstrap + loader found is our ProxyRefLoader, as required. + + In a future version of this code as integrated into J2SE 5, + this workaround could be replaced by direct access to the + internals of RMI. For now, we use the same code base for J2SE + and for the standalone Reference Implementation. + + The byte code below encodes the following class, compiled using + J2SE 1.4.2 with the -g:none option. + + package jdk.jmx.remote.internal.rmi; + + import java.lang.reflect.Method; + import java.rmi.Remote; + import java.rmi.server.RemoteRef; + import com.sun.jmx.remote.internal.rmi.ProxyRef; + + public class PRef extends ProxyRef { + public PRef(RemoteRef ref) { + super(ref); + } + + public Object invoke(Remote obj, Method method, + Object[] params, long opnum) + throws Exception { + return ref.invoke(obj, method, params, opnum); + } + } + */ + + private static final String rmiServerImplStubClassName = + RMIServer.class.getName() + "Impl_Stub"; + private static final Class rmiServerImplStubClass; + private static final String rmiConnectionImplStubClassName = + RMIConnection.class.getName() + "Impl_Stub"; + private static final Class rmiConnectionImplStubClass; + private static final String pRefClassName = + "jdk.jmx.remote.internal.rmi.PRef"; + private static final Constructor proxyRefConstructor; + static { + final String pRefByteCodeString = + "\312\376\272\276\0\0\0\65\0\27\12\0\5\0\15\11\0\4\0\16\13\0\17"+ + "\0\20\7\0\21\7\0\22\1\0\6\1\0\36(Ljava/rmi/server/Remote"+ + "Ref;)V\1\0\4Code\1\0\6invoke\1\0S(Ljava/rmi/Remote;Ljava/lang/"+ + "reflect/Method;[Ljava/lang/Object;J)Ljava/lang/Object;\1\0\12E"+ + "xceptions\7\0\23\14\0\6\0\7\14\0\24\0\25\7\0\26\14\0\11\0\12\1"+ + "\0 jdk/jmx/remote/internal/rmi/PRef\1\0(com/sun/jmx/remote/int"+ + "ernal/rmi/ProxyRef\1\0\23java/lang/Exception\1\0\3ref\1\0\33Lj"+ + "ava/rmi/server/RemoteRef;\1\0\31java/rmi/server/RemoteRef\0!\0"+ + "\4\0\5\0\0\0\0\0\2\0\1\0\6\0\7\0\1\0\10\0\0\0\22\0\2\0\2\0\0\0"+ + "\6*+\267\0\1\261\0\0\0\0\0\1\0\11\0\12\0\2\0\10\0\0\0\33\0\6\0"+ + "\6\0\0\0\17*\264\0\2+,-\26\4\271\0\3\6\0\260\0\0\0\0\0\13\0\0\0"+ + "\4\0\1\0\14\0\0"; + final byte[] pRefByteCode = + NoCallStackClassLoader.stringToBytes(pRefByteCodeString); + PrivilegedExceptionAction> action = + new PrivilegedExceptionAction>() { + public Constructor run() throws Exception { + Class thisClass = RMIConnector.class; + ClassLoader thisLoader = thisClass.getClassLoader(); + ProtectionDomain thisProtectionDomain = + thisClass.getProtectionDomain(); + + String proxyRefCName = ProxyRef.class.getName(); + ClassLoader cl = + new NoCallStackClassLoader(pRefClassName, + pRefByteCode, + new String[] { proxyRefCName }, + thisLoader, + thisProtectionDomain); + + Module jmxModule = ProxyRef.class.getModule(); + Module rmiModule = RemoteRef.class.getModule(); + + String pkg = packageOf(pRefClassName); + assert pkg != null && pkg.length() > 0 && + !pkg.equals(packageOf(proxyRefCName)); + + ModuleDescriptor descriptor = + ModuleDescriptor.newModule("jdk.remoteref", Set.of(SYNTHETIC)) + .packages(Set.of(pkg)) + .build(); + Module m = Modules.defineModule(cl, descriptor, null); + + // jdk.remoteref needs to read to java.base and jmxModule + Modules.addReads(m, Object.class.getModule()); + Modules.addReads(m, jmxModule); + Modules.addReads(m, rmiModule); + + // jdk.remoteref needs access to ProxyRef class + Modules.addExports(jmxModule, packageOf(proxyRefCName), m); + + // java.management needs to instantiate the fabricated RemoteRef class + Modules.addReads(jmxModule, m); + Modules.addExports(m, pkg, jmxModule); + + Class c = cl.loadClass(pRefClassName); + return c.getConstructor(RemoteRef.class); + } + }; + + Class serverStubClass; + try { + serverStubClass = Class.forName(rmiServerImplStubClassName); + } catch (Exception e) { + logger.error("", + "Failed to instantiate " + + rmiServerImplStubClassName + ": " + e); + logger.debug("",e); + serverStubClass = null; + } + rmiServerImplStubClass = serverStubClass; + + Class stubClass; + Constructor constr; + try { + stubClass = Class.forName(rmiConnectionImplStubClassName); + constr = (Constructor) AccessController.doPrivileged(action); + } catch (Exception e) { + logger.error("", + "Failed to initialize proxy reference constructor "+ + "for " + rmiConnectionImplStubClassName + ": " + e); + logger.debug("",e); + stubClass = null; + constr = null; + } + rmiConnectionImplStubClass = stubClass; + proxyRefConstructor = constr; + } + + private static String packageOf(String cn) { + int i = cn.lastIndexOf('.'); + return i > 0 ? cn.substring(0, i) : ""; + } + + private static RMIConnection shadowJrmpStub(RemoteObject stub) + throws InstantiationException, IllegalAccessException, + InvocationTargetException, ClassNotFoundException, + NoSuchMethodException { + RemoteRef ref = stub.getRef(); + RemoteRef proxyRef = (RemoteRef) + proxyRefConstructor.newInstance(new Object[] {ref}); + final Constructor rmiConnectionImplStubConstructor = + rmiConnectionImplStubClass.getConstructor(RemoteRef.class); + Object[] args = {proxyRef}; + RMIConnection proxyStub = (RMIConnection) + rmiConnectionImplStubConstructor.newInstance(args); + return proxyStub; + } + + private static RMIConnection getConnection(RMIServer server, + Object credentials, + boolean checkStub) + throws IOException { + RMIConnection c = server.newClient(credentials); + if (checkStub) checkStub(c, rmiConnectionImplStubClass); + try { + if (c.getClass() == rmiConnectionImplStubClass) + return shadowJrmpStub((RemoteObject) c); + logger.trace("getConnection", + "Did not wrap " + c.getClass() + " to foil " + + "stack search for classes: class loading semantics " + + "may be incorrect"); + } catch (Exception e) { + logger.error("getConnection", + "Could not wrap " + c.getClass() + " to foil " + + "stack search for classes: class loading semantics " + + "may be incorrect: " + e); + logger.debug("getConnection",e); + // so just return the original stub, which will work for all + // but the most exotic class loading situations + } + return c; + } + + private static byte[] base64ToByteArray(String s) { + int sLen = s.length(); + int numGroups = sLen/4; + if (4*numGroups != sLen) + throw new IllegalArgumentException( + "String length must be a multiple of four."); + int missingBytesInLastGroup = 0; + int numFullGroups = numGroups; + if (sLen != 0) { + if (s.charAt(sLen-1) == '=') { + missingBytesInLastGroup++; + numFullGroups--; + } + if (s.charAt(sLen-2) == '=') + missingBytesInLastGroup++; + } + byte[] result = new byte[3*numGroups - missingBytesInLastGroup]; + + // Translate all full groups from base64 to byte array elements + int inCursor = 0, outCursor = 0; + for (int i=0; i> 4)); + result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2)); + result[outCursor++] = (byte) ((ch2 << 6) | ch3); + } + + // Translate partial group, if present + if (missingBytesInLastGroup != 0) { + int ch0 = base64toInt(s.charAt(inCursor++)); + int ch1 = base64toInt(s.charAt(inCursor++)); + result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4)); + + if (missingBytesInLastGroup == 1) { + int ch2 = base64toInt(s.charAt(inCursor++)); + result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2)); + } + } + // assert inCursor == s.length()-missingBytesInLastGroup; + // assert outCursor == result.length; + return result; + } + + /** + * Translates the specified character, which is assumed to be in the + * "Base 64 Alphabet" into its equivalent 6-bit positive integer. + * + * @throws IllegalArgumentException if + * c is not in the Base64 Alphabet. + */ + private static int base64toInt(char c) { + int result; + + if (c >= base64ToInt.length) + result = -1; + else + result = base64ToInt[c]; + + if (result < 0) + throw new IllegalArgumentException("Illegal character " + c); + return result; + } + + /** + * This array is a lookup table that translates unicode characters + * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) + * into their 6-bit positive integer equivalents. Characters that + * are not in the Base64 alphabet but fall within the bounds of the + * array are translated to -1. + */ + private static final byte base64ToInt[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + //-------------------------------------------------------------------- + // Private stuff - Find / Set default class loader + //-------------------------------------------------------------------- + private ClassLoader pushDefaultClassLoader() { + final Thread t = Thread.currentThread(); + final ClassLoader old = t.getContextClassLoader(); + if (defaultClassLoader != null) + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + t.setContextClassLoader(defaultClassLoader); + return null; + } + }); + return old; + } + + private void popDefaultClassLoader(final ClassLoader old) { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + Thread.currentThread().setContextClassLoader(old); + return null; + } + }); + } + + //-------------------------------------------------------------------- + // Private variables + //-------------------------------------------------------------------- + /** + * @serial The RMIServer stub of the RMI JMX Connector server to + * which this client connector is (or will be) connected. This + * field can be null when jmxServiceURL is not + * null. This includes the case where jmxServiceURL + * contains a serialized RMIServer stub. If both + * rmiServer and jmxServiceURL are null then + * serialization will fail. + * + * @see #RMIConnector(RMIServer,Map) + **/ + private final RMIServer rmiServer; + + /** + * @serial The JMXServiceURL of the RMI JMX Connector server to + * which this client connector will be connected. This field can + * be null when rmiServer is not null. If both + * rmiServer and jmxServiceURL are null then + * serialization will fail. + * + * @see #RMIConnector(JMXServiceURL,Map) + **/ + private final JMXServiceURL jmxServiceURL; + + // --------------------------------------------------------- + // WARNING - WARNING - WARNING - WARNING - WARNING - WARNING + // --------------------------------------------------------- + // Any transient variable which needs to be initialized should + // be initialized in the method initTransient() + private transient Map env; + private transient ClassLoader defaultClassLoader; + private transient RMIConnection connection; + private transient String connectionId; + + private transient long clientNotifSeqNo = 0; + + private transient WeakHashMap> rmbscMap; + private transient WeakReference nullSubjectConnRef = null; + + private transient RMINotifClient rmiNotifClient; + // = new RMINotifClient(new Integer(0)); + + private transient long clientNotifCounter = 0; + + private transient boolean connected; + // = false; + private transient boolean terminated; + // = false; + + private transient Exception closeException; + + private transient NotificationBroadcasterSupport connectionBroadcaster; + + private transient ClientCommunicatorAdmin communicatorAdmin; + + /** + * A static WeakReference to an {@link org.omg.CORBA.ORB ORB} to + * connect unconnected stubs. + **/ + private static volatile WeakReference orb = null; + + // TRACES & DEBUG + //--------------- + private static String objects(final Object[] objs) { + if (objs == null) + return "null"; + else + return Arrays.asList(objs).toString(); + } + + private static String strings(final String[] strs) { + return objects(strs); + } + + static String getAttributesNames(AttributeList attributes) { + return attributes != null ? + attributes.asList().stream() + .map(Attribute::getName) + .collect(Collectors.joining(", ", "[", "]")) + : "[]"; + } +}