blob: 59663a466d5e72ed90562afdd83376a451b755a9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.mosgi.jmx.agent.mx4j.server;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.BadAttributeValueExpException;
import javax.management.BadBinaryOpValueExpException;
import javax.management.BadStringOperationException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidApplicationException;
import javax.management.InvalidAttributeValueException;
import javax.management.JMRuntimeException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanPermission;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.MBeanServerPermission;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.RuntimeErrorException;
import javax.management.RuntimeOperationsException;
import javax.management.StandardMBean;
import javax.management.loading.ClassLoaderRepository;
import javax.management.loading.PrivateClassLoader;
import org.apache.felix.mosgi.jmx.agent.mx4j.ImplementationException;
import org.apache.felix.mosgi.jmx.agent.mx4j.MX4JSystemKeys;
import org.apache.felix.mosgi.jmx.agent.mx4j.loading.ClassLoaderObjectInputStream;
import org.apache.felix.mosgi.jmx.agent.mx4j.log.Log;
import org.apache.felix.mosgi.jmx.agent.mx4j.log.Logger;
import org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor.InvokerMBeanServerInterceptor;
import org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor.MBeanServerInterceptor;
import org.apache.felix.mosgi.jmx.agent.mx4j.server.interceptor.MBeanServerInterceptorConfigurator;
import org.apache.felix.mosgi.jmx.agent.mx4j.util.Utils;
/**
* The MX4J MBeanServer implementation. <br>
* The MBeanServer accomplishes these roles:
* <ul>
* <li> Returns information about the Agent
* <li> Acts as a repository for MBeans
* <li> Acts as an invoker, on behalf of the user, on MBeans
* </ul>
* <br>
* The repository function is delegated to instances of {@link MBeanRepository} classes.
* This class acts as a factory for MBeanRepository instances, that can be controlled via the system property
* {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_REPOSITORY} to the qualified name of the implementation class. <br>
*
* This class also acts as an invoker on MBeans. The architecture is interceptor-based, that is whenever you call
* from a client an MBeanServer method that will end up to call the MBean instance, the call is dispatched to
* the interceptor chain and eventually to the MBean. <br>
* The interceptors are configurable via the MBean {@link MBeanServerInterceptorConfigurator}.
* When the call is about to arrive to the MBean instance, the last interceptor dispatches the call depending on
* the MBean type: if the MBean is a dynamic MBean, the call is dispatched directly; if the MBean is a standard
* MBean an {@link MBeanInvoker} is delegated to invoke on the MBean instance.
*
* @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a>
* @version $Revision: 1.3 $
*/
public class MX4JMBeanServer implements MBeanServer, java.io.Serializable
{
private String defaultDomain;
private MBeanRepository mbeanRepository;
private MBeanServerDelegate delegate;
private ObjectName delegateName;
private MBeanIntrospector introspector;
private MBeanServerInterceptorConfigurator invoker;
private static long notifications;
private ModifiableClassLoaderRepository classLoaderRepository;
private Map domains = new HashMap();
private static final String[] EMPTY_PARAMS = new String[0];
private static final Object[] EMPTY_ARGS = new Object[0];
/**
* Create a new MBeanServer implementation with the specified default domain.
* If the default domain is null, then the empty string is assumed.
*
* @param defaultDomain The default domain to be used
* @throws SecurityException if access is not granted to create an MBeanServer instance
*/
public MX4JMBeanServer(String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate)
{
Logger logger = getLogger();
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Creating MBeanServer instance...");
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Checking permission to create MBeanServer...");
sm.checkPermission(new MBeanServerPermission("newMBeanServer"));
}
if (defaultDomain == null) defaultDomain = "";
this.defaultDomain = defaultDomain;
if (delegate==null) throw new JMRuntimeException("Delegate can't be null");
this.delegate = delegate;
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("MBeanServer default domain is: '" + this.defaultDomain + "'");
mbeanRepository = createMBeanRepository();
classLoaderRepository = createClassLoaderRepository();
// JMX 1.2 requires the CLR to have as first entry the classloader of this class
classLoaderRepository.addClassLoader(getClass().getClassLoader());
introspector = new MBeanIntrospector();
// This is the official name of the delegate, it is used as a source for MBeanServerNotifications
try
{
delegateName = new ObjectName("JMImplementation", "type", "MBeanServerDelegate");
}
catch (MalformedObjectNameException ignored)
{
}
try
{
ObjectName invokerName = new ObjectName(MBeanServerInterceptorConfigurator.OBJECT_NAME);
invoker = new MBeanServerInterceptorConfigurator(this);
// ContextClassLoaderMBeanServerInterceptor ccl = new ContextClassLoaderMBeanServerInterceptor();
// NotificationListenerMBeanServerInterceptor notif = new NotificationListenerMBeanServerInterceptor();
// SecurityMBeanServerInterceptor sec = new SecurityMBeanServerInterceptor();
InvokerMBeanServerInterceptor inv = new InvokerMBeanServerInterceptor(outer==null ? this : outer);
// invoker.addPreInterceptor(ccl);
// invoker.addPreInterceptor(notif);
// invoker.addPostInterceptor(sec);
invoker.addPostInterceptor(inv);
invoker.start();
// The interceptor stack is in place, register the configurator and all interceptors
privilegedRegisterMBean(invoker, invokerName);
// ObjectName cclName = new ObjectName("JMImplementation", "interceptor", "contextclassloader");
// ObjectName notifName = new ObjectName("JMImplementation", "interceptor", "notificationwrapper");
// ObjectName secName = new ObjectName("JMImplementation", "interceptor", "security");
ObjectName invName = new ObjectName("JMImplementation", "interceptor", "invoker");
// privilegedRegisterMBean(ccl, cclName);
// privilegedRegisterMBean(notif, notifName);
// privilegedRegisterMBean(sec, secName);
privilegedRegisterMBean(inv, invName);
}
catch (Exception x)
{
logger.error("MBeanServerInterceptorConfigurator cannot be registered", x);
throw new ImplementationException();
}
// Now register the delegate
try
{
privilegedRegisterMBean(delegate, delegateName);
}
catch (Exception x)
{
logger.error("MBeanServerDelegate cannot be registered", x);
throw new ImplementationException(x.toString());
}
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("MBeanServer instance created successfully");
}
/**
* Returns the ClassLoaderRepository for this MBeanServer.
* When first the ClassLoaderRepository is created in the constructor, the system property
* {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY} is tested;
* if it is non-null and defines a subclass of
* {@link ModifiableClassLoaderRepository}, then that class is used instead of the default one.
*/
public ClassLoaderRepository getClassLoaderRepository()
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
sm.checkPermission(new MBeanPermission("-#-[-]", "getClassLoaderRepository"));
}
return getModifiableClassLoaderRepository();
}
private ModifiableClassLoaderRepository getModifiableClassLoaderRepository()
{
return classLoaderRepository;
}
public ClassLoader getClassLoader(ObjectName name) throws InstanceNotFoundException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
name = secureObjectName(name);
if (name == null)
{
sm.checkPermission(new MBeanPermission("-#-[-]", "getClassLoader"));
}
else
{
MBeanMetaData metadata = findMBeanMetaData(name);
sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", name, "getClassLoader"));
}
}
return getClassLoaderImpl(name);
}
public ClassLoader getClassLoaderFor(ObjectName name) throws InstanceNotFoundException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
name = secureObjectName(name);
}
// If name is null, I get InstanceNotFoundException
MBeanMetaData metadata = findMBeanMetaData(name);
if (sm != null)
{
sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", name, "getClassLoaderFor"));
}
return metadata.mbean.getClass().getClassLoader();
}
/**
* Returns the MBean classloader corrispondent to the given ObjectName.
* If <code>name</code> is null, the classloader of this class is returned.
*/
private ClassLoader getClassLoaderImpl(ObjectName name) throws InstanceNotFoundException
{
if (name == null)
{
return getClass().getClassLoader();
}
else
{
MBeanMetaData metadata = findMBeanMetaData(name);
if (metadata.mbean instanceof ClassLoader)
{
return (ClassLoader)metadata.mbean;
}
else
{
throw new InstanceNotFoundException(name.getCanonicalName());
}
}
}
public ObjectInputStream deserialize(String className, ObjectName loaderName, byte[] bytes)
throws InstanceNotFoundException, OperationsException, ReflectionException
{
if (className == null || className.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid class name '" + className + "'"));
}
ClassLoader cl = getClassLoader(loaderName);
try
{
Class cls = cl.loadClass(className);
return deserializeImpl(cls.getClassLoader(), bytes);
}
catch (ClassNotFoundException x)
{
throw new ReflectionException(x);
}
}
public ObjectInputStream deserialize(String className, byte[] bytes)
throws OperationsException, ReflectionException
{
if (className == null || className.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid class name '" + className + "'"));
}
// Find the classloader that can load the given className using the ClassLoaderRepository
try
{
Class cls = getClassLoaderRepository().loadClass(className);
return deserializeImpl(cls.getClassLoader(), bytes);
}
catch (ClassNotFoundException x)
{
throw new ReflectionException(x);
}
}
public ObjectInputStream deserialize(ObjectName objectName, byte[] bytes)
throws InstanceNotFoundException, OperationsException
{
ClassLoader cl = getClassLoaderFor(objectName);
return deserializeImpl(cl, bytes);
}
/**
* Deserializes the given bytes using the specified classloader.
*/
private ObjectInputStream deserializeImpl(ClassLoader classloader, byte[] bytes) throws OperationsException
{
if (bytes == null || bytes.length == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid byte array " + bytes));
}
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
try
{
return new ClassLoaderObjectInputStream(bais, classloader);
}
catch (IOException x)
{
throw new OperationsException(x.toString());
}
}
private MBeanServerInterceptor getHeadInterceptor()
{
MBeanServerInterceptor head = invoker.getHeadInterceptor();
if (head == null) throw new IllegalStateException("No MBeanServer interceptor, probably the configurator has been stopped");
return head;
}
private Logger getLogger()
{
return Log.getLogger(getClass().getName());
}
/**
* Creates a new repository for MBeans.
* The system property {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_REPOSITORY} is tested
* for a full qualified name of a class implementing the {@link MBeanRepository} interface.
* In case the system property is not defined or the class is not loadable or instantiable, a default
* implementation is returned.
*/
private MBeanRepository createMBeanRepository()
{
Logger logger = getLogger();
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Checking for system property " + MX4JSystemKeys.MX4J_MBEANSERVER_REPOSITORY);
String value = (String)AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
return System.getProperty(MX4JSystemKeys.MX4J_MBEANSERVER_REPOSITORY);
}
});
if (value != null)
{
if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Property found for custom MBeanServer registry; class is: " + value);
try
{
MBeanRepository registry = (MBeanRepository)Thread.currentThread().getContextClassLoader().loadClass(value).newInstance();
if (logger.isEnabledFor(Logger.TRACE))
{
logger.trace("Custom MBeanServer registry created successfully");
}
return registry;
}
catch (Exception x)
{
if (logger.isEnabledFor(Logger.TRACE))
{
logger.trace("Custom MBeanServer registry could not be created", x);
}
}
}
return new DefaultMBeanRepository();
}
/**
* Creates a new ClassLoaderRepository for ClassLoader MBeans.
* The system property {@link mx4j.MX4JSystemKeys#MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY}
* is tested for a full qualified name of a class
* extending the {@link ModifiableClassLoaderRepository} class.
* In case the system property is not defined or the class is not loadable or instantiable, a default
* implementation is returned.
*/
private ModifiableClassLoaderRepository createClassLoaderRepository()
{
Logger logger = getLogger();
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Checking for system property " + MX4JSystemKeys.MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY);
String value = (String)AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
return System.getProperty(MX4JSystemKeys.MX4J_MBEANSERVER_CLASSLOADER_REPOSITORY);
}
});
if (value != null)
{
if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Property found for custom ClassLoaderRepository; class is: " + value);
try
{
ModifiableClassLoaderRepository repository = (ModifiableClassLoaderRepository)Thread.currentThread().getContextClassLoader().loadClass(value).newInstance();
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Custom ClassLoaderRepository created successfully " + repository);
return repository;
}
catch (Exception x)
{
if (logger.isEnabledFor(Logger.TRACE)) logger.trace("Custom ClassLoaderRepository could not be created", x);
}
}
return new DefaultClassLoaderRepository();
}
/**
* Returns the repository of MBeans for this MBeanServer
*/
private MBeanRepository getMBeanRepository()
{
return mbeanRepository;
}
/**
* Looks up the metadata associated with the given ObjectName.
* @throws InstanceNotFoundException if the given ObjectName is not a registered MBean
*/
private MBeanMetaData findMBeanMetaData(ObjectName objectName) throws InstanceNotFoundException
{
MBeanMetaData metadata = null;
if (objectName != null)
{
objectName = normalizeObjectName(objectName);
MBeanRepository repository = getMBeanRepository();
synchronized (repository)
{
metadata = repository.get(objectName);
}
}
if (metadata == null)
{
throw new InstanceNotFoundException("MBeanServer cannot find MBean with ObjectName " + objectName);
}
return metadata;
}
public void addNotificationListener(ObjectName observed, ObjectName listener, NotificationFilter filter, Object handback)
throws InstanceNotFoundException
{
listener = secureObjectName(listener);
Object mbean = findMBeanMetaData(listener).mbean;
if (!(mbean instanceof NotificationListener))
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + listener + " is not a NotificationListener"));
}
addNotificationListener(observed, (NotificationListener)mbean, filter, handback);
}
public void addNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback)
throws InstanceNotFoundException
{
if (listener == null)
{
throw new RuntimeOperationsException(new IllegalArgumentException("NotificationListener cannot be null"));
}
observed = secureObjectName(observed);
MBeanMetaData metadata = findMBeanMetaData(observed);
Object mbean = metadata.mbean;
if (!(mbean instanceof NotificationBroadcaster))
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + observed + " is not a NotificationBroadcaster"));
}
addNotificationListenerImpl(metadata, listener, filter, handback);
}
private void addNotificationListenerImpl(MBeanMetaData metadata, NotificationListener listener, NotificationFilter filter, Object handback)
{
getHeadInterceptor().addNotificationListener(metadata, listener, filter, handback);
}
public void removeNotificationListener(ObjectName observed, ObjectName listener)
throws InstanceNotFoundException, ListenerNotFoundException
{
listener = secureObjectName(listener);
Object mbean = findMBeanMetaData(listener).mbean;
if (!(mbean instanceof NotificationListener))
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + listener + " is not a NotificationListener"));
}
removeNotificationListener(observed, (NotificationListener)mbean);
}
public void removeNotificationListener(ObjectName observed, NotificationListener listener)
throws InstanceNotFoundException, ListenerNotFoundException
{
if (listener == null)
{
throw new ListenerNotFoundException("NotificationListener cannot be null");
}
observed = secureObjectName(observed);
MBeanMetaData metadata = findMBeanMetaData(observed);
Object mbean = metadata.mbean;
if (!(mbean instanceof NotificationBroadcaster))
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + observed + " is not a NotificationBroadcaster"));
}
removeNotificationListenerImpl(metadata, listener);
}
public void removeNotificationListener(ObjectName observed, ObjectName listener, NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException
{
listener = secureObjectName(listener);
Object mbean = findMBeanMetaData(listener).mbean;
if (!(mbean instanceof NotificationListener))
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + listener + " is not a NotificationListener"));
}
removeNotificationListener(observed, (NotificationListener)mbean, filter, handback);
}
public void removeNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException
{
if (listener == null)
{
throw new ListenerNotFoundException("NotificationListener cannot be null");
}
observed = secureObjectName(observed);
MBeanMetaData metadata = findMBeanMetaData(observed);
Object mbean = metadata.mbean;
if (!(mbean instanceof NotificationEmitter))
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean " + observed + " is not a NotificationEmitter"));
}
removeNotificationListenerImpl(metadata, listener, filter, handback);
}
private void removeNotificationListenerImpl(MBeanMetaData metadata, NotificationListener listener)
throws ListenerNotFoundException
{
getHeadInterceptor().removeNotificationListener(metadata, listener);
}
private void removeNotificationListenerImpl(MBeanMetaData metadata, NotificationListener listener, NotificationFilter filter, Object handback)
throws ListenerNotFoundException
{
getHeadInterceptor().removeNotificationListener(metadata, listener, filter, handback);
}
public Object instantiate(String className)
throws ReflectionException, MBeanException
{
return instantiate(className, null, null);
}
public Object instantiate(String className, Object[] args, String[] parameters)
throws ReflectionException, MBeanException
{
if (className == null || className.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Class name cannot be null or empty"));
}
try
{
Class cls = getModifiableClassLoaderRepository().loadClass(className);
return instantiateImpl(className, cls.getClassLoader(), null, parameters, args).mbean;
}
catch (ClassNotFoundException x)
{
throw new ReflectionException(x);
}
}
public Object instantiate(String className, ObjectName loaderName)
throws ReflectionException, MBeanException, InstanceNotFoundException
{
return instantiate(className, loaderName, null, null);
}
public Object instantiate(String className, ObjectName loaderName, Object[] args, String[] parameters)
throws ReflectionException, MBeanException, InstanceNotFoundException
{
if (className == null || className.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Class name cannot be null or empty"));
}
// loaderName can be null: means using this class' ClassLoader
loaderName = secureObjectName(loaderName);
if (loaderName != null && loaderName.isPattern())
{
throw new RuntimeOperationsException(new IllegalArgumentException("ObjectName for the ClassLoader cannot be a pattern ObjectName: " + loaderName));
}
ClassLoader cl = getClassLoaderImpl(loaderName);
return instantiateImpl(className, cl, null, parameters, args).mbean;
}
private MBeanMetaData instantiateImpl(String className, ClassLoader classloader, ObjectName name, String[] params, Object[] args)
throws ReflectionException, MBeanException
{
if (params == null) params = EMPTY_PARAMS;
if (args == null) args = EMPTY_ARGS;
MBeanMetaData metadata = createMBeanMetaData();
metadata.classloader = classloader;
metadata.name = secureObjectName(name);
getHeadInterceptor().instantiate(metadata, className, params, args);
return metadata;
}
public ObjectInstance createMBean(String className, ObjectName objectName)
throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException
{
return createMBean(className, objectName, null, null);
}
public ObjectInstance createMBean(String className, ObjectName objectName, Object[] args, String[] parameters)
throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException
{
try
{
Class cls = getModifiableClassLoaderRepository().loadClass(className);
MBeanMetaData metadata = instantiateImpl(className, cls.getClassLoader(), objectName, parameters, args);
registerImpl(metadata, false);
return metadata.instance;
}
catch (ClassNotFoundException x)
{
throw new ReflectionException(x);
}
}
public ObjectInstance createMBean(String className, ObjectName objectName, ObjectName loaderName)
throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException
{
return createMBean(className, objectName, loaderName, null, null);
}
public ObjectInstance createMBean(String className, ObjectName objectName, ObjectName loaderName, Object[] args, String[] parameters)
throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException
{
loaderName = secureObjectName(loaderName);
ClassLoader cl = getClassLoaderImpl(loaderName);
MBeanMetaData metadata = instantiateImpl(className, cl, objectName, parameters, args);
registerImpl(metadata, false);
return metadata.instance;
}
public ObjectInstance registerMBean(Object mbean, ObjectName objectName)
throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException
{
return registerMBeanImpl(mbean, objectName, false);
}
private ObjectInstance registerMBeanImpl(Object mbean, ObjectName objectName, boolean privileged)
throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException
{
if (mbean == null)
{
throw new RuntimeOperationsException(new IllegalArgumentException("MBean instance cannot be null"));
}
MBeanMetaData metadata = createMBeanMetaData();
metadata.mbean = mbean;
metadata.classloader = mbean.getClass().getClassLoader();
metadata.name = secureObjectName(objectName);
registerImpl(metadata, privileged);
return metadata.instance;
}
/**
* Returns a new instance of the metadata class used to store MBean information.
*/
private MBeanMetaData createMBeanMetaData()
{
return new MBeanMetaData();
}
/**
* This method is called only to register implementation MBeans from the constructor.
* Since to create an instance of this class already requires a permission, here we hide the registration
* of implementation MBeans to the client that thus need no further permissions.
*/
private ObjectInstance privilegedRegisterMBean(final Object mbean, final ObjectName name)
throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException
{
try
{
return (ObjectInstance)AccessController.doPrivileged(new PrivilegedExceptionAction()
{
public Object run() throws Exception
{
return registerMBeanImpl(mbean, name, true);
}
});
}
catch (PrivilegedActionException x)
{
Exception xx = x.getException();
if (xx instanceof InstanceAlreadyExistsException)
throw (InstanceAlreadyExistsException)xx;
else if (xx instanceof MBeanRegistrationException)
throw (MBeanRegistrationException)xx;
else if (xx instanceof NotCompliantMBeanException)
throw (NotCompliantMBeanException)xx;
else
throw new MBeanRegistrationException(xx);
}
}
private void registerImpl(MBeanMetaData metadata, boolean privileged) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException
{
introspector.introspect(metadata);
if (!introspector.isMBeanCompliant(metadata)) throw new NotCompliantMBeanException("MBean is not compliant");
MBeanServerInterceptor head = getHeadInterceptor();
try
{
// With this call, the MBean implementor can replace the ObjectName with a subclass that is not secure, secure it again
head.registration(metadata, MBeanServerInterceptor.PRE_REGISTER);
metadata.name = secureObjectName(metadata.name);
metadata.instance = new ObjectInstance(metadata.name, metadata.info.getClassName());
register(metadata, privileged);
head.registration(metadata, MBeanServerInterceptor.POST_REGISTER_TRUE);
}
catch (Throwable x)
{
try
{
head.registration(metadata, MBeanServerInterceptor.POST_REGISTER_FALSE);
}
catch (MBeanRegistrationException ignored)
{/* Ignore this one to rethrow the other one */
}
if (x instanceof SecurityException)
{
throw (SecurityException)x;
}
else if (x instanceof InstanceAlreadyExistsException)
{
throw (InstanceAlreadyExistsException)x;
}
else if (x instanceof MBeanRegistrationException)
{
throw (MBeanRegistrationException)x;
}
else if (x instanceof RuntimeOperationsException)
{
throw (RuntimeOperationsException)x;
}
else if (x instanceof JMRuntimeException)
{
throw (JMRuntimeException)x;
}
else if (x instanceof Exception)
{
throw new MBeanRegistrationException((Exception)x);
}
else if (x instanceof Error)
{
throw new MBeanRegistrationException(new RuntimeErrorException((Error)x));
}
else
{
throw new ImplementationException();
}
}
if (metadata.mbean instanceof ClassLoader && !(metadata.mbean instanceof PrivateClassLoader))
{
ClassLoader cl = (ClassLoader)metadata.mbean;
getModifiableClassLoaderRepository().addClassLoader(cl);
}
}
private void register(MBeanMetaData metadata, boolean privileged) throws InstanceAlreadyExistsException
{
metadata.name = normalizeObjectName(metadata.name);
ObjectName objectName = metadata.name;
if (objectName == null || objectName.isPattern())
{
throw new RuntimeOperationsException(new IllegalArgumentException("ObjectName cannot be null or a pattern ObjectName"));
}
if (objectName.getDomain().equals("JMImplementation") && !privileged)
{
throw new JMRuntimeException("Domain 'JMImplementation' is reserved for the JMX Agent");
}
MBeanRepository repository = getMBeanRepository();
synchronized (repository)
{
if (repository.get(objectName) != null) throw new InstanceAlreadyExistsException(objectName.toString());
repository.put(objectName, metadata);
}
addDomain(objectName.getDomain());
notify(objectName, MBeanServerNotification.REGISTRATION_NOTIFICATION);
}
private void notify(ObjectName objectName, String notificationType)
{
long sequenceNumber = 0;
synchronized (MX4JMBeanServer.class)
{
sequenceNumber = notifications;
++notifications;
}
delegate.sendNotification(new MBeanServerNotification(notificationType, delegateName, sequenceNumber, objectName));
}
private void addDomain(String domain)
{
synchronized (domains)
{
Integer count = (Integer)domains.get(domain);
if (count == null)
domains.put(domain, new Integer(1));
else
domains.put(domain, new Integer(count.intValue() + 1));
}
}
private void removeDomain(String domain)
{
synchronized (domains)
{
Integer count = (Integer)domains.get(domain);
if (count == null) throw new ImplementationException();
if (count.intValue() < 2)
domains.remove(domain);
else
domains.put(domain, new Integer(count.intValue() - 1));
}
}
public void unregisterMBean(ObjectName objectName)
throws InstanceNotFoundException, MBeanRegistrationException
{
objectName = secureObjectName(objectName);
if (objectName == null || objectName.isPattern())
{
throw new RuntimeOperationsException(new IllegalArgumentException("ObjectName cannot be null or a pattern ObjectName"));
}
if (objectName.getDomain().equals("JMImplementation"))
{
throw new RuntimeOperationsException(new IllegalArgumentException("Domain 'JMImplementation' is reserved for the JMX Agent"));
}
MBeanMetaData metadata = findMBeanMetaData(objectName);
try
{
MBeanServerInterceptor head = getHeadInterceptor();
head.registration(metadata, MBeanServerInterceptor.PRE_DEREGISTER);
unregister(metadata);
getHeadInterceptor().registration(metadata, MBeanServerInterceptor.POST_DEREGISTER);
if (metadata.mbean instanceof ClassLoader && !(metadata.mbean instanceof PrivateClassLoader))
{
getModifiableClassLoaderRepository().removeClassLoader((ClassLoader)metadata.mbean);
}
}
catch (MBeanRegistrationException x)
{
throw x;
}
catch (SecurityException x)
{
throw x;
}
catch (Exception x)
{
throw new MBeanRegistrationException(x);
}
catch (Error x)
{
throw new MBeanRegistrationException(new RuntimeErrorException(x));
}
}
private void unregister(MBeanMetaData metadata)
{
ObjectName objectName = metadata.name;
MBeanRepository repository = getMBeanRepository();
synchronized (repository)
{
repository.remove(objectName);
}
removeDomain(objectName.getDomain());
notify(objectName, MBeanServerNotification.UNREGISTRATION_NOTIFICATION);
}
public Object getAttribute(ObjectName objectName, String attribute)
throws InstanceNotFoundException, MBeanException, AttributeNotFoundException, ReflectionException
{
if (attribute == null || attribute.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute"));
}
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
return getHeadInterceptor().getAttribute(metadata, attribute);
}
public void setAttribute(ObjectName objectName, Attribute attribute)
throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
{
if (attribute == null || attribute.getName().trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute"));
}
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
getHeadInterceptor().setAttribute(metadata, attribute);
}
public AttributeList getAttributes(ObjectName objectName, String[] attributes)
throws InstanceNotFoundException, ReflectionException
{
if (attributes == null || attributes.length == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute list"));
}
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
// Must check if the user has the right to call this method, regardless of the attributes
sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "getAttribute"));
}
return getHeadInterceptor().getAttributes(metadata, attributes);
}
public AttributeList setAttributes(ObjectName objectName, AttributeList attributes)
throws InstanceNotFoundException, ReflectionException
{
if (attributes == null)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid attribute list"));
}
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
// Must check if the user has the right to call this method, regardless of the attributes
sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "setAttribute"));
}
return getHeadInterceptor().setAttributes(metadata, attributes);
}
public Object invoke(ObjectName objectName, String methodName, Object[] args, String[] parameters)
throws InstanceNotFoundException, MBeanException, ReflectionException
{
if (methodName == null || methodName.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid operation name '" + methodName + "'"));
}
if (args == null) args = EMPTY_ARGS;
if (parameters == null) parameters = EMPTY_PARAMS;
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
return getHeadInterceptor().invoke(metadata, methodName, parameters, args);
}
public String getDefaultDomain()
{
return defaultDomain;
}
public String[] getDomains()
{
synchronized (domains)
{
Set keys = domains.keySet();
return (String[])keys.toArray(new String[keys.size()]);
}
}
public Integer getMBeanCount()
{
MBeanRepository repository = getMBeanRepository();
synchronized (repository)
{
return new Integer(repository.size());
}
}
public boolean isRegistered(ObjectName objectName)
{
try
{
return findMBeanMetaData(objectName) != null;
}
catch (InstanceNotFoundException x)
{
return false;
}
}
public MBeanInfo getMBeanInfo(ObjectName objectName)
throws InstanceNotFoundException, IntrospectionException, ReflectionException
{
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
MBeanInfo info = getHeadInterceptor().getMBeanInfo(metadata);
if (info == null) throw new JMRuntimeException("MBeanInfo returned for MBean " + objectName + " is null");
return info;
}
public ObjectInstance getObjectInstance(ObjectName objectName)
throws InstanceNotFoundException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
objectName = secureObjectName(objectName);
}
MBeanMetaData metadata = findMBeanMetaData(objectName);
if (sm != null)
{
sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "getObjectInstance"));
}
return metadata.instance;
}
public boolean isInstanceOf(ObjectName objectName, String className)
throws InstanceNotFoundException
{
if (className == null || className.trim().length() == 0)
{
throw new RuntimeOperationsException(new IllegalArgumentException("Invalid class name"));
}
objectName = secureObjectName(objectName);
MBeanMetaData metadata = findMBeanMetaData(objectName);
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
sm.checkPermission(new MBeanPermission(metadata.info.getClassName(), "-", objectName, "isInstanceOf"));
}
try
{
ClassLoader loader = metadata.classloader;
Class cls = null;
if (loader != null)
cls = loader.loadClass(className);
else
cls = Class.forName(className, false, null);
if (metadata.mbean instanceof StandardMBean)
{
Object impl = ((StandardMBean) metadata.mbean).getImplementation();
return cls.isInstance(impl);
}
else
{
return cls.isInstance(metadata.mbean);
}
}
catch (ClassNotFoundException x)
{
return false;
}
}
public Set queryMBeans(ObjectName patternName, QueryExp filter)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
patternName = secureObjectName(patternName);
// Must check if the user has the right to call this method,
// no matter which ObjectName has been passed.
sm.checkPermission(new MBeanPermission("-#-[-]", "queryMBeans"));
}
Set match = queryObjectNames(patternName, filter, true);
Set set = new HashSet();
for (Iterator i = match.iterator(); i.hasNext();)
{
ObjectName name = (ObjectName)i.next();
try
{
MBeanMetaData metadata = findMBeanMetaData(name);
set.add(metadata.instance);
}
catch (InstanceNotFoundException ignored)
{
// A concurrent thread removed the MBean after queryNames, ignore
}
}
return set;
}
public Set queryNames(ObjectName patternName, QueryExp filter)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
{
patternName = secureObjectName(patternName);
// Must check if the user has the right to call this method,
// no matter which ObjectName has been passed.
sm.checkPermission(new MBeanPermission("-#-[-]", "queryNames"));
}
return queryObjectNames(patternName, filter, false);
}
/**
* Utility method for queryNames and queryMBeans that returns a set of ObjectNames.
* It does 3 things:
* 1) filter the MBeans following the given ObjectName pattern
* 2) filter the MBeans following the permissions that client code has
* 3) filter the MBeans following the given QueryExp
* It is important that these 3 operations are done in this order
*/
private Set queryObjectNames(ObjectName patternName, QueryExp filter, boolean instances)
{
// First, retrieve the scope of the query: all mbeans matching the patternName
Set scope = findMBeansByPattern(patternName);
// Second, filter the scope by checking the caller's permissions
Set secureScope = filterMBeansBySecurity(scope, instances);
// Third, filter the scope using the given QueryExp
Set match = filterMBeansByQuery(secureScope, filter);
return match;
}
/**
* Returns a set of ObjectNames of the registered MBeans that match the given ObjectName pattern
*/
private Set findMBeansByPattern(ObjectName pattern)
{
if (pattern == null)
{
try
{
pattern = new ObjectName("*:*");
}
catch (MalformedObjectNameException ignored)
{
}
}
pattern = normalizeObjectName(pattern);
String patternDomain = pattern.getDomain();
Hashtable patternProps = pattern.getKeyPropertyList();
Set set = new HashSet();
// Clone the repository, we are faster than holding the lock while iterating
MBeanRepository repository = (MBeanRepository)getMBeanRepository().clone();
for (Iterator i = repository.iterator(); i.hasNext();)
{
MBeanMetaData metadata = (MBeanMetaData)i.next();
ObjectName name = metadata.name;
Hashtable props = name.getKeyPropertyList();
String domain = name.getDomain();
if (Utils.wildcardMatch(patternDomain, domain))
{
// Domain matches, now check properties
if (pattern.isPropertyPattern())
{
// A property pattern with no entries, can only be '*'
if (patternProps.size() == 0)
{
// User wants all properties
set.add(name);
}
else
{
// Loop on the properties of the pattern.
// If one is not found then the current ObjectName does not match
boolean found = true;
for (Iterator j = patternProps.entrySet().iterator(); j.hasNext();)
{
Map.Entry entry = (Map.Entry)j.next();
Object patternKey = entry.getKey();
Object patternValue = entry.getValue();
if (patternKey.equals("*"))
{
continue;
}
// Try to see if the current ObjectName contains this entry
if (!props.containsKey(patternKey))
{
// Not even the key is present
found = false;
break;
}
else
{
// The key is present, let's check if the values are equal
Object value = props.get(patternKey);
if (value == null && patternValue == null)
{
// Values are equal, go on with next pattern entry
continue;
}
if (value != null && value.equals(patternValue))
{
// Values are equal, go on with next pattern entry
continue;
}
// Here values are different
found = false;
break;
}
}
if (found) set.add(name);
}
}
else
{
if (props.entrySet().equals(patternProps.entrySet())) set.add(name);
}
}
}
return set;
}
/**
* Filters the given set of ObjectNames following the permission that client code has granted.
* Returns a set containing the allowed ObjectNames.
*/
private Set filterMBeansBySecurity(Set mbeans, boolean instances)
{
SecurityManager sm = System.getSecurityManager();
if (sm == null) return mbeans;
HashSet set = new HashSet();
for (Iterator i = mbeans.iterator(); i.hasNext();)
{
ObjectName name = (ObjectName)i.next();
try
{
MBeanMetaData metadata = findMBeanMetaData(name);
String className = metadata.info.getClassName();
sm.checkPermission(new MBeanPermission(className, "-", name, instances ? "queryMBeans" : "queryNames"));
set.add(name);
}
catch (InstanceNotFoundException ignored)
{
// A concurrent thread removed this MBean, continue
continue;
}
catch (SecurityException ignored)
{
// Don't add the name to the list, and go on.
}
}
return set;
}
/**
* Filters the given set of ObjectNames following the given QueryExp.
* Returns a set of ObjectNames that match the given QueryExp.
*/
private Set filterMBeansByQuery(Set scope, QueryExp filter)
{
if (filter == null) return scope;
Set set = new HashSet();
for (Iterator i = scope.iterator(); i.hasNext();)
{
ObjectName name = (ObjectName)i.next();
filter.setMBeanServer(this);
try
{
if (filter.apply(name)) set.add(name);
}
catch (BadStringOperationException ignored)
{
}
catch (BadBinaryOpValueExpException ignored)
{
}
catch (BadAttributeValueExpException x)
{
}
catch (InvalidApplicationException x)
{
}
catch (SecurityException x)
{
}
catch (Exception x)
{
// The 1.2 spec says Exceptions must not be propagated
}
}
return set;
}
/**
* Returns a normalized ObjectName from the given one.
* If an ObjectName is specified with the abbreviated notation for the default domain, that is ':key=value'
* this method returns an ObjectName whose domain is the default domain of this MBeanServer and with the same
* properties.
*/
private ObjectName normalizeObjectName(ObjectName name)
{
if (name == null) return null;
String defaultDomain = getDefaultDomain();
String domain = name.getDomain();
if (domain.length() == 0 && defaultDomain.length() > 0)
{
// The given object name specifies the abbreviated form to indicate the default domain,
// ie ':key=value', the empty string as domain. I must convert this abbreviated form
// to the full one, if the default domain of this mbeanserver is not the empty string as well
StringBuffer buffer = new StringBuffer(defaultDomain).append(":").append(name.getKeyPropertyListString());
if (name.isPropertyPattern())
{
if (name.getKeyPropertyList().size() > 0)
buffer.append(",*");
else
buffer.append("*");
}
try
{
name = new ObjectName(buffer.toString());
}
catch (MalformedObjectNameException ignored)
{
}
}
return name;
}
/**
* Returns an ObjectName instance even if the provided ObjectName is a subclass.
* This is done to avoid security holes: a nasty ObjectName subclass can bypass security checks.
*/
private ObjectName secureObjectName(ObjectName name)
{
// I cannot trust ObjectName, since a malicious user can send a subclass that overrides equals and hashcode
// to match another ObjectName for which it does not have permission, or returns different results from
// ObjectName.getCanonicalName() for different calls, so that passes the security checks but in fact will
// later refer to a different ObjectName for which it does not have permission.
if (name == null) return null;
return ObjectName.getInstance(name);
}
}