/*
 * 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;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import javax.management.JMException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.ObjectName;

import org.jboss.deployment.DeploymentException;
import org.jboss.deployment.DeploymentInfo;
import org.jboss.deployment.DeploymentState;
import org.jboss.logging.Logger;
import org.jboss.mx.server.ServerConstants;
import org.jboss.mx.util.JBossNotificationBroadcasterSupport;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.mx.util.ObjectNameFactory;
import org.w3c.dom.Element;

/**
 * This is the main Service Controller. A controller can deploy a service to a
 * jboss.system It installs by delegating, it configures by delegating
 *
 * @see org.jboss.system.Service
 *
 * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
 * @version $Revision: 57205 $
 *
 * @jmx:mbean name="jboss.system:service=ServiceController"
 */
public class ServiceController extends JBossNotificationBroadcasterSupport
   implements ServiceControllerMBean, MBeanRegistration
{
   // Constants -----------------------------------------------------

   /** The ObjectName of the default loader repository */
   public static final ObjectName DEFAULT_LOADER_REPOSITORY = ObjectNameFactory.create(ServerConstants.DEFAULT_LOADER_NAME);

   /** The operation name for lifecycle */
   public static final String JBOSS_INTERNAL_LIFECYCLE = "jbossInternalLifecycle";

   /** The signature for lifecycle operations */
   public static final String[] JBOSS_INTERNAL_LIFECYCLE_SIG = new String[] { String.class.getName() };

   /** Class logger. */
   private static final Logger log = Logger.getLogger(ServiceController.class);
   
   // Attributes ----------------------------------------------------

   /** A callback to the JMX MBeanServer */
   protected MBeanServer server;

   /** Creator, helper class to instantiate MBeans **/
   protected ServiceCreator creator;

   /** Configurator, helper class to configure MBeans **/
   protected ServiceConfigurator configurator;

   /** ObjectName to ServiceContext map **/
   protected Map nameToServiceMap = Collections.synchronizedMap(new HashMap());

   /** A linked list of services in the order they were created **/
   protected List installedServices = new LinkedList();

   // Constructors --------------------------------------------------

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

   /**
    * Plugin a ServiceBinding policy
    * 
    * @param serviceBinding policy
    */   
   public void setServiceBinding(ServiceBinding serviceBinding)
   {
      if (configurator != null)
      {
         configurator.setServiceBinding(serviceBinding);
      }
   }
   
   /**
    * Lists the ServiceContexts of deployed mbeans
    *
    * @return the list of ServiceContexts for mbeans deployed through ServiceController.
    * @jmx:managed-operation
    */
   public List listDeployed()
   {
      return new ArrayList(installedServices);
   }

   /**
    * The <code>listIncompletelyDeployed</code> method returns the service contexts
    * for the mbeans whose status is not CREATED, RUNNING, STOPPED or DESTROYED.
    *
    * An MBean that has reached one of the above states has its dependencies resolved.
    * 
    * @return a List<ServiceContext>
    * @jmx:managed-operation
    */
   public List listIncompletelyDeployed()
   {
      List id = new ArrayList();
      for (Iterator i = installedServices.iterator(); i.hasNext();)
      {
         ServiceContext sc = (ServiceContext) i.next();
         if ( sc.state != ServiceContext.CREATED &&
              sc.state != ServiceContext.RUNNING &&
              sc.state != ServiceContext.STOPPED &&
              sc.state != ServiceContext.DESTROYED )
         {
            id.add(sc);
         }
      }
      return id;
   }

   /**
    * lists ObjectNames of deployed mbeans deployed through serviceController.
    *
    * @return a list of ObjectNames of deployed mbeans.
    * @jmx:managed-operation
    */
   public List listDeployedNames()
   {
      List names = new ArrayList(installedServices.size());
      for (Iterator i = installedServices.iterator(); i.hasNext();)
      {
         ServiceContext ctx = (ServiceContext) i.next();
         names.add(ctx.objectName);
      }

      return names;
   }

   /**
    * Gets the Configuration attribute of the ServiceController object
    *
    * @param objectNames Description of Parameter
    * @return The Configuration value
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public String listConfiguration(ObjectName[] objectNames) throws Exception
   {
      return configurator.getConfiguration(objectNames);
   }

   /** Go through the mbeans of the DeploymentInfo and validate that they
    * are in a state at least equal to that of the argument state
    *
    * @jmx:managed-operation
    */
   public void validateDeploymentState(DeploymentInfo di, DeploymentState state)
   {
      ArrayList mbeans = new ArrayList(di.mbeans);
      if (di.deployedObject != null)
         mbeans.add(di.deployedObject);
      boolean mbeansStateIsValid = true;
      for (int m = 0; m < mbeans.size(); m++)
      {
         ObjectName serviceName = (ObjectName) mbeans.get(m);
         ServiceContext ctx = this.getServiceContext(serviceName);
         if (ctx != null && state == DeploymentState.STARTED)
            mbeansStateIsValid &= ctx.state == ServiceContext.RUNNING;
      }
      if (mbeansStateIsValid == true)
         di.state = state;
   }

   /**
    * Deploy the beans
    *
    * Deploy means "instantiate and configure" so the MBean is created in the MBeanServer
    * You must call "create" and "start" separately on the MBean to affect the service lifecycle
    * deploy doesn't bother with service lifecycle only MBean instanciation/registration/configuration
    *
    * @param config
    * @param loaderName
    * @return Description of the Returned Value
    * @throws DeploymentException
    * @jmx:managed-operation
    */
   public synchronized List install(Element config, ObjectName loaderName)
         throws DeploymentException
   {
      List mbeans = configurator.install(config, loaderName);
      for (Iterator i = mbeans.iterator(); i.hasNext();)
      {
         ObjectName mbean = (ObjectName) i.next();
         installedServices.add(createServiceContext(mbean));
      }
      return mbeans;
   }

   /**
    * Register the mbean against the microkernel with no dependencies.
    * @see #register(ObjectName, java.util.Collection)
    * @jmx:managed-operation
    */
   public synchronized void register(ObjectName serviceName) throws Exception
   {
      register(serviceName, null);
   }

   /**
    * @see #register(ServiceContext, java.util.Collection)
    * @jmx:managed-operation
    */
   public synchronized void register(ObjectName serviceName, Collection depends)
         throws Exception
   {
      if (serviceName == null)
      {
         log.warn("Ignoring request to register null service: ", new Exception("STACKTRACE"));
         return;
      }
      
      log.debug("Registering service " + serviceName);
      ServiceContext ctx = createServiceContext(serviceName);

      register(ctx, depends);
   }

   /**
    * #Description of the Method
    *
    * @param serviceName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public synchronized void create(ObjectName serviceName) throws Exception
   {
      create(serviceName, null);
   }

   /**
    * #Description of the Method
    *
    * @param serviceName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public synchronized void create(ObjectName serviceName, Collection depends)
         throws Exception
   {
      if (serviceName == null)
      {
         log.warn("Ignoring request to create null service: ", new Exception("STACKTRACE"));
         return;
      }
      
      log.debug("Creating service " + serviceName);
      ServiceContext ctx = createServiceContext(serviceName);

      // Register the context and its dependencies if necessary
      register(ctx, depends);

      // If we are already created (can happen in dependencies) or failed just return
      if (ctx.state == ServiceContext.CREATED
            || ctx.state == ServiceContext.RUNNING
            || ctx.state == ServiceContext.FAILED)
      {
         log.debug("Ignoring create request for service: " + ctx.objectName);
         return;
      }

      // JSR 77, and to avoid circular dependencies
      int oldState = ctx.state;
      ctx.state = ServiceContext.CREATED;

      // Are all the mbeans I depend on created?   if not just return
      for (Iterator iterator = ctx.iDependOn.iterator(); iterator.hasNext();)
      {
         ServiceContext sc = (ServiceContext) iterator.next();
         int state = sc.state;

         // A dependent is not created or running
         if (!(state == ServiceContext.CREATED || state == ServiceContext.RUNNING))
         {
            log.debug("waiting in create of " + serviceName +
                  " waiting on " + sc.objectName);
            ctx.state = oldState;
            return;
         }
      }

      // Call create on the service Proxy
      try
      {
         ctx.proxy.create();
         sendControllerNotification(ServiceMBean.CREATE_EVENT, serviceName);            
      }
      catch (Throwable e)
      {
         ctx.state = ServiceContext.FAILED;
         ctx.problem = e;
         log.warn("Problem creating service " + serviceName, e);
         return;
      }

      // Those that depend on me are waiting for my creation, recursively create them
      log.debug("Creating dependent components for: " + serviceName
            + " dependents are: " + ctx.dependsOnMe);
      ArrayList tmp = new ArrayList(ctx.dependsOnMe);
      for (int n = 0; n < tmp.size(); n++)
      {
         // marcf fixme circular dependencies?
         ServiceContext ctx2 = (ServiceContext) tmp.get(n);
         create(ctx2.objectName);
      }
      tmp.clear();
   }

   /**
    * Starts the indicated service
    *
    * @param serviceName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public synchronized void start(ObjectName serviceName) throws Exception
   {
      if (serviceName == null)
      {
         log.warn("Ignoring request to start null service: ", new Exception("STACKTRACE"));
         return;
      }

      log.debug("starting service " + serviceName);

      ServiceContext ctx = createServiceContext(serviceName);

      if (!installedServices.contains(ctx))
         installedServices.add(ctx);

      // If we are already started (can happen in dependencies) just return
      if (ctx.state == ServiceContext.RUNNING || ctx.state == ServiceContext.FAILED)
      {
         log.debug("Ignoring start request for service: " + ctx.objectName);
         return;
      }

      // Start() is called before create(), so call create() to compensate
      if (ctx.state != ServiceContext.CREATED && ctx.state != ServiceContext.STOPPED)
      {
         log.debug("Start requested before create, calling create now for service: " + serviceName);
         create(serviceName);
      }
      
      // Get the fancy service proxy (for the lifecycle API)
      if (ctx.proxy == null)
         ctx.proxy = getServiceProxy(ctx.objectName, null);

      // JSR 77, and to avoid circular dependencies
      int oldState = ctx.state;
      ctx.state = ServiceContext.RUNNING;

      // Are all the mbeans I depend on started?   if not just return
      for (Iterator iterator = ctx.iDependOn.iterator(); iterator.hasNext();)
      {
         ServiceContext sctx = (ServiceContext) iterator.next();

         int state = sctx.state;

         // A dependent is not running
         if (!(state == ServiceContext.RUNNING))
         {
            log.debug("waiting in start " + serviceName + " on " + sctx.objectName);
            ctx.state = oldState;
            return;
         }
      }

      // Call start on the service Proxy
      try
      {
         ctx.proxy.start();
         sendControllerNotification(ServiceMBean.START_EVENT, serviceName);            
      }
      catch (Throwable e)
      {
         ctx.state = ServiceContext.FAILED;
         ctx.problem = e;
         log.warn("Problem starting service " + serviceName, e);
         return;
      }
      // Those that depend on me are waiting for my start, recursively start them
      log.debug("Starting dependent components for: " + serviceName
            + " dependent components: " + ctx.dependsOnMe);
      ArrayList tmp = new ArrayList(ctx.dependsOnMe);
      for (int n = 0; n < tmp.size(); n++)
      {
         // marcf fixme circular dependencies?
         ServiceContext ctx2 = (ServiceContext) tmp.get(n);
         start(ctx2.objectName);
      }
      tmp.clear();
   }

   /**
    * Stops and restarts the given service
    *
    * @param serviceName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public void restart(ObjectName serviceName) throws Exception
   {
      if (serviceName == null)
      {
         log.warn("Ignoring request to restart null service: ", new Exception("STACKTRACE"));
         return;
      }

      log.debug("restarting service " + serviceName);
      stop(serviceName);
      start(serviceName);
   }

   /**
    * Stop the given service
    *
    * @param serviceName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public void stop(ObjectName serviceName) throws Exception
   {
      if (serviceName == null)
      {
         log.warn("Ignoring request to stop null service: ", new Exception("STACKTRACE"));
         return;
      }

      ServiceContext ctx = (ServiceContext) nameToServiceMap.get(serviceName);
      log.debug("stopping service: " + serviceName);

      if (ctx == null)
      {
         log.warn("Ignoring request to stop nonexistent service: " + serviceName);
         return;
      }

      // If we are already stopped (can happen in dependencies) just return
      if (ctx.state != ServiceContext.RUNNING) return;

      // JSR 77 and to avoid circular dependencies
      ctx.state = ServiceContext.STOPPED;

      log.debug("stopping dependent services for: " + serviceName
            + " dependent services are: " + ctx.dependsOnMe);
      
      ArrayList tmp = new ArrayList(ctx.dependsOnMe);
      for (int n = 0; n < tmp.size(); n++)
      {
         // stop all the mbeans that depend on me
         ServiceContext ctx2 = (ServiceContext) tmp.get(n);
         ObjectName other = ctx2.objectName;
         stop(other);
      }
      tmp.clear();

      // Call stop on the service Proxy
      if (ctx.proxy != null)
      {
         try
         {
            ctx.proxy.stop();
            sendControllerNotification(ServiceMBean.STOP_EVENT, serviceName);
         }
         catch (Throwable e)
         {
            ctx.state = ServiceContext.FAILED;
            ctx.problem = e;
            log.warn("Problem stopping service " + serviceName, e);
         }
      }
   }

   /**
    * #Description of the Method
    *
    * @param serviceName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public void destroy(ObjectName serviceName) throws Exception
   {
      if (serviceName == null)
      {
         log.warn("Ignoring request to destroy null service: ", new Exception("STACKTRACE"));
         return;
      }

      ServiceContext ctx = (ServiceContext) nameToServiceMap.get(serviceName);
      log.debug("destroying service: " + serviceName);

      if (ctx == null)
      {
         log.warn("Ignoring request to destroy nonexistent service: " + serviceName);
         return;
      }

      // If we are already destroyed (can happen in dependencies) just return
      if (ctx.state == ServiceContext.DESTROYED ||
          ctx.state == ServiceContext.NOTYETINSTALLED)
         return;
      
      // If we are still running, stop service first
      if (ctx.state == ServiceContext.RUNNING)
      {
         log.debug("Destroy requested before stop, calling stop now for service: " + serviceName);
         stop(serviceName);
      }

      // JSR 77, and to avoid circular dependencies
      ctx.state = ServiceContext.DESTROYED;

      log.debug("destroying dependent services for: " + serviceName
            + " dependent services are: " + ctx.dependsOnMe);
      
      ArrayList tmp = new ArrayList(ctx.dependsOnMe);
      for (int n = 0; n < tmp.size(); n++)
      {
         // destroy all the mbeans that depend on me
         ServiceContext ctx2 = (ServiceContext) tmp.get(n);
         ObjectName other = ctx2.objectName;
         destroy(other);
      }
      tmp.clear();

      // Call destroy on the service Proxy
      if (ctx.proxy != null)
      {
         try
         {
            ctx.proxy.destroy();
            sendControllerNotification(ServiceMBean.DESTROY_EVENT, serviceName);
         }
         catch (Throwable e)
         {
            ctx.state = ServiceContext.FAILED;
            ctx.problem = e;
            log.warn("Problem destroying service " + serviceName, e);
         }
      }
   }

   /**
    * This MBean is going bye bye
    *
    * @param objectName Description of Parameter
    * @exception Exception Description of Exception
    * @jmx:managed-operation
    */
   public void remove(ObjectName objectName) throws Exception
   {
      if (objectName == null)
      {
         log.warn("Ignoring request to remove null service: ", new Exception("STACKTRACE"));
         return;
      }

      ServiceContext ctx = (ServiceContext) nameToServiceMap.get(objectName);
      if (ctx == null)
      {
         log.trace("Ignoring request to remove nonexistent service: " + objectName);
         return;
      }
      log.debug("removing service: " + objectName);

      // Notify those that think I depend on them
      Iterator iterator = ctx.iDependOn.iterator();
      while (iterator.hasNext())
      {
         ServiceContext iDependOnContext = (ServiceContext) iterator.next();
         iDependOnContext.dependsOnMe.remove(ctx);

         // Remove any context whose only reason for existence is that
         // we depend on it, i.e. it otherwise unknown to the system
         if (iDependOnContext.state == ServiceContext.NOTYETINSTALLED
               && iDependOnContext.dependsOnMe.size() == 0)
         {
            nameToServiceMap.remove(iDependOnContext.objectName);
            log.debug("Removing context for nonexistent service it is " +
                  "no longer recording dependencies: " + iDependOnContext);
         }
      }
      //We remove all traces of our dependency configuration, since we
      //don't know what will show up the next time we are deployed.
      ctx.iDependOn.clear();

      // Do we have a deployed MBean?
      if (server.isRegistered(objectName))
      {
         log.debug("removing " + objectName + " from server");

         // Remove the context, unless it is still recording dependencies
         if (ctx.dependsOnMe.size() == 0)
            nameToServiceMap.remove(objectName);
         else
         {
            log.debug("Context not removed, it is recording " +
                  "dependencies: " + ctx);
            ctx.proxy = null;
         }

         // remove the mbean from the instaled ones
         installedServices.remove(ctx);
         creator.remove(objectName);
      }
      else
      {
         // Remove the context, unless it is still recording dependencies
         installedServices.remove(ctx);
         if (ctx.dependsOnMe.size() == 0)
         {
            log.debug("removing already unregistered " + objectName + " from server");
            nameToServiceMap.remove(objectName);
         }
         else
         {
            log.debug("no need to remove " + objectName + " from server");
            ctx.proxy = null;
         }
      }
      // This context is no longer installed, but it may still exist
      // to record dependent services
      ctx.state = ServiceContext.NOTYETINSTALLED;
   }

   /**
    * Lookup the ServiceContext for the given serviceName
    *
    * @jmx.managed-operation
    */
   public ServiceContext getServiceContext(ObjectName serviceName)
   {
      ServiceContext ctx = (ServiceContext) nameToServiceMap.get(serviceName);
      return ctx;
   }
   
   /**
    * Describe <code>shutdown</code> method here.
    *
    * @jmx:managed-operation
    */
   public void shutdown()
   {
      log.debug("Stopping " + nameToServiceMap.size() + " services");

      List servicesCopy = new ArrayList(installedServices);

      int serviceCounter = 0;
      ObjectName name = null;

      ListIterator i = servicesCopy.listIterator(servicesCopy.size());
      while (i.hasPrevious())
      {
         ServiceContext ctx = (ServiceContext) i.previous();
         name = ctx.objectName;

         // Go through the full stop/destroy cycle
         try
         {
            stop(name);
         }
         catch (Throwable e)
         {
            log.error("Could not stop mbean: " + name, e);
         }
         try
         {
            destroy(name);
         }
         catch (Throwable e)
         {
            log.error("Could not destroy mbean: " + name, e);
         }
         try
         {
            remove(name);
            serviceCounter++;
         }
         catch (Throwable e)
         {
            log.error("Could not remove mbean: " + name, e);
         }
      }
      log.debug("Stopped " + serviceCounter + " services");
   }
   
   // MBeanRegistration implementation ----------------------------------------

   /**
    * #Description of the Method
    *
    * @param server Description of Parameter
    * @param name Description of Parameter
    * @return Description of the Returned Value
    * @exception Exception Description of Exception
    */
   public ObjectName preRegister(MBeanServer server, ObjectName name)
         throws Exception
   {
      this.server = server;

      creator = new ServiceCreator(server);
      configurator = new ServiceConfigurator(server, this, creator);

      // Register the ServiceController as a running service
      ServiceContext sc = this.createServiceContext(name);
      sc.state = ServiceContext.RUNNING;

      log.debug("Controller MBean online");
      return name == null ? OBJECT_NAME : name;
   }

   public void postRegister(Boolean registrationDone)
   {
      if (!registrationDone.booleanValue())
      {
         log.info("Registration of ServiceController failed");
      }
   }

   public void preDeregister()
         throws Exception
   {
   }

   public void postDeregister()
   {
      nameToServiceMap.clear();
      installedServices.clear();
      creator.shutdown();
      creator = null;
      configurator = null;
      server = null;
   }

   // Package Protected ---------------------------------------------
   
   // Create a Service Context for the service, or get one if it exists
   synchronized ServiceContext createServiceContext(ObjectName objectName)
   {
      // If it is already there just return it
      if (nameToServiceMap.containsKey(objectName))
         return (ServiceContext) nameToServiceMap.get(objectName);

      // If not create it, add it and return it
      ServiceContext ctx = new ServiceContext();
      ctx.objectName = objectName;

      // we keep track of these here
      nameToServiceMap.put(objectName, ctx);

      return ctx;
   }

   void registerDependency(ObjectName needs, ObjectName used)
   {
      log.debug("recording that " + needs + " depends on " + used);
      ServiceContext needsCtx = createServiceContext(needs);
      ServiceContext usedCtx = createServiceContext(used);


      if (!needsCtx.iDependOn.contains(usedCtx))
      {
         // needsCtx depends on usedCtx
         needsCtx.iDependOn.add(usedCtx);
         // UsedCtx needs to know I depend on him
         usedCtx.dependsOnMe.add(needsCtx);
      }
   }

   // Private -------------------------------------------------------
   
   /**
    * Register the service context and its dependencies against the microkernel.
    * If the context is already registered it does nothing.
    * 
    * @param ctx the ServiceContext to register
    * @param depends a collection of ObjectNames of services the registered service depends on
    */
   private void register(ServiceContext ctx, Collection depends) throws Exception
   {
      if (!installedServices.contains(ctx))
         installedServices.add(ctx);
      
      if (depends != null)
      {
         log.debug("adding depends in ServiceController.register: " + depends);
         for (Iterator i = depends.iterator(); i.hasNext();)
         {
            registerDependency(ctx.objectName, (ObjectName) i.next());
         }
      }

      // Get the fancy service proxy (for the lifecycle API), if needed
      if (ctx.proxy == null)
         ctx.proxy = getServiceProxy(ctx.objectName, null);
   }
   
   /**
    * Get the Service interface through which the mbean given by objectName
    * will be managed.
    *
    * @param objectName
    * @param serviceFactory
    * @return The Service value
    *
    * @throws ClassNotFoundException
    * @throws InstantiationException
    * @throws IllegalAccessException
    */
   private Service getServiceProxy(ObjectName objectName, String serviceFactory)
         throws ClassNotFoundException, InstantiationException,
         IllegalAccessException, JMException
   {
      Service service = null;
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      if (serviceFactory != null && serviceFactory.length() > 0)
      {
         Class clazz = loader.loadClass(serviceFactory);
         ServiceFactory factory = (ServiceFactory) clazz.newInstance();
         service = factory.createService(server, objectName);
      }
      else
      {
         MBeanInfo info = server.getMBeanInfo(objectName);
         MBeanOperationInfo[] opInfo = info.getOperations();
         Class[] interfaces = {Service.class};
         InvocationHandler handler = new ServiceProxy(objectName, opInfo);
         service = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(), interfaces, handler);
      }

      return service;
   }

   /**
    * Sends outs controller notifications about service lifecycle events 
    */
   private void sendControllerNotification(String type, ObjectName serviceName)
   {
      Notification notification = new Notification(type, this, super.nextNotificationSequenceNumber());
      notification.setUserData(serviceName);
      sendNotification(notification);
   }
   
   // Inner classes -------------------------------------------------

   /**
    * A mapping from the Service interface method names to the corresponding
    * index into the ServiceProxy.hasOp array.
    */
   private static HashMap serviceOpMap = new HashMap();

   /**
    * An implementation of InvocationHandler used to proxy of the Service
    * interface for mbeans. It determines which of the start/stop
    * methods of the Service interface an mbean implements by inspecting its
    * MBeanOperationInfo values. Each Service interface method that has a
    * matching operation is forwarded to the mbean by invoking the method
    * through the MBeanServer object.
    */
   public class ServiceProxy implements InvocationHandler
   {
      private boolean[] hasOp = {false, false, false, false};
      private ObjectName objectName;

      /** Whether we have the lifecycle method */
      private boolean hasJBossInternalLifecycle;

      /**
       * Go through the opInfo array and for each operation that matches on of
       * the Service interface methods set the corresponding hasOp array value
       * to true.
       *
       * @param objectName
       * @param opInfo
       */
      public ServiceProxy(ObjectName objectName, MBeanOperationInfo[] opInfo)
      {
         this.objectName = objectName;

         for (int op = 0; op < opInfo.length; op++)
         {
            MBeanOperationInfo info = opInfo[op];
            String name = info.getName();

            if (name.equals(JBOSS_INTERNAL_LIFECYCLE))
            {
               hasJBossInternalLifecycle = true;
               continue;
            }

            Integer opID = (Integer) serviceOpMap.get(name);
            if (opID == null)
            {
               continue;
            }

            // Validate that is a no-arg void return type method
            if (info.getReturnType().equals("void") == false)
            {
               continue;
            }
            if (info.getSignature().length != 0)
            {
               continue;
            }

            hasOp[opID.intValue()] = true;
         }
      }

      /**
       * Map the method name to a Service interface method index and if the
       * corresponding hasOp array element is true, dispatch the method to the
       * mbean we are proxying.
       *
       * @param proxy
       * @param method
       * @param args
       * @return             Always null.
       * @throws Throwable
       */
      public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
      {
         String name = method.getName();

         if (hasJBossInternalLifecycle)
         {
            try
            {
               server.invoke(objectName, JBOSS_INTERNAL_LIFECYCLE, new Object[] { name }, JBOSS_INTERNAL_LIFECYCLE_SIG);
               return null;
            }
            catch (Exception e)
            {
               throw JMXExceptionDecoder.decode(e);
            }
         }

         Integer opID = (Integer) serviceOpMap.get(name);

         if (opID != null && hasOp[opID.intValue()] == true)
         {
            // deal with those pesky JMX exceptions
            try
            {
               String[] sig = {};
               server.invoke(objectName, name, args, sig);
            }
            catch (Exception e)
            {
               throw JMXExceptionDecoder.decode(e);
            }
         }

         return null;
      }
   }

   /**
    * Initialize the service operation map.
    */
   static
   {
      serviceOpMap.put("create", new Integer(0));
      serviceOpMap.put("start", new Integer(1));
      serviceOpMap.put("destroy", new Integer(2));
      serviceOpMap.put("stop", new Integer(3));
   }
}
