/*
* Copyright (c) 2002, 2010, 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.mbeanserver.Util;
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.ProxyRef;
import com.sun.jmx.remote.internal.IIOPHelper;
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.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.io.WriteAbortedException;
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.MarshalException;
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.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
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 sun.reflect.misc.ReflectUtil;
import sun.rmi.server.UnicastRef2;
import sun.rmi.transport.LiveRef;
/**
*
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;
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 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:
*
* @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.
**/
@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
//--------------------------------------------------------------------
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.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);
// 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);
try {
connection = getConnection(stub, credentials, checkStub);
} catch (java.rmi.RemoteException re) {
if (jmxServiceURL != null) {
final String pro = jmxServiceURL.getProtocol();
final String path = jmxServiceURL.getURLPath();
if ("rmi".equals(pro) &&
path.startsWith("/jndi/iiop:")) {
MalformedURLException mfe = new MalformedURLException(
"Protocol is rmi but JNDI scheme is iiop: " + jmxServiceURL);
mfe.initCause(re);
throw mfe;
}
}
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");
}
MBeanServerConnection rmbsc = rmbscMap.get(delegationSubject);
if (rmbsc != null) {
return rmbsc;
}
rmbsc = new RemoteMBeanServerConnection(delegationSubject);
rmbscMap.put(delegationSubject, rmbsc);
return rmbsc;
}
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 + ", params="
+ objects(params) + ", signature="
+ strings(signature));
final MarshalledObject