/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, 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.logging.Logger;
import org.jboss.mx.loading.LoaderRepository;
import org.jboss.mx.loading.RepositoryClassLoader;
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.ServerObjectInstance;
import org.jboss.mx.util.ObjectNamePatternHelper;
import org.jboss.mx.util.ObjectNamePatternHelper.PropertyPattern;
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: 57200 $
 */
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 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 (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 (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 RepositoryClassLoader) == 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
      {
         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.
         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)
            {
               if (ObjectNamePatternHelper.patternMatch((String) mapEntry.getKey(), patternDomain))
               {
                  for (Iterator mbeanIter = value.values().iterator(); mbeanIter.hasNext();)
                  {
                     MBeanEntry entry = (MBeanEntry) mbeanIter.next();
                     if (propertyPattern.patternMatch(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;
   }

   /**
    *
    * getMBeanMap should be called from a synchronized method if createIfMissing=true
    *
    * @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;
   }
}