/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.mx.server.registry; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javax.management.Descriptor; import javax.management.DynamicMBean; import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanRegistration; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MBeanServerDelegate; import javax.management.MBeanServerNotification; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.RuntimeErrorException; import javax.management.RuntimeMBeanException; import javax.management.RuntimeOperationsException; import javax.management.loading.ClassLoaderRepository; import javax.management.modelmbean.ModelMBeanInfo; import javax.management.modelmbean.RequiredModelMBean; import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap; import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong; import org.jboss.classloading.spi.RealClassLoader; import org.jboss.logging.Logger; import org.jboss.mx.loading.LoaderRepository; import org.jboss.mx.metadata.MBeanCapability; import org.jboss.mx.modelmbean.ModelMBeanConstants; import org.jboss.mx.modelmbean.RequiredModelMBeanInvoker; import org.jboss.mx.modelmbean.XMBean; import org.jboss.mx.modelmbean.XMBeanConstants; import org.jboss.mx.server.AbstractMBeanInvoker; import org.jboss.mx.server.MBeanInvoker; import org.jboss.mx.server.RawDynamicInvoker; import org.jboss.mx.server.ServerConfig; import org.jboss.mx.server.ServerObjectInstance; import org.jboss.util.NestedRuntimeException; /** * The registry for object name - object reference mapping in the * MBean server. * <p> * The implementation of this class affects the invocation speed * directly, please check any changes for performance. * * @todo JMI_DOMAIN isn't very protected * * @see org.jboss.mx.server.registry.MBeanRegistry * * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>. * @author <a href="mailto:trevor@protocool.com">Trevor Squires</a>. * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>. * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>. * @author <a href="mailto:thomas.diesler@jboss.com">Thomas Diesler</a>. * * @version $Revision: 85294 $ */ public class BasicMBeanRegistry implements MBeanRegistry { // Constants ----------------------------------------------------- /** The server config */ private static ServerConfig serverConfig = ServerConfig.getInstance(); /** The default domain */ private static String JMI_DOMAIN = serverConfig.getJMIDomain(); // Attributes ---------------------------------------------------- /** * A map of domain name to another map containing object name canonical * key properties to registry entries. * domain -> canonicalKeyProperties -> MBeanEntry */ private Map domainMap = new ConcurrentReaderHashMap(); /** * The default domain for this registry */ private String defaultDomain; /** * The MBeanServer for which we are the registry. */ private MBeanServer server; /** * The loader repository for loading classes */ private LoaderRepository loaderRepository; /** * Sequence number for the MBean server registration notifications. */ protected final SynchronizedLong registrationNotificationSequence = new SynchronizedLong (1); /** * Sequence number for the MBean server unregistration notifications. */ protected final SynchronizedLong unregistrationNotificationSequence = new SynchronizedLong (1); /** * Direct reference to the mandatory MBean server delegate MBean. */ protected MBeanServerDelegate delegate; protected Vector fMbInfosToStore; private ObjectName mbeanInfoService; // Static -------------------------------------------------------- /** * The logger */ protected static Logger log = Logger.getLogger(BasicMBeanRegistry.class); // Constructors -------------------------------------------------- /** * Constructs a new BasicMBeanRegistry.<p> */ public BasicMBeanRegistry(MBeanServer server, String defaultDomain, ClassLoaderRepository clr) { // Store the context this.server = server; this.defaultDomain = defaultDomain; try { loaderRepository = (LoaderRepository) clr; mbeanInfoService = new ObjectName("user:service=MBeanInfoDB"); } catch (Exception e) { throw new NestedRuntimeException("Error instantiating registry", e); } } // MBeanRegistry Implementation ---------------------------------- public ObjectInstance registerMBean(Object object, ObjectName name, Map valueMap) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { ObjectName regName = name; boolean registrationDone = true; boolean invokedPreRegister = false; String magicToken = null; MBeanInvoker invoker = null; if (object == null) throw new RuntimeOperationsException( new IllegalArgumentException("Attempting to register null object")); // get mbean type, dynamic or standard MBeanCapability mbcap = MBeanCapability.of(object.getClass()); try { if (valueMap != null) magicToken = (String) valueMap.get(JMI_DOMAIN); // TODO: allow custom factory for diff invoker types int mbeanType = mbcap.getMBeanType(); if (mbeanType == MBeanCapability.STANDARD_MBEAN) { invoker = new XMBean(object, XMBeanConstants.STANDARD_MBEAN); } else if (object instanceof MBeanInvoker) { invoker = (MBeanInvoker)object; } else if (mbeanType == MBeanCapability.DYNAMIC_MBEAN) { if( object instanceof RequiredModelMBean ) invoker = new RequiredModelMBeanInvoker((DynamicMBean)object); else invoker = new RawDynamicInvoker((DynamicMBean)object); } // Perform the pregistration MBeanEntry entry = new MBeanEntry(regName, invoker, object, valueMap); AbstractMBeanInvoker.setMBeanEntry(entry); regName = invokePreRegister(invoker, regName, magicToken); invokedPreRegister = true; try { MBeanInfo info = invoker.getMBeanInfo(); verifyMBeanInfo(info, name); entry.setResourceClassName(info.getClassName()); // Register the mbean // Update the registered name to the final value entry.setObjectName(regName); add(entry); try { // Add the classloader to the repository if (object instanceof ClassLoader) registerClassLoader((ClassLoader)object); try { if (delegate != null) sendRegistrationNotification (regName); else if (serverConfig.getMBeanServerDelegateName().equals(name)) delegate = (MBeanServerDelegate) object; ServerObjectInstance serverObjInst = new ServerObjectInstance (regName, entry.getResourceClassName(), delegate.getMBeanServerId()); persistIfRequired(invoker.getMBeanInfo(), regName); return serverObjInst; } catch (Throwable t) { // Problem, remove a classloader from the repository if (object instanceof ClassLoader) loaderRepository.removeClassLoader((ClassLoader)object); throw t; } } catch (Throwable t) { // Problem, remove the mbean from the registry remove(regName); throw t; } } // Throw for null MBeanInfo catch (NotCompliantMBeanException e) { throw e; } // Thrown by the registry catch (InstanceAlreadyExistsException e) { throw e; } catch (Throwable t) { // Something is broken log.error("Unexpected Exception:", t); throw t; } } catch (NotCompliantMBeanException e) { registrationDone = false; throw e; } catch (InstanceAlreadyExistsException e) { // It was already registered registrationDone = false; throw e; } catch (MBeanRegistrationException e) { // The MBean cancelled the registration registrationDone = false; log.warn(e.toString()); throw e; } catch (RuntimeOperationsException e) { // There was a problem with one the arguments registrationDone = false; throw e; } catch (Exception ex) { // any other exception is mapped to NotCompliantMBeanException registrationDone = false; NotCompliantMBeanException ncex = new NotCompliantMBeanException("Cannot register MBean: " + name); ncex.initCause(ex); throw ncex; } catch (Throwable t) { // Some other error log.error("Cannot register MBean", t); registrationDone = false; return null; } finally { // Tell the MBean the result of the registration if (invoker != null) { try { invoker.postRegister(new Boolean(registrationDone)); } catch(Exception e) { // Only throw this if preRegister succeeded if( invokedPreRegister == true ) { if( e instanceof RuntimeException ) throw new RuntimeMBeanException((RuntimeException) e); else throw new MBeanRegistrationException(e); } } } AbstractMBeanInvoker.setMBeanEntry(null); } } /** * Verifies the MBeanInfo and throws an exception if something is wrong. * @param info a MBeanInfo * @param name a ObjectName * @throws NotCompliantMBeanException when something is wrong with the MBean info */ private void verifyMBeanInfo(MBeanInfo info, ObjectName name) throws NotCompliantMBeanException { try { if (info == null) throw new NotCompliantMBeanException("MBeanInfo cannot be null, for: " + name); if (info.getClassName() == null) throw new NotCompliantMBeanException("Classname returned from MBeanInfo cannot be null, for: " + name); } catch (NotCompliantMBeanException ncex) { throw ncex; } catch (Throwable t) { NotCompliantMBeanException ncex = new NotCompliantMBeanException("Cannot verify MBeanInfo, for: " + name); ncex.initCause(t); throw ncex; } } /** * send a MBeanServerNotification.REGISTRATION_NOTIFICATION notification * to regName * * @param regName */ protected void sendRegistrationNotification (ObjectName regName) { long sequence = registrationNotificationSequence.increment (); delegate.sendNotification ( new MBeanServerNotification ( MBeanServerNotification.REGISTRATION_NOTIFICATION, delegate, sequence, regName)); } /** * subclasses can override to provide their own pre-registration pre- and post- logic for * <tt>preRegister</tt> and must call preRegister on the MBeanRegistration instance * * @param registrationInterface * @param regName * @return object name * @throws Exception */ protected ObjectName handlePreRegistration (MBeanRegistration registrationInterface, ObjectName regName) throws Exception { ObjectName mbean = registrationInterface.preRegister (server, regName); if (regName == null) { return mbean; } else { return regName; } } /** * subclasses can override to provide any custom preDeregister logic * and must call preDregister on the MBeanRegistration instance * * @param registrationInterface * @throws Exception */ protected void handlePreDeregister (MBeanRegistration registrationInterface) throws Exception { registrationInterface.preDeregister (); } /** * Subclasses can override if they wish to control the classloader * registration to loader repository. * * @param cl classloader */ protected void registerClassLoader(ClassLoader cl) { if( (cl instanceof RealClassLoader) == false ) { // Only register non-UCLs as UCLs already have a repository loaderRepository.addClassLoader(cl); } } public void unregisterMBean(ObjectName name) throws InstanceNotFoundException, MBeanRegistrationException { name = qualifyName(name); if (name.getDomain().equals(JMI_DOMAIN)) throw new RuntimeOperationsException(new IllegalArgumentException( "Not allowed to unregister: " + name.toString())); MBeanEntry entry = get(name); Object resource = entry.getResourceInstance(); try { // allow subclasses to perform their own pre- and post- pre-deregister logic handlePreDeregister (entry.getInvoker()); } catch (Exception e) { // don't double wrap MBeanRegistrationException if (e instanceof MBeanRegistrationException) throw (MBeanRegistrationException)e; throw new MBeanRegistrationException(e, "preDeregister"); } // Remove any classloader if (resource instanceof ClassLoader) loaderRepository.removeClassLoader((ClassLoader)resource); // It is no longer registered remove(name); sendUnRegistrationNotification (name); entry.getInvoker().postDeregister(); } /** * send MBeanServerNotification.UNREGISTRATION_NOTIFICATION notification to * name * * @param name */ protected void sendUnRegistrationNotification (ObjectName name) { long sequence = unregistrationNotificationSequence.increment (); delegate.sendNotification ( new MBeanServerNotification ( MBeanServerNotification.UNREGISTRATION_NOTIFICATION, delegate, sequence, name ) ); } public MBeanEntry get(ObjectName name) throws InstanceNotFoundException { if (name == null) throw new RuntimeOperationsException(new IllegalArgumentException("null object name")); // Determine the domain and retrieve its entries String domain = name.getDomain(); if (domain.length() == 0) domain = defaultDomain; String props = name.getCanonicalKeyPropertyListString(); Map mbeanMap = getMBeanMap(domain, false); // Retrieve the mbean entry Object o = null; if (null == mbeanMap || null == (o = mbeanMap.get(props))) throw new InstanceNotFoundException(name + " is not registered."); // We are done return (MBeanEntry) o; } public String getDefaultDomain() { return defaultDomain; } public String[] getDomains() { ArrayList domains = new ArrayList(domainMap.size()); for (Iterator iterator = domainMap.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = (Map.Entry) iterator.next(); String domainName = (String) entry.getKey(); Map mbeans = (Map) entry.getValue(); if (mbeans != null && mbeans.isEmpty() == false) domains.add(domainName); } return (String[]) domains.toArray(new String[domains.size()]); } public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException { if (!contains(name)) throw new InstanceNotFoundException(name + " not registered."); return new ServerObjectInstance(qualifyName(name), get(name).getResourceClassName(), delegate.getMBeanServerId()); } public Object getValue(ObjectName name, String key) throws InstanceNotFoundException { return get(name).getValue(key); } public boolean contains(ObjectName name) { // null safety check if (name == null) return false; // Determine the domain and retrieve its entries String domain = name.getDomain(); if (domain.length() == 0) domain = defaultDomain; String props = name.getCanonicalKeyPropertyListString(); Map mbeanMap = getMBeanMap(domain, false); // Return the result return (null != mbeanMap && mbeanMap.containsKey(props)); } public int getSize() { int retval = 0; for (Iterator iterator = domainMap.values().iterator(); iterator.hasNext();) { retval += ((Map)iterator.next()).size(); } return retval; } public List findEntries(ObjectName pattern) { ArrayList retval = new ArrayList(); // There are a couple of shortcuts we can employ to make this a // bit faster - they're commented. // First, if pattern == null or pattern.getCanonicalName() == "*:*" we want the // set of all MBeans. if (pattern == null || pattern.getCanonicalName().equals("*:*")) { for (Iterator domainIter = domainMap.values().iterator(); domainIter.hasNext();) retval.addAll(((Map)domainIter.next()).values()); } // Next, if !pattern.isPattern() then we are doing a simple get (maybe defaultDomain). else if (!pattern.isPattern()) { // simple get try { retval.add(get(pattern)); } catch (InstanceNotFoundException e) { // we don't care } } // Now we have to do a brute force, oh well. else { // Here we go, step through every domain and see if our pattern matches before optionally checking // each ObjectName's properties for a match. for (Iterator domainIter = domainMap.entrySet().iterator(); domainIter.hasNext();) { Map.Entry mapEntry = (Map.Entry) domainIter.next(); Map value = (Map) mapEntry.getValue(); if (value != null && value.isEmpty() == false) { for (Iterator mbeanIter = value.values().iterator(); mbeanIter.hasNext();) { MBeanEntry entry = (MBeanEntry) mbeanIter.next(); if (pattern.apply(entry.getObjectName())) retval.add(entry); } } } } return retval; } /** * Cleans up the registry before the MBean server is released. */ public void releaseRegistry() // This is based on patch by Rod Burgett (Bug report: 763378) // Modified. Server is calling the registry. { server = null; delegate = null; // clear each value element from the domainMap for (Iterator iterator = domainMap.keySet().iterator(); iterator.hasNext();) { Map nextMap = (Map) domainMap.get(iterator.next()); if ( nextMap.size() > 0 ) { nextMap.clear(); } } domainMap.clear(); domainMap = null; } // Protected ----------------------------------------------------- protected ObjectName invokePreRegister(MBeanInvoker invoker, ObjectName regName, String magicToken) throws MBeanRegistrationException, NotCompliantMBeanException { // if we were given a non-null object name for registration, qualify it // and expand default domain if (regName != null) regName = qualifyName(regName); // store the name returned by preRegister() here ObjectName mbeanName = null; try { // invoke preregister on the invoker, it will delegate to the resource // if needed mbeanName = invoker.preRegister(server, regName); } // if during pre registration, the mbean turns out to be not compliant catch (NotCompliantMBeanException ncex) { throw ncex; } // catch all exceptions cause by preRegister, these will abort registration catch (Exception e) { if (e instanceof MBeanRegistrationException) { throw (MBeanRegistrationException)e; } throw new MBeanRegistrationException(e, "preRegister() failed: " + "[ObjectName='" + regName + "', Class=" + invoker.getResource().getClass().getName() + " (" + invoker.getResource() + ")]" ); } catch (Throwable t) { log.warn("preRegister() failed for " + regName + ": ", t); if (t instanceof Error) throw new RuntimeErrorException((Error)t); else throw new RuntimeException(t.toString()); } // if registered with null name, use the default name returned by // the preregister implementation if (regName == null) regName = mbeanName; return validateAndQualifyName(regName, magicToken); } /** * Adds an MBean entry<p> * * WARNING: The object name should be fully qualified. * * @param entry the MBean entry to add * @exception InstanceAlreadyExistsException when the MBean's object name * is already registered */ protected synchronized void add(MBeanEntry entry) throws InstanceAlreadyExistsException { // Determine the MBean's name and properties ObjectName name = entry.getObjectName(); String domain = name.getDomain(); String props = name.getCanonicalKeyPropertyListString(); // Create a properties -> entry map if we don't have one Map mbeanMap = getMBeanMap(domain, true); // Make sure we aren't already registered if (mbeanMap.get(props) != null) throw new InstanceAlreadyExistsException(name + " already registered."); // Ok, we are registered mbeanMap.put(props, entry); } /** * Removes an MBean entry * * WARNING: The object name should be fully qualified. * * @param name the object name of the entry to remove * @exception InstanceNotFoundException when the object name is not * registered */ protected synchronized void remove(ObjectName name) throws InstanceNotFoundException { // Determine the MBean's name and properties String domain = name.getDomain(); String props = name.getCanonicalKeyPropertyListString(); Map mbeanMap = getMBeanMap(domain, false); // Remove the entry, raise an exception when it didn't exist if (null == mbeanMap || null == mbeanMap.remove(props)) throw new InstanceNotFoundException(name + " not registered."); } /** * Validates and qualifies an MBean<p> * * Validates the name is not a pattern.<p> * * Adds the default domain if no domain is specified.<p> * * Checks the name is not in the reserved domain JMImplementation when * the magicToken is not {@link org.jboss.mx.server.ServerConstants#JMI_DOMAIN JMI_DOMAIN} * * @param name the name to validate * @param magicToken used to get access to the reserved domain * @return the original name or the name prepended with the default domain * if no domain is specified. * @exception RuntimeOperationsException containing an * IllegalArgumentException for a problem with the name */ protected ObjectName validateAndQualifyName(ObjectName name, String magicToken) { // Check for qualification ObjectName result = qualifyName(name); // Make sure the name is not a pattern if (result.isPattern()) throw new RuntimeOperationsException( new IllegalArgumentException("Object name is a pattern:" + name)); // Check for reserved domain if (magicToken != JMI_DOMAIN && result.getDomain().equals(JMI_DOMAIN)) throw new RuntimeOperationsException(new IllegalArgumentException( "Domain " + JMI_DOMAIN + " is reserved")); // I can't think of anymore tests, we're done return result; } /** * Qualify an object name with the default domain<p> * * Adds the default domain if no domain is specified. * * @param name the name to qualify * @return the original name or the name prepended with the default domain * if no domain is specified. * @exception RuntimeOperationsException containing an * IllegalArgumentException when there is a problem */ protected ObjectName qualifyName(ObjectName name) { if (name == null) throw new RuntimeOperationsException( new IllegalArgumentException("Null object name")); try { if (name.getDomain().length() == 0) return new ObjectName(defaultDomain + ":" + name.getCanonicalKeyPropertyListString()); else return name; } catch (MalformedObjectNameException e) { throw new RuntimeOperationsException( new IllegalArgumentException(e.toString())); } } /** * Adds the given MBean Info object to the persistence queue if it explicity denotes * (via metadata) that it should be stored. * @todo -- add notification of registration of MBeanInfoDb. * It is possible that some MBeans whose MBean Info should be stored are * registered before the MBean Info Storage delegate is available. These * MBeans are remembered by the registry and should be added to the storage delegate * as soon as it is available. In the current mechanism, they are added only if another * MBean requesting MBean info persistence is registered after the delegate is registered. * Someone more familiar with the server could make this more robust by adding * a notification mechanism such that the queue is flushed as soon as the * delegate is available. - Matt Munz * @todo does this code need to be here? can't a notification listener be * registered with the MBeanServerDelegate that stores a backlog * until the service becomes available? * @todo the mbInfoStores is a memory leak if the service is never registered * @todo mbInfoStores is not synchronized correctly * Thread1 adds * Thread1 clones and invokes * Thread2 adds * Thread1 clears * Thread2's add is lost * @todo Don't use Vector, performs too fine grained synchronization, * probably not important in this case. */ protected void persistIfRequired(MBeanInfo info, ObjectName name) throws MalformedObjectNameException, InstanceNotFoundException, MBeanException, ReflectionException { if(!(info instanceof ModelMBeanInfo)) { return; } ModelMBeanInfo mmbInfo = (ModelMBeanInfo) info; Descriptor descriptor; try { descriptor = mmbInfo.getMBeanDescriptor(); } catch(MBeanException cause) { log.error("Error trying to get descriptors.", cause); return; } if (descriptor == null) return; String persistInfo = (String) descriptor.getFieldValue(ModelMBeanConstants.PERSIST_INFO); if (persistInfo == null) return; // use default -- no persistence log.debug("persistInfo: " + persistInfo); Boolean shouldPersist = new Boolean(persistInfo); if(!shouldPersist.booleanValue()) { return; } mbInfosToStore().add(name); // see if MBeanDb is available if(contains(mbeanInfoService)) { // flush queue to the MBeanDb log.debug("flushing queue"); server.invoke( mbeanInfoService, "add", new Object[] { mbInfosToStore().clone() }, new String[] { mbInfosToStore().getClass().getName() }); log.debug("clearing queue"); mbInfosToStore().clear(); } else { log.debug("service is not registered. items remain in queue"); } } /** * ObjectName objects bound to MBean Info objects that are waiting to be stored in the * persistence store. */ protected Vector mbInfosToStore() { if(fMbInfosToStore == null) { fMbInfosToStore = new Vector(10); } return fMbInfosToStore; } /** * The <code>getMBeanMap</code> method provides synchronized access * to the mbean map for a domain. This is actually a solution to a * bug that resulted in wiping out the jboss domain mbeanMap for no * apparent reason. * * @param domain a <code>String</code> value * @param createIfMissing a <code>boolean</code> value * @return a <code>Map</code> value */ private Map getMBeanMap(String domain, boolean createIfMissing) { Map mbeanMap = (Map) domainMap.get(domain); if (mbeanMap == null && createIfMissing) { mbeanMap = new ConcurrentReaderHashMap(); domainMap.put(domain, mbeanMap); } return mbeanMap; } }