/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package javax.management;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.management.loading.ClassLoaderRepository;

import org.jboss.mx.server.ServerConstants;
import org.jboss.mx.util.PropertyAccess;

/**
 * MBeanServerFactory is used to create instances of MBean servers.
 *
 * @see javax.management.MBeanServer
 * @see javax.management.MBeanServerBuilder
 *
 * @author  <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
 * @author  <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.16 $
 *
 * <p><b>Revisions:</b>
 *
 * <p><b>20030806 Juha Lindfors:</b>
 * <ul>
 * <li>
 *   Attempts a reflected invoke to "releaseServer" method on the MBean server
 *   instance before letting go of the server reference. This allows the
 *   MBean server to do some clean up. Based on the patch submitted by
 *   Rod Burgett (Bug Tracker: #763378)
 * </ul>
 */
public class MBeanServerFactory
{

   // Constants -----------------------------------------------------

   private static final MBeanServerPermission CREATE =
      new MBeanServerPermission("createMBeanServer", null);
   private static final MBeanServerPermission FIND = 
      new MBeanServerPermission("findMBeanServer", null);
   private static final MBeanServerPermission NEW = 
      new MBeanServerPermission("newMBeanServer", null);
   private static final MBeanServerPermission RELEASE = 
      new MBeanServerPermission("releaseMBeanServer", null);


   // Attributes ----------------------------------------------------

   /**
    * Hold on to the server instances here (unless requested not to)
    */
   private static Map serverMap = new HashMap();


   // hide public constructor
   private MBeanServerFactory()
   {
   }

   // Public --------------------------------------------------------

   /**
    * Releases the reference to an MBean server. A security permission check
    * is made first if security manager has been installed.
    *
    * @param mbeanServer reference to the MBean server you want to release
    */
   public static void releaseMBeanServer(MBeanServer mbeanServer)
   {
      checkPermission(RELEASE);
      try
      {
         String agentID = null;
         /* Run the create using the jmx codebase priviledges since the
         MBeanServerPermission(releaseMBeanServer) has passed.
         */
         final MBeanServer theServer = mbeanServer;
         final ObjectName delegateName = new ObjectName(ServerConstants.MBEAN_SERVER_DELEGATE);
         try
         {
            agentID = (String) AccessController.doPrivileged(
               new PrivilegedExceptionAction()
               {
                  public Object run() throws Exception
                  {
                     return (String) theServer.getAttribute(delegateName,
                        "MBeanServerId");
                  }
               }
            );
         }
         catch(PrivilegedActionException e)
         {
            Exception ex = e.getException();
            if( ex instanceof JMException )
               throw (JMException) ex;
            else if( ex instanceof RuntimeException )
               throw (RuntimeException) ex;
            else
            {
               JMException jex = new JMException("Unknown exception during getAttribute(MBeanServerId)");
               jex.initCause(ex);
               throw jex;
            }
         }

         Object server = serverMap.remove(agentID);

         try
         {
            Method m = server.getClass().getMethod("releaseServer", null);
            m.invoke(server, null);
         }
         catch (Exception ignored)
         {
            // if the release fails, then it fails
         }

         if (server == null)
            throw new IllegalArgumentException("MBean server reference not found.");
      }
      catch (MalformedObjectNameException e)
      {
         throw new Error(e.toString());
      }
      catch (JMException e)
      {
         throw new Error("Cannot retrieve AgentID: " + e.toString());
      }
   }

   public static MBeanServer createMBeanServer()
   {
      return createMBeanServer(ServerConstants.DEFAULT_DOMAIN);
   }

   public static MBeanServer createMBeanServer(String domain)
   {
      checkPermission(CREATE);
      return createMBeanServer(domain, true);
   }

   public static MBeanServer newMBeanServer()
   {
      return newMBeanServer(ServerConstants.DEFAULT_DOMAIN);
   }

   public static MBeanServer newMBeanServer(String domain)
   {
      checkPermission(NEW);
      return createMBeanServer(domain, false);
   }

   public synchronized static ArrayList findMBeanServer(String agentId)
   {
      checkPermission(FIND);
      if (agentId != null)
      {
         ArrayList list = new ArrayList(1);
         Object server = serverMap.get(agentId);
         
         if (server != null)
            list.add(server);
            
         return list;
      }

      return new ArrayList(serverMap.values());
   }

   /**
    * Returns the classloader repository for an MBeanServer
    *
    * @param server the mbean server, pass null for the default loader repository
    *        shared by all mbeanservers
    * @return the loader repository
    */
   public static ClassLoaderRepository getClassLoaderRepository(MBeanServer server)
   {
      return server.getClassLoaderRepository();
   }


   // Private -------------------------------------------------------

   private static MBeanServer createMBeanServer(final String defaultDomain,
      boolean registerServer)
   {
      String builderClass = PropertyAccess.getProperty(
            ServerConstants.MBEAN_SERVER_BUILDER_CLASS_PROPERTY,
            ServerConstants.DEFAULT_MBEAN_SERVER_BUILDER_CLASS
      );

      try
      {
         ClassLoader cl = Thread.currentThread().getContextClassLoader();
         Class clazz = cl.loadClass(builderClass);
         Constructor constructor = clazz.getConstructor(new Class[0]);
         final MBeanServerBuilder builder = (MBeanServerBuilder)
            constructor.newInstance(new Object[0]);
         final MBeanServerDelegate delegate = builder.newMBeanServerDelegate();
         /* Run the create using the jmx codebase priviledges since the
         MBeanServerPermission(createMBeanServer) has passed.
         */
         MBeanServer server = null;
         try
         {
            server = (MBeanServer) AccessController.doPrivileged(
               new PrivilegedExceptionAction()
               {
                  public Object run() throws Exception
                  {
                     return builder.newMBeanServer(defaultDomain, null, delegate);
                  }
               }
            );
         }
         catch(PrivilegedActionException e)
         {
            RuntimeException re = (RuntimeException) e.getException();
            throw re;
         }

         if (registerServer)
         {
            String agentID = delegate.getMBeanServerId();
            serverMap.put(agentID, server);
         }

         return server;
      }
      catch (ClassNotFoundException e)
      {
         throw new IllegalArgumentException("The MBean server builder implementation class " + builderClass + " was not found: " + e.toString());
      }
      catch (NoSuchMethodException e) 
      {
         throw new IllegalArgumentException("The MBean server builder implementation class " + builderClass + " must contain a default constructor.");
      }
      catch (InstantiationException e) 
      {
         throw new IllegalArgumentException("Cannot instantiate class " + builderClass + ": " + e.toString());
      }
      catch (IllegalAccessException e)
      {
         throw new IllegalArgumentException("Unable to create the MBean server builder instance. Illegal access to class " + builderClass + " constructor: " + e.toString());
      }
      catch (InvocationTargetException e)
      {
         throw new RuntimeException("Unable to create the MBean server builder instance. Class " + builderClass + " has raised an exception in constructor: " + e.getTargetException().toString());
      }
   }

   private static void checkPermission(MBeanServerPermission permission)
   {
      SecurityManager security = System.getSecurityManager();
      if (security == null)
         return;
      security.checkPermission(permission);
   }

}