/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.mx.server.registry;
import javax.management.Descriptor;
import javax.management.DynamicMBean;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MalformedObjectNameException;
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.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.RuntimeErrorException;
import javax.management.RuntimeOperationsException;
import javax.management.modelmbean.ModelMBeanInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import org.jboss.mx.loading.LoaderRepository;
import org.jboss.mx.logging.Logger;
import org.jboss.mx.logging.SystemLogger;
import org.jboss.mx.metadata.MBeanCapability;
import org.jboss.mx.modelmbean.ModelMBeanConstants;
import org.jboss.mx.modelmbean.XMBean;
import org.jboss.mx.modelmbean.XMBeanConstants;
import org.jboss.mx.server.MBeanInvoker;
import org.jboss.mx.server.RawDynamicInvoker;
import org.jboss.mx.server.ServerObjectInstance;
import org.jboss.mx.util.ObjectNamePatternHelper;
import org.jboss.mx.util.ObjectNamePatternHelper.PropertyPattern;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong;
/**
* 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>.
* @version $Revision: 1.33 $
*/
public class BasicMBeanRegistry
implements MBeanRegistry
{
// 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 HashMap();
/**
* The default domain for this registry
*/
private String defaultDomain;
/**
* The MBeanServer for which we are the registry.
*/
private MBeanServer server = null;
/**
* The loader repository for loading classes
*/
private LoaderRepository loaderRepository = LoaderRepository.getDefaultLoaderRepository();
/**
* 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 = null;
protected Vector fMbInfosToStore;
// Static --------------------------------------------------------
/**
* The logger
*/
protected static Logger log = SystemLogger.getLogger(BasicMBeanRegistry.class);
// Constructors --------------------------------------------------
/**
* Constructs a new BasicMBeanRegistry.<p>
*
* @param the default domain of this registry.
*/
public BasicMBeanRegistry(MBeanServer server, String defaultDomain)
{
// Store the context
this.server = server;
this.defaultDomain = defaultDomain;
}
// MBeanRegistry Implementation ----------------------------------
public ObjectInstance registerMBean(Object object, ObjectName name,
Map valueMap)
throws InstanceAlreadyExistsException,
MBeanRegistrationException,
NotCompliantMBeanException
{
ObjectName regName = name;
boolean registrationDone = true;
String magicToken = null;
MBeanInvoker invoker = null;
if (object == null)
throw new RuntimeOperationsException(
new IllegalArgumentException("Attempting to register null object"));
// FIXME: This is untidy, in the pathological case it is
// wrong because we modifying the entry parameter
// which could get reused by the caller
// This is a fix for the SAR TCL problem in 4.0
// Allow the classloader in the value map to be an ObjectName
if (valueMap != null)
{
Object obj = valueMap.get(CLASSLOADER);
if (obj != null && obj instanceof ObjectName)
{
MBeanEntry clEntry = null;
try
{
clEntry = get((ObjectName) obj);
}
catch (InstanceNotFoundException e)
{
throw new RuntimeOperationsException(
new IllegalArgumentException(e.toString()));
}
valueMap.put(CLASSLOADER, clEntry.getResourceInstance());
}
}
// Check the objects compliance
MBeanCapability mbcap = MBeanCapability.of(object.getClass());
try
{
if (valueMap != null)
magicToken = (String) valueMap.get(JMI_DOMAIN);
// TODO: allow custom factory for diff invoker types
if (mbcap.getMBeanType() == MBeanCapability.STANDARD_MBEAN)
{
invoker = new XMBean(object, XMBeanConstants.STANDARD_MBEAN);
}
else if (object instanceof MBeanInvoker)
{
invoker = (MBeanInvoker)object;
}
else if (object instanceof DynamicMBean)
{
invoker = new RawDynamicInvoker((DynamicMBean)object);
}
regName = invokePreRegister(invoker, regName, magicToken);
try
{
// Register the mbean
// Test for Null MBeanInfo
if (invoker.getMBeanInfo() == null)
throw new NotCompliantMBeanException("Null MBeanInfo for " + name);
// FIXME redundant getResource()
MBeanEntry entry = new MBeanEntry(regName, invoker, invoker.getResource(), valueMap);
add(entry);
try
{
// Add the classloader to the repository
if (object instanceof ClassLoader)
registerClassLoader((ClassLoader)object);
try
{
if (delegate != null)
{
sendRegistrationNotification (regName);
}
else if (name.getCanonicalName ().equals (MBEAN_SERVER_DELEGATE))
{
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 (Throwable t)
{
// Some other error
registrationDone = false;
return null;
}
finally
{
// Tell the MBean the result of the registration
//if (registrationInterface != null)
// registrationInterface.postRegister(new Boolean(registrationDone));
if (invoker != null)
invoker.postRegister(new Boolean(registrationDone));
}
}
/**
* 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
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
*/
protected ObjectName handlePreRegistration (MBeanRegistration registrationInterface, ObjectName regName)
throws Exception
{
ObjectName mbean = registrationInterface.preRegister (server, regName);
if (regName == null)
{
return mbean;
}
else
{
return regName;
}
}
protected void registerClassLoader(ClassLoader cl)
{
loaderRepository.addClassLoader(cl);
}
/**
* subclasses can override to provide any custom preDeregister logic
* and must call preDregister on the MBeanRegistration instance
*
* @param registrationInterface
* @throws Exception
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
*/
protected void handlePreDeregister (MBeanRegistration registrationInterface)
throws Exception
{
registrationInterface.preDeregister ();
}
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 InstanceNotFoundException("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());
synchronized (domainMap)
{
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.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;
synchronized (domainMap)
{
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("*:*"))
{
synchronized (domainMap)
{
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
{
String patternDomain = pattern.getDomain();
if (patternDomain.length() == 0)
patternDomain = defaultDomain;
PropertyPattern propertyPattern = new PropertyPattern(pattern);
// Here we go, step through every domain and see if our pattern matches before optionally checking
// each ObjectName's properties for a match.
synchronized (domainMap)
{
for (Iterator domainIter = domainMap.entrySet().iterator(); domainIter.hasNext();)
{
Map.Entry mapEntry = (Map.Entry) domainIter.next();
if (ObjectNamePatternHelper.patternMatch((String) mapEntry.getKey(), patternDomain))
{
for (Iterator mbeanIter = ((Map)mapEntry.getValue()).values().iterator(); mbeanIter.hasNext();)
{
MBeanEntry entry = (MBeanEntry) mbeanIter.next();
if (propertyPattern.patternMatch(entry.getObjectName()))
retval.add(entry);
}
}
}
}
}
return retval;
}
// Protected -----------------------------------------------------
protected ObjectName invokePreRegister(MBeanInvoker invoker, ObjectName regName, String magicToken)
throws MBeanRegistrationException
{
// 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);
}
// 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 RuntimeOperationException 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 RuntimeOperationException 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
*/
protected void persistIfRequired(MBeanInfo info, ObjectName name)
throws
MalformedObjectNameException,
InstanceNotFoundException,
MBeanException,
ReflectionException
{
if(!(info instanceof ModelMBeanInfo))
{
return;
}
ModelMBeanInfo mmbInfo = (ModelMBeanInfo) info;
Descriptor[] descriptors;
try { descriptors = mmbInfo.getDescriptors(ModelMBeanConstants.MBEAN_DESCRIPTOR); }
catch(MBeanException cause)
{
log.error("Error trying to get descriptors.", cause);
return;
}
log.debug("descriptors: " + descriptors);
if(descriptors == null) { return; }
String persistInfo = null;
for(int i = 0; ((i < descriptors.length) && (persistInfo == null)); i++)
{
persistInfo = (String) descriptors[i].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
ObjectName mbeanInfoService = new ObjectName("user:service=MBeanInfoDB");
if(server.isRegistered(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)
{
synchronized (domainMap)
{
Map mbeanMap = (Map)domainMap.get(domain);
if (mbeanMap == null && createIfMissing)
{
mbeanMap = new HashMap();
domainMap.put(domain, mbeanMap);
} // end of if ()
return mbeanMap;
}
}
}