/* * @(#)RMIConnector.java 1.117 05/12/01 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.management.remote.rmi; // JMX 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.MalformedObjectNameException; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanRegistrationException; import javax.management.MBeanServerConnection; import javax.management.MBeanServerNotification; import javax.management.NotCompliantMBeanException; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationEmitter; 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.JMXAuthenticator; 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.TargetedNotification; import javax.management.remote.JMXServerErrorException; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; // Util import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.WeakHashMap; // IO 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.io.WriteAbortedException; import java.io.NotSerializableException; // Net import java.net.MalformedURLException; // RMI import java.rmi.MarshalledObject; import java.rmi.NoSuchObjectException; import java.rmi.MarshalException; import java.rmi.UnmarshalException; import java.rmi.ServerException; import java.rmi.server.RemoteObject; import java.rmi.server.RemoteRef; //IIOP RMI import javax.rmi.PortableRemoteObject; import javax.rmi.CORBA.Stub; import org.omg.CORBA.portable.Delegate; // JNDI import javax.naming.InitialContext; import javax.naming.NamingException; import com.sun.jmx.remote.internal.ClientNotifForwarder; import com.sun.jmx.remote.internal.ClientCommunicatorAdmin; import com.sun.jmx.remote.internal.ClientListenerInfo; import com.sun.jmx.remote.internal.ProxyInputStream; import com.sun.jmx.remote.internal.ProxyRef; import com.sun.jmx.remote.util.ClassLogger; import com.sun.jmx.remote.util.EnvHelp; // SECURITY import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import javax.security.auth.Subject; /** *

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 * @since.unbundled 1.0 */ public class RMIConnector implements JMXConnector, Serializable { private static final ClassLogger logger = new ClassLogger("javax.management.remote.rmi", "RMIConnector"); private static final long serialVersionUID = 817323035842634473L; 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.EMPTY_MAP; } else { EnvHelp.checkAttributes(environment); this.env = Collections.unmodifiableMap(environment); } } /** *

Constructs an RMIConnector that will connect * the RMI connector server with the given address.

* *

The address can refer directly to the connector server, * using one of the following syntaxes:

* *
     * service:jmx:rmi://[host[:port]]/stub/encoded-stub
     * service:jmx:iiop://[host[:port]]/ior/encoded-IOR
     * 
* *

(Here, the square brackets [] 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 one of the following syntaxes:

* *
     * service:jmx:rmi://[host[:port]]/jndi/jndi-name
     * service:jmx:iiop://[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 url * is null. */ public RMIConnector(JMXServiceURL url, Map environment) { this(null, url, environment); } /** *

Constructs an 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 rmiServer * is null. */ public RMIConnector(RMIServer rmiServer, Map environment) { this(rmiServer, null, environment); } /** *

Returns a string representation of this object. In general, * the 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. **/ public String toString() { final StringBuffer b = new StringBuffer(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(); } //-------------------------------------------------------------------- // implements JMXConnector interface //-------------------------------------------------------------------- public void connect() throws IOException { connect(null); } 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.EMPTY_MAP: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); // Connect IIOP Stub if needed. if (tracing) logger.trace("connect",idstr + " connecting stub..."); stub = connectStub(stub,usemap); idstr = (tracing?"["+this.toString()+"]":null); // Calling newClient on the RMIServer stub. if (tracing) logger.trace("connect",idstr + " getting connection..."); Object credentials = usemap.get(CREDENTIALS); connection = getConnection(stub, credentials); // 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, clientNotifID++, "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 (IOException) 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"); } MBeanServerConnection mbsc = (MBeanServerConnection) rmbscMap.get(delegationSubject); if (mbsc != null) return mbsc; mbsc = new RemoteMBeanServerConnection(delegationSubject); rmbscMap.put(delegationSubject, mbsc); return mbsc; } 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, clientNotifID++, "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 (IOException) 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 = 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.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 + ", params=" + objects(params) + ", 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 + ", params=" + objects(params) + ", 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=" + attribute); 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 + ", attributes=" + 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 + ", params=" + objects(params) + ", 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.removeNotificationListener(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); } } 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.removeNotificationListener(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); } } } //-------------------------------------------------------------------- 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 { IOException org; while (true) { // used for a successful re-connection try { return connection.fetchNotifications(clientSequenceNumber, maxNotifications, timeout); } catch (IOException ioe) { org = ioe; // inform of IOException try { communicatorAdmin.gotIOException(ioe); // The connection should be re-established. continue; } catch (IOException ee) { // No more fetch, the Exception will be re-thrown. break; } // never reached } // never reached } // specially treating for an UnmarshalException if (org instanceof UnmarshalException) { UnmarshalException ume = (UnmarshalException)org; if (ume.detail instanceof ClassNotFoundException) throw (ClassNotFoundException) ume.detail; /* In Sun's RMI implementation, if a method return contains an unserializable object, then we get UnmarshalException wrapping WriteAbortedException wrapping NotSerializableException. In that case we extract the NotSerializableException so that our caller can realize it should try to skip past the notification that presumably caused it. It's not certain that every other RMI implementation will generate this exact exception sequence. If not, we will not detect that the problem is due to an unserializable object, and we will stop trying to receive notifications from the server. It's not clear we can do much better. */ if (ume.detail instanceof WriteAbortedException) { WriteAbortedException wae = (WriteAbortedException) ume.detail; if (wae.detail instanceof IOException) throw (IOException) wae.detail; } } else if (org instanceof MarshalException) { // IIOP will throw MarshalException wrapping a NotSerializableException // when a server fails to serialize a response. MarshalException me = (MarshalException)org; if (me.detail instanceof NotSerializableException) { throw (NotSerializableException)me.detail; } } // Not serialization problem, simply re-throw the orginal exception throw org; } protected Integer addListenerForMBeanRemovedNotif() throws IOException, InstanceNotFoundException { MarshalledObject sFilter = null; NotificationFilterSupport clientFilter = new NotificationFilterSupport(); clientFilter.enableType( MBeanServerNotification.UNREGISTRATION_NOTIFICATION); sFilter = new MarshalledObject(clientFilter); Integer[] listenerIDs; final ObjectName[] names = new ObjectName[] {delegateName}; final MarshalledObject[] filters = 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(delegateName, new Integer[] {id}, null); } catch (IOException ioe) { communicatorAdmin.gotIOException(ioe); connection.removeNotificationListeners(delegateName, 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, new Long(number)); sendNotification(n); } } private class RMIClientCommunicatorAdmin extends ClientCommunicatorAdmin { public RMIClientCommunicatorAdmin(long period) { super(period); } 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, clientNotifID++, "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 = new MarshalledObject[len]; final Object[] handbacks = new Object[len]; for (i=0;iIn order to be usable, an IIOP stub must be connected to an ORB. * The stub is automagically connected to the ORB if: *
    *
  • It was returned by the COS naming
  • *
  • Its server counterpart has been registered in COS naming * through JNDI.
  • *
* Otherwise, it is not connected. A stub which is deserialized * from Jini is not connected. A stub which is obtained from a * non registered RMIIIOPServerImpl is not a connected.
* A stub which is not connected can't be serialized, and thus * can't be registered in Jini. A stub which is not connected can't * be used to invoke methods on the server. *

* In order to palliate this, this method will connect the * given stub if it is not yet connected. If the given * RMIServer is not an instance of * {@link javax.rmi.CORBA.Stub javax.rmi.CORBA.Stub}, then the * method do nothing and simply returns that stub. Otherwise, * this method will attempt to connect the stub to an ORB as * follows: *

    *

    This method looks in the provided environment for * the "java.naming.corba.orb" property. If it is found, the * referenced object (an {@link org.omg.CORBA.ORB ORB}) is used to * connect the stub. Otherwise, a new org.omg.CORBA.ORB is created * by calling {@link * org.omg.CORBA.ORB#init(String[], Properties) * org.omg.CORBA.ORB.init((String[])null,(Properties)null)} *

    The new created ORB is kept in a static * {@link WeakReference} and can be reused for connecting other * stubs. However, no reference is ever kept on the ORB provided * in the environment map, if any. *

* @param rmiServer A RMI Server Stub. * @param environment An environment map, possibly containing an ORB. * @return the given stub. * @exception IllegalArgumentException if the * java.naming.corba.orb property is specified and * does not point to an {@link org.omg.CORBA.ORB ORB}. * @exception IOException if the connection to the ORB failed. **/ static RMIServer connectStub(RMIServer rmiServer, Map environment) throws IOException { if (rmiServer instanceof javax.rmi.CORBA.Stub) { javax.rmi.CORBA.Stub stub = (javax.rmi.CORBA.Stub) rmiServer; try { stub._orb(); } catch (org.omg.CORBA.BAD_OPERATION x) { stub.connect(resolveOrb(environment)); } } return rmiServer; } /** * Get the ORB specified by environment, or create a * new one. *

This method looks in the provided environment for * the "java.naming.corba.orb" property. If it is found, the * referenced object (an {@link org.omg.CORBA.ORB ORB}) is * returned. Otherwise, a new org.omg.CORBA.ORB is created * by calling {@link * org.omg.CORBA.ORB#init(String[], java.util.Properties) * org.omg.CORBA.ORB.init((String[])null,(Properties)null)} *

The new created ORB is kept in a static * {@link WeakReference} and can be reused for connecting other * stubs. However, no reference is ever kept on the ORB provided * in the environment map, if any. * @param environment An environment map, possibly containing an ORB. * @return An ORB. * @exception IllegalArgumentException if the * java.naming.corba.orb property is specified and * does not point to an {@link org.omg.CORBA.ORB ORB}. * @exception IOException if the ORB initialization failed. **/ static org.omg.CORBA.ORB resolveOrb(Map environment) throws IOException { if (environment != null) { final Object orb = environment.get(EnvHelp.DEFAULT_ORB); if (orb != null && !(orb instanceof org.omg.CORBA.ORB)) throw new IllegalArgumentException(EnvHelp.DEFAULT_ORB + " must be an instance of org.omg.CORBA.ORB."); if (orb != null) return (org.omg.CORBA.ORB)orb; } final Object orb = (RMIConnector.orb==null)?null:RMIConnector.orb.get(); if (orb != null) return (org.omg.CORBA.ORB)orb; final org.omg.CORBA.ORB newOrb = org.omg.CORBA.ORB.init((String[])null, (Properties)null); RMIConnector.orb = new WeakReference(newOrb); return newOrb; } /** * Read RMIConnector fields from an {@link java.io.ObjectInputStream * ObjectInputStream}. * Calls 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 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."); connectStub(this.rmiServer,env); s.defaultWriteObject(); } // Initialization of transient variables. private void initTransients() { rmbscMap = new WeakHashMap(); connected = false; terminated = false; connectionBroadcaster = new NotificationBroadcasterSupport(); } //-------------------------------------------------------------------- // Private stuff - RMIServer creation //-------------------------------------------------------------------- private RMIServer findRMIServer(JMXServiceURL directoryURL, Map environment) throws NamingException, IOException { final boolean isIiop = RMIConnectorServer.isIiopURL(directoryURL,true); if (isIiop) { // Make sure java.naming.corba.orb is in the Map. environment.put(EnvHelp.DEFAULT_ORB,resolveOrb(environment)); } String path = directoryURL.getURLPath(); if (path.startsWith("/jndi/")) return findRMIServerJNDI(path.substring(6), environment, isIiop); else if (path.startsWith("/stub/")) return findRMIServerJRMP(path.substring(6), environment, isIiop); else if (path.startsWith("/ior/")) return findRMIServerIIOP(path.substring(5), environment, isIiop); 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.: *
  • rmi://registry-host:port/rmi-stub-name
  • *
  • or iiop://cosnaming-host:port/iiop-stub-name
  • *
  • or ldap://ldap-host:port/java-container-dn
  • *
* @param env the environment Map passed to the connector. * @param isIiop true if the stub is expected to be an IIOP stub. * @return The retrieved RMIServer stub. * @exception NamingException if the stub couldn't be found. **/ private RMIServer findRMIServerJNDI(String jndiURL, Map env, boolean isIiop) throws NamingException { InitialContext ctx = new InitialContext(EnvHelp.mapToHashtable(env)); Object objref = ctx.lookup(jndiURL); ctx.close(); if (isIiop) return narrowIIOPServer(objref); else return narrowJRMPServer(objref); } private static RMIServer narrowJRMPServer(Object objref) { return (RMIServer) objref; } private static RMIServer narrowIIOPServer(Object objref) { try { return (RMIServer) PortableRemoteObject.narrow(objref, RMIServer.class); } catch (ClassCastException e) { if (logger.traceOn()) logger.trace("narrowIIOPServer","Failed to narrow objref=" + objref + ": " + e); if (logger.debugOn()) logger.debug("narrowIIOPServer",e); return null; } } private RMIServer findRMIServerIIOP(String ior, Map env, boolean isIiop) { // could forbid "rmi:" URL here -- but do we need to? final org.omg.CORBA.ORB orb = (org.omg.CORBA.ORB) env.get(EnvHelp.DEFAULT_ORB); final Object stub = orb.string_to_object(ior); return (RMIServer) PortableRemoteObject.narrow(stub, RMIServer.class); } private RMIServer findRMIServerJRMP(String base64, Map env, boolean isIiop) throws IOException { // could forbid "iiop:" URL here -- but do we need to? 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) PortableRemoteObject.narrow(stub, RMIServer.class); } private static final class ObjectInputStreamWithLoader extends ObjectInputStream { ObjectInputStreamWithLoader(InputStream in, ClassLoader cl) throws IOException { super(in); this.loader = cl; } protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException { return Class.forName(classDesc.getName(), false, loader); } private final ClassLoader loader; } /* 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 com.sun.jmx.remote.internal; import java.lang.reflect.Method; import java.rmi.Remote; import java.rmi.server.RemoteRef; import com.sun.jmx.remote.internal.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 rmiConnectionImplStubClassName = RMIConnection.class.getName() + "Impl_Stub"; private static final Class rmiConnectionImplStubClass; private static final String pRefClassName = "com.sun.jmx.remote.internal.PRef"; private static final Constructor proxyRefConstructor; static { final String pRefByteCodeString = "\312\376\272\276\0\0\0.\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/RemoteRef;"+ ")V\1\0\4Code\1\0\6invoke\1\0S(Ljava/rmi/Remote;Ljava/lang/reflec"+ "t/Method;[Ljava/lang/Object;J)Ljava/lang/Object;\1\0\12Exception"+ "s\7\0\23\14\0\6\0\7\14\0\24\0\25\7\0\26\14\0\11\0\12\1\0\40com/"+ "sun/jmx/remote/internal/PRef\1\0$com/sun/jmx/remote/internal/Pr"+ "oxyRef\1\0\23java/lang/Exception\1\0\3ref\1\0\33Ljava/rmi/serve"+ "r/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 Object run() throws Exception { Class thisClass = RMIConnector.class; ClassLoader thisLoader = thisClass.getClassLoader(); ProtectionDomain thisProtectionDomain = thisClass.getProtectionDomain(); String[] otherClassNames = {ProxyRef.class.getName()}; ClassLoader cl = new NoCallStackClassLoader(pRefClassName, pRefByteCode, otherClassNames, thisLoader, thisProtectionDomain); Class c = cl.loadClass(pRefClassName); return c.getConstructor(new Class[] {RemoteRef.class}); } }; Class stubClass; Constructor constr; try { stubClass = Class.forName(rmiConnectionImplStubClassName); constr = (Constructor) AccessController.doPrivileged(action); } catch (Exception e) { logger.error("", "Failed to initialize proxy reference consructor "+ "for " + rmiConnectionImplStubClassName + ": " + e); logger.debug("",e); stubClass = null; constr = null; } rmiConnectionImplStubClass = stubClass; proxyRefConstructor = constr; } 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 Class[] constrTypes = {RemoteRef.class}; final Constructor rmiConnectionImplStubConstructor = rmiConnectionImplStubClass.getConstructor(constrTypes); Object[] args = {proxyRef}; RMIConnection proxyStub = (RMIConnection) rmiConnectionImplStubConstructor.newInstance(args); return proxyStub; } /* The following code performs a similar trick for RMI/IIOP to the one described above for RMI/JRMP. Unlike JRMP, though, we can't easily insert an object between the RMIConnection stub and the RMI/IIOP deserialization code, as explained below. A method in an RMI/IIOP stub does the following. It makes an org.omg.CORBA_2_3.portable.OutputStream for each request, and writes the parameters to it. Then it calls _invoke(OutputStream) which it inherits from CORBA's ObjectImpl. That returns an org.omg.CORBA_2_3.portable.InputStream. The return value is read from this InputStream. So the stack during deserialization looks like this: MBeanServerConnection.getAttribute (or whatever) -> _RMIConnection_Stub.getAttribute -> Util.readAny (a CORBA method) -> InputStream.read_any -> internal CORBA stuff What we would have *liked* to have done would be the same thing as for RMI/JRMP. We create a "ProxyDelegate" that is an org.omg.CORBA.portable.Delegate that simply forwards every operation to the real original Delegate from the RMIConnection stub, except that the InputStream returned by _invoke is wrapped by a "ProxyInputStream" that is loaded by our NoCallStackClassLoader. Unfortunately, this doesn't work, at least with Sun's J2SE 1.4.2, because the CORBA code is not designed to allow you to change Delegates arbitrarily. You get a ClassCastException from code that expects the Delegate to implement an internal interface. So instead we do the following. We create a subclass of the stub that overrides the _invoke method so as to wrap the returned InputStream in a ProxyInputStream. We create a subclass of ProxyInputStream using the NoCallStackClassLoader and override its read_any and read_value(Class) methods. (These are the only methods called during deserialization of MBeanServerConnection return values.) We extract the Delegate from the original stub and insert it into our subclass stub, and away we go. The state of a stub consists solely of its Delegate. We also need to catch ApplicationException, which will encode any exceptions declared in the throws clause of the called method. Its InputStream needs to be wrapped in a ProxyInputSteam too. We override _releaseReply in the stub subclass so that it replaces a ProxyInputStream argument with the original InputStream. This avoids problems if the implementation of _releaseReply ends up casting this InputStream to an implementation-specific interface (which in Sun's J2SE 5 it does). It is not strictly necessary for the stub subclass to be loaded by a NoCallStackClassLoader, since the call-stack search stops at the ProxyInputStream subclass. However, it is convenient for two reasons. One is that it means that the ProxyInputStream subclass can be accessed directly, without using reflection. The other is that it avoids build problems, since usually stubs are created after other classes are compiled, so we can't access them from this class without, again, using reflection. The strings below encode the following two Java classes, compiled using J2SE 1.4.2 with javac -g:none. package com.sun.jmx.remote.internal; import org.omg.stub.javax.management.remote.rmi._RMIConnection_Stub; import org.omg.CORBA.portable.ApplicationException; import org.omg.CORBA.portable.InputStream; import org.omg.CORBA.portable.OutputStream; import org.omg.CORBA.portable.RemarshalException; public class ProxyStub extends _RMIConnection_Stub { public InputStream _invoke(OutputStream out) throws ApplicationException, RemarshalException { try { return new PInputStream(super._invoke(out)); } catch (ApplicationException e) { InputStream pis = new PInputStream(e.getInputStream()); throw new ApplicationException(e.getId(), pis); } } public void _releaseReply(InputStream in) { PInputStream pis = (PInputStream) in; super._releaseReply(pis.getProxiedInputStream()); } } package com.sun.jmx.remote.internal; public class PInputStream extends ProxyInputStream { public PInputStream(org.omg.CORBA.portable.InputStream in) { super(in); } public org.omg.CORBA.Any read_any() { return in.read_any(); } public java.io.Serializable read_value(Class clz) { return narrow().read_value(clz); } } */ private static final String iiopConnectionStubClassName = "org.omg.stub.javax.management.remote.rmi._RMIConnection_Stub"; private static final String proxyStubClassName = "com.sun.jmx.remote.internal.ProxyStub"; private static final String pInputStreamClassName = "com.sun.jmx.remote.internal.PInputStream"; private static final Class proxyStubClass; static { final String proxyStubByteCodeString = "\312\376\272\276\0\0\0.\0)\12\0\14\0\26\7\0\27\12\0\14\0\30\12"+ "\0\2\0\31\7\0\32\12\0\5\0\33\12\0\5\0\34\12\0\5\0\35\12\0\2\0"+ "\36\12\0\14\0\37\7\0\40\7\0!\1\0\6\1\0\3()V\1\0\4Code\1"+ "\0\7_invoke\1\0K(Lorg/omg/CORBA/portable/OutputStream;)Lorg/o"+ "mg/CORBA/portable/InputStream;\1\0\12Exceptions\7\0\"\1\0\15_"+ "releaseReply\1\0'(Lorg/omg/CORBA/portable/InputStream;)V\14\0"+ "\15\0\16\1\0(com/sun/jmx/remote/internal/PInputStream\14\0\20"+ "\0\21\14\0\15\0\25\1\0+org/omg/CORBA/portable/ApplicationExce"+ "ption\14\0#\0$\14\0%\0&\14\0\15\0'\14\0(\0$\14\0\24\0\25\1\0%"+ "com/sun/jmx/remote/internal/ProxyStub\1\0\1\0'(L"+ "org/omg/CORBA/portable/InputStream;)V\1\0\4Code\1\0\10read_an"+ "y\1\0\25()Lorg/omg/CORBA/Any;\1\0\12read_value\1\0)(Ljava/lan"+ "g/Class;)Ljava/io/Serializable;\14\0\10\0\11\14\0\30\0\31\7\0"+ "\32\14\0\13\0\14\14\0\33\0\34\7\0\35\14\0\15\0\16\1\0(com/sun"+ "/jmx/remote/internal/PInputStream\1\0,com/sun/jmx/remote/inte"+ "rnal/ProxyInputStream\1\0\2in\1\0$Lorg/omg/CORBA/portable/Inp"+ "utStream;\1\0\"org/omg/CORBA/portable/InputStream\1\0\6narrow"+ "\1\0*()Lorg/omg/CORBA_2_3/portable/InputStream;\1\0&org/omg/C"+ "ORBA_2_3/portable/InputStream\0!\0\6\0\7\0\0\0\0\0\3\0\1\0\10"+ "\0\11\0\1\0\12\0\0\0\22\0\2\0\2\0\0\0\6*+\267\0\1\261\0\0\0\0"+ "\0\1\0\13\0\14\0\1\0\12\0\0\0\24\0\1\0\1\0\0\0\10*\264\0\2\266"+ "\0\3\260\0\0\0\0\0\1\0\15\0\16\0\1\0\12\0\0\0\25\0\2\0\2\0\0\0"+ "\11*\266\0\4+\266\0\5\260\0\0\0\0\0\0"; final byte[] proxyStubByteCode = NoCallStackClassLoader.stringToBytes(proxyStubByteCodeString); final byte[] pInputStreamByteCode = NoCallStackClassLoader.stringToBytes(pInputStreamByteCodeString); final String[] classNames={proxyStubClassName, pInputStreamClassName}; final byte[][] byteCodes = {proxyStubByteCode, pInputStreamByteCode}; final String[] otherClassNames = { iiopConnectionStubClassName, ProxyInputStream.class.getName(), }; PrivilegedExceptionAction action = new PrivilegedExceptionAction() { public Object run() throws Exception { Class thisClass = RMIConnector.class; ClassLoader thisLoader = thisClass.getClassLoader(); ProtectionDomain thisProtectionDomain = thisClass.getProtectionDomain(); ClassLoader cl = new NoCallStackClassLoader(classNames, byteCodes, otherClassNames, thisLoader, thisProtectionDomain); return cl.loadClass(proxyStubClassName); } }; Class stubClass; try { stubClass = (Class) AccessController.doPrivileged(action); } catch (Exception e) { logger.error("", "Unexpected exception making shadow IIOP stub class: "+e); logger.debug("",e); stubClass = null; } proxyStubClass = stubClass; } private static RMIConnection shadowIiopStub(Stub stub) throws InstantiationException, IllegalAccessException { Stub proxyStub = (Stub) proxyStubClass.newInstance(); proxyStub._set_delegate(stub._get_delegate()); return (RMIConnection) proxyStub; } private static RMIConnection getConnection(RMIServer server, Object credentials) throws IOException { RMIConnection c = server.newClient(credentials); try { if (c.getClass() == rmiConnectionImplStubClass) return shadowJrmpStub((RemoteObject) c); if (c.getClass().getName().equals(iiopConnectionStubClassName)) return shadowIiopStub((Stub) 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. * * @throw 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 Object run() { t.setContextClassLoader(defaultClassLoader); return null; } }); return old; } private void popDefaultClassLoader(final ClassLoader old) { AccessController.doPrivileged(new PrivilegedAction() { public Object 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 long clientNotifID = 0; private transient WeakHashMap rmbscMap; 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 WeakReference orb = null; private static final ObjectName delegateName; static { try { delegateName = new ObjectName("JMImplementation:type=MBeanServerDelegate"); } catch (MalformedObjectNameException e) { Error error = new Error("Can't initialize delegateName"); EnvHelp.initCause(error, e); throw error; } } // 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); } }