/*
 * 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.system.server.jmx;

import java.io.ObjectInputStream;
import java.lang.reflect.Constructor;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanException;
import javax.management.NotCompliantMBeanException;
import javax.management.InstanceNotFoundException;
import javax.management.QueryExp;
import javax.management.AttributeNotFoundException;
import javax.management.AttributeList;
import javax.management.Attribute;
import javax.management.InvalidAttributeValueException;
import javax.management.NotificationListener;
import javax.management.NotificationFilter;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanInfo;
import javax.management.IntrospectionException;
import javax.management.OperationsException;
import javax.management.MBeanServerDelegate;
import javax.management.loading.ClassLoaderRepository;

/**
 * An MBeanServer implementation that bridges between the platform MBeanServer
 * and the jboss implementation. This is used by the @{link MBeanServerBuilderImpl}
 * to bridge the period from the vm startup until the jboss server is loaded
 * and the jboss MBeanServer is available.
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 57205 $
 */
public class LazyMBeanServer
   implements MBeanServer
{
   private static MBeanServer registeredServer;
   private static MBeanServer theServer;
   private static MBeanServer platformServer;
   private static String defaultDomain;
   private static MBeanServerDelegate theDelegate;
   /** A HashSet<String> of the jmx domain prefixes that need to be
    * handled by the platform server
    */
   private static Set platformServerDomains = Collections.synchronizedSet(new HashSet());
   /** A HashSet<String> of the jmx domain prefixes that need to be
    * handled by the jboss server
    */
   private static Set serverDomains = Collections.synchronizedSet(new HashSet());

   /**
    * Called by the ServerImpl to reset theServer implementation to the jboss
    * version so that our xmbean and the like function correctly.
    * 
    * @param server - the existing MBeanServer
    * @return org.jboss.mx.server.MBeanServerImp
    * @throws Exception
    */ 
   public static MBeanServer resetToJBossServer(MBeanServer server)
      throws Exception
   {
      MBeanServer coreServer = server;
      if( theDelegate != null )
      {
         Class[] sig = {String.class, MBeanServer.class, MBeanServerDelegate.class};
         Object[] args = {defaultDomain, null, theDelegate};
         ClassLoader loader = Thread.currentThread().getContextClassLoader();
         Class c = loader.loadClass("org.jboss.mx.server.MBeanServerImpl");
         Constructor ctor = c.getConstructor(sig);
         theServer = (MBeanServer) ctor.newInstance(args);
         coreServer = theServer;
         String[] domains = theServer.getDomains();
         for(int n = 0; n < domains.length; n ++)
         {
            serverDomains.add(domains[n]);
         }
      }
      return coreServer;
   }
   /**
    * Provide access to the original MBeanServer that was registered so that
    * it can be removed on shutdown for example.
    * 
    * @param server - the ServerImple MBeanServer
    * @return either the argument, or the last LazyMBeanServer created
    */ 
   public static MBeanServer getRegisteredMBeanServer(MBeanServer server)
   {
      MBeanServer outerServer = server;
      if( registeredServer != null )
         outerServer = registeredServer;
      return outerServer;
   }

   /**
    * Updates the platformServerDomains and serverDomains sets of jmx
    * domain names.
    */
   public static synchronized void reloadDomains()
   {
      // Initialize the platform server domains
      String[] domains = platformServer.getDomains();
      for(int n = 0; n < domains.length; n ++)
      {
         platformServerDomains.add(domains[n]);
      }
      domains = theServer.getDomains();
      for(int n = 0; n < domains.length; n ++)
      {
         serverDomains.add(domains[n]);
      }
   }
   LazyMBeanServer(String domain, MBeanServer outer,
      MBeanServerDelegate delegate)
   {
      defaultDomain = domain;
      platformServer = outer;
      theServer = outer;
      registeredServer = this;
      theDelegate = delegate;
      // Initialize the platform server domains
      String[] domains = platformServer.getDomains();
      for(int n = 0; n < domains.length; n ++)
      {
         platformServerDomains.add(domains[n]);
      }
   }

   public ObjectInstance createMBean(String className, ObjectName name)
      throws ReflectionException, InstanceAlreadyExistsException,
      MBeanRegistrationException, MBeanException,
      NotCompliantMBeanException
   {
      try
      {
         return createMBean(className, name, null);
      }
      catch (InstanceNotFoundException e)
      {
         throw new MBeanException(e);
      }
   }

   public ObjectInstance createMBean(String className, ObjectName name,
                 ObjectName loaderName) 
      throws ReflectionException, InstanceAlreadyExistsException,
      MBeanRegistrationException, MBeanException,
      NotCompliantMBeanException, InstanceNotFoundException
   {
      return getServer(name).createMBean(className, name, loaderName);
   }

   public ObjectInstance createMBean(String className, ObjectName name,
                 Object params[], String signature[]) 
      throws ReflectionException, InstanceAlreadyExistsException,
      MBeanRegistrationException, MBeanException,
      NotCompliantMBeanException
   {
      return getServer(name).createMBean(className, name, params, signature);
   }

   public ObjectInstance createMBean(String className, ObjectName name,
                 ObjectName loaderName, Object params[],
                 String signature[]) 
      throws ReflectionException, InstanceAlreadyExistsException,
      MBeanRegistrationException, MBeanException,
      NotCompliantMBeanException, InstanceNotFoundException
   {
      return getServer(name).createMBean(className, name, loaderName, params, signature);
   }

   public ObjectInstance registerMBean(Object object, ObjectName name)
      throws InstanceAlreadyExistsException, MBeanRegistrationException,
      NotCompliantMBeanException
   {
      return theServer.registerMBean(object, name);
   }

   public void unregisterMBean(ObjectName name)
      throws InstanceNotFoundException, MBeanRegistrationException
   {
      theServer.unregisterMBean(name);
   }

   public ObjectInstance getObjectInstance(ObjectName name)
      throws InstanceNotFoundException
   {
      ObjectInstance oi = getServer(name).getObjectInstance(name);
      return oi;
   }

   public Set queryMBeans(ObjectName name, QueryExp query)
   {
      // query both servers
      Set beans = platformServer.queryMBeans(name, query);
      Set beans2 = theServer.queryMBeans(name, query);
      beans.addAll(beans2);
      return beans;
   }

   public Set queryNames(ObjectName name, QueryExp query)
   {
      Set names = platformServer.queryNames(name, query);
      Set names2 = theServer.queryNames(name, query);
      names.addAll(names2);
      return names;
   }

   public boolean isRegistered(ObjectName name)
   {
      return getServer(name).isRegistered(name);
   }

   public Integer getMBeanCount()
   {
      return theServer.getMBeanCount();
   }

   public Object getAttribute(ObjectName name, String attribute)
      throws MBeanException, AttributeNotFoundException,
      InstanceNotFoundException, ReflectionException
   {
      return getServer(name).getAttribute(name, attribute);
   }

   public AttributeList getAttributes(ObjectName name, String[] attributes)
      throws InstanceNotFoundException, ReflectionException
   {
      return getServer(name).getAttributes(name, attributes);
   }

   public void setAttribute(ObjectName name, Attribute attribute)
      throws InstanceNotFoundException, AttributeNotFoundException,
      InvalidAttributeValueException, MBeanException, 
      ReflectionException
   {
      getServer(name).setAttribute(name, attribute);
   }

   public AttributeList setAttributes(ObjectName name,
                  AttributeList attributes)
  throws InstanceNotFoundException, ReflectionException
   {
      return getServer(name).setAttributes(name, attributes);
   }

   public Object invoke(ObjectName name, String operationName,
         Object params[], String signature[])
      throws InstanceNotFoundException, MBeanException,
      ReflectionException
   {
      Object value = getServer(name).invoke(name, operationName, params, signature);
      return value;
   }

   public String getDefaultDomain()
   {
      return theServer.getDefaultDomain();
   }

   public String[] getDomains()
   {
      return theServer.getDomains();
   }

   public void addNotificationListener(ObjectName name,
              NotificationListener listener,
              NotificationFilter filter,
              Object handback)
      throws InstanceNotFoundException
   {
      getServer(name).addNotificationListener(name, listener, filter, handback);
   }

   public void addNotificationListener(ObjectName name,
              ObjectName listener,
              NotificationFilter filter,
              Object handback)
      throws InstanceNotFoundException
   {
      getServer(name).addNotificationListener(name, listener, filter, handback);
   }

   public void removeNotificationListener(ObjectName name,
                 ObjectName listener) 
  throws InstanceNotFoundException, ListenerNotFoundException
   {
      getServer(name).removeNotificationListener(name, listener);
   }

   public void removeNotificationListener(ObjectName name,
                 ObjectName listener,
                 NotificationFilter filter,
                 Object handback)
      throws InstanceNotFoundException, ListenerNotFoundException
   {
      getServer(name).removeNotificationListener(name, listener, filter, handback);
   }

   public void removeNotificationListener(ObjectName name,
                 NotificationListener listener)
      throws InstanceNotFoundException, ListenerNotFoundException
   {
      getServer(name).removeNotificationListener(name, listener);
   }

   public void removeNotificationListener(ObjectName name,
                 NotificationListener listener,
                 NotificationFilter filter,
                 Object handback)
      throws InstanceNotFoundException, ListenerNotFoundException
   {
      getServer(name).removeNotificationListener(name, listener, filter, handback);
   }

   /**
    * Obtains the MBeanInfo for the given ObjectName. In the event of an
    * InstanceNotFoundException, the jmx domain to server sets are reloaded
    * by calling reloadDomains(), and the lookup retried.
    * @param name
    * @return
    * @throws InstanceNotFoundException
    * @throws IntrospectionException
    * @throws ReflectionException
    */
   public MBeanInfo getMBeanInfo(ObjectName name)
      throws InstanceNotFoundException, IntrospectionException,
      ReflectionException
   {
      MBeanInfo info = null;
      try
      {
         info = getServer(name).getMBeanInfo(name);
      }
      catch(InstanceNotFoundException e)
      {
         reloadDomains();
         info = getServer(name).getMBeanInfo(name);
      }
      return info;
   }

   public boolean isInstanceOf(ObjectName name, String className)
      throws InstanceNotFoundException
   {
      return getServer(name).isInstanceOf(name, className);
   }

   public Object instantiate(String className)
      throws ReflectionException, MBeanException
   {
      return theServer.instantiate(className);
   }

   public Object instantiate(String className, ObjectName loaderName) 
      throws ReflectionException, MBeanException,
      InstanceNotFoundException
   {
      return theServer.instantiate(className, loaderName);
   }

   public Object instantiate(String className, Object params[],
              String signature[]) 
      throws ReflectionException, MBeanException
   {
      return theServer.instantiate(className, params, signature);
   }

   public Object instantiate(String className, ObjectName loaderName,
              Object params[], String signature[]) 
      throws ReflectionException, MBeanException,
      InstanceNotFoundException
   {
      return theServer.instantiate(className, loaderName, params, signature);
   }

   public ObjectInputStream deserialize(ObjectName name, byte[] data)
      throws InstanceNotFoundException, OperationsException
   {
      return getServer(name).deserialize(name, data);
   }

   public ObjectInputStream deserialize(String className, byte[] data)
      throws OperationsException, ReflectionException
   {
      return theServer.deserialize(className, data);
   }

   public ObjectInputStream deserialize(String className,
               ObjectName loaderName,
               byte[] data)
      throws InstanceNotFoundException, OperationsException,
      ReflectionException
   {
      return theServer.deserialize(className, loaderName, data);
   }

   public ClassLoader getClassLoaderFor(ObjectName mbeanName) 
      throws InstanceNotFoundException
   {
      return getServer(mbeanName).getClassLoaderFor(mbeanName);
   }

   public ClassLoader getClassLoader(ObjectName loaderName)
  throws InstanceNotFoundException
   {
      return getServer(loaderName).getClassLoader(loaderName);
   }

   public ClassLoaderRepository getClassLoaderRepository()
   {
      return theServer.getClassLoaderRepository();
   }

   /**
    * Choose the MBeanServer based on the ObjectName domain. If the serverDomain
    * contains the domain of the name, theServer is returned. If the 
    * platformServerDomains set contains the domain of the name, the
    * platformServer is returned. For an unknown domain theServer is returned.
    * 
    * @param name
    * @return Either the platform MBeanServer, or the jboss MBeanServer
    */ 
   private MBeanServer getServer(ObjectName name)
   {
      String domain = name != null ? name.getDomain() : "";
      if( serverDomains.contains(domain) )
         return theServer;
      if( platformServerDomains.contains(domain) )
         return platformServer;
      return theServer;
   }
}
