/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2000 - 2008, 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.cache.factories;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.CacheStatus;
import org.jboss.cache.Lifecycle;
import org.jboss.cache.Version;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.config.RuntimeConfig;
import org.jboss.cache.factories.annotations.DefaultFactoryFor;
import org.jboss.cache.factories.annotations.Destroy;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.NonVolatile;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.factories.annotations.Stop;
import org.jboss.cache.util.BeanUtils;
import org.jboss.cache.util.reflect.ReflectionUtil;

import javax.management.MBeanServerFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A registry where components which have been created are stored.  Components are stored as singletons, registered under
 * a specific name.
 * <p/>
 * Components can be retrieved from the registry using {@link #getComponent(Class)}.
 * <p/>
 * Components can be registered using {@link #registerComponent(Object, Class)}, which will cause any dependencies to be
 * wired in as well.  Components that need to be created as a result of wiring will be done using {@link #getOrCreateComponent(Class)},
 * which will look up the default factory for the component type (factories annotated with the appropriate {@link DefaultFactoryFor} annotation.
 * <p/>
 * Default factories are treated as components too and will need to be wired before being used.
 * <p/>
 * The registry can exist in one of several states, as defined by the {@link CacheStatus} enumeration.  In terms of the cache,
 * state changes in the following manner:
 * <ul>
 * <li>INSTANTIATED - when first constructed</li>
 * <li>CONSTRUCTED - when created using the DefaultCacheFactory</li>
 * <li>When {@link org.jboss.cache.Cache#create()} is called, the components are rewired.</li>
 * <li>STARTED - when {@link org.jboss.cache.Cache#start()} is called</li>
 * <li>STOPPED - when {@link org.jboss.cache.Cache#stop()} is called</li>
 * <li>DESTROYED - when {@link org.jboss.cache.Cache#destroy()} is called.</li>
 * </ul>
 * <p/>
 * Cache configuration can only be changed and will only be reinjected if the cache is not in the {@link org.jboss.cache.CacheStatus#STARTED} state.
 *
 * @author Manik Surtani (<a href="mailto:manik AT jboss DOT org">manik AT jboss DOT org</a>)
 * @since 2.1.0
 */
@NonVolatile
public class ComponentRegistry implements Lifecycle
{
   /**
    * Contains class definitions of component factories that can be used to construct certain components
    */
   Map<Class, Class<? extends ComponentFactory>> defaultFactories = null;

   private static final Log log = LogFactory.getLog(ComponentRegistry.class);
   private static final boolean trace = log.isTraceEnabled();
   protected static final Object NULL_COMPONENT = new Object();

   // component and method containers
   final Map<String, Component> componentLookup = new HashMap<String, Component>();

   volatile CacheStatus state = CacheStatus.INSTANTIATED;

   /**
    * Hook to shut down the cache when the JVM exits.
    */
   private Thread shutdownHook;
   /**
    * A flag that the shutdown hook sets before calling cache.stop().  Allows stop() to identify if it has been called
    * from a shutdown hook.
    */
   private boolean invokedFromShutdownHook;

   private volatile boolean statusCheckNecessary = true;

   /**
    * Creates an instance of the component registry.  The configuration passed in is automatically registered.
    *
    * @param configuration configuration with which this is created
    */
   public ComponentRegistry(Configuration configuration, CacheSPI cache)
   {
      try
      {
         // bootstrap.
         registerDefaultClassLoader(null);
         registerComponent(this, ComponentRegistry.class);
         registerComponent(configuration, Configuration.class);
         registerComponent(new BootstrapFactory(cache, configuration, this), BootstrapFactory.class);
      }
      catch (Exception e)
      {
         throw new CacheException("Unable to construct ComponentRegistry", e);
      }
   }

   /**
    * Retrieves the state of the registry
    *
    * @return state of the registry
    */
   public CacheStatus getState()
   {
      return state;
   }

   /**
    * Wires an object instance with dependencies annotated with the {@link org.jboss.cache.factories.annotations.Inject} annotation, creating more components
    * as needed based on the Configuration passed in if these additional components don't exist in the
    * {@link ComponentRegistry}.  Strictly for components that don't otherwise live in the registry and have a lifecycle, such as Nodes.
    *
    * @param target object to wire
    * @throws ConfigurationException if there is a problem wiring the instance
    */
   public void wireDependencies(Object target) throws ConfigurationException
   {
      try
      {
         // don't use the reflection cache for wireDependencies calls since these are not managed by the ComponentRegistry
         // and may be invoked at any time, even after the cache starts.
         List<Method> methods = ReflectionUtil.getAllMethods(target.getClass(), Inject.class);

         // search for anything we need to inject
         for (Method method : methods) invokeInjectionMethod(target, method);
      }
      catch (Exception e)
      {
         throw new ConfigurationException("Unable to configure component (type: " + target.getClass() + ", instance " + target + ")", e);
      }
   }

   /**
    * Registers the default class loader.  This method *must* be called before any other components are registered,
    * typically called by bootstrap code.  Defensively, it is called in the constructor of ComponentRegistry with a null
    * parameter.
    *
    * @param loader a class loader to use by default.  If this is null, the class loader used to load this instance of ComponentRegistry is used.
    */
   public void registerDefaultClassLoader(ClassLoader loader)
   {
      registerComponent(loader == null ? getClass().getClassLoader() : loader, ClassLoader.class);
      // make sure the class loader is non-volatile, so it survives restarts.
      componentLookup.get(ClassLoader.class.getName()).nonVolatile = true;
   }

   /**
    * This is hard coded for now, since scanning the classpath for factories annotated with {@link org.jboss.cache.factories.annotations.DefaultFactoryFor}
    * does not work with all class loaders.  This is a temporary solution until a more elegant one can be designed.
    * <p/>
    * BE SURE TO ADD ANY NEW FACTORY TYPES ANNOTATED WITH DefaultFactoryFor TO THIS SET!!
    * <p/>
    *
    * @return set of known factory types.
    */
   private Set<Class<? extends ComponentFactory>> getHardcodedFactories()
   {
      Set<Class<? extends ComponentFactory>> s = new HashSet<Class<? extends ComponentFactory>>();
      s.add(BootstrapFactory.class);
      s.add(BuddyManagerFactory.class);
      s.add(EmptyConstructorFactory.class);
      s.add(InterceptorChainFactory.class);
      s.add(RuntimeConfigAwareFactory.class);
      s.add(TransactionManagerFactory.class);
      s.add(ReplicationQueueFactory.class);
      s.add(LockManagerFactory.class);
      s.add(ContextMetaFactory.class);
      s.add(NodeMetaFactory.class);
      s.add(StateTransferManagerFactory.class);
      s.add(StateTransferFactory.class);
      s.add(RegionManagerFactory.class);
      s.add(NodeMetaFactory.class);
      s.add(CommandsMetaFactory.class);
      s.add(TransactionLogFactory.class);
      return s;
   }

   /**
    * Registers a component in the registry under the given type, and injects any dependencies needed.  If a component
    * of this type already exists, it is overwritten.
    *
    * @param component component to register
    * @param type      type of component
    */
   public void registerComponent(Object component, Class type)
   {

      String name = type.getName();
      Component old = componentLookup.get(name);

      if (old != null && old.instance.equals(component))
      {
         if (trace)
         {
            log.trace("Attempting to register a component equal to one that already exists under the same name (" + name + ").  Not doing anything.");
         }
         return;
      }


      Component c;
      if (old != null)
      {
         if (trace) log.trace("Replacing old component " + old + " with new instance " + component);
         old.instance = component;
         old.methodsScanned = false;
         c = old;

         if (state == CacheStatus.STARTED) populateLifecycleMethods();
      }
      else
      {
         c = new Component();
         c.name = name;
         c.instance = component;
         if (trace) log.trace("Registering component " + c + " under name " + name);
         componentLookup.put(name, c);
      }
      c.nonVolatile = component.getClass().isAnnotationPresent(NonVolatile.class);
      addComponentDependencies(c);
      // inject dependencies for this component
      c.injectDependencies();
   }

   /**
    * Adds component dependencies for a given component, by populating {@link Component#injectionMethods}.
    *
    * @param c component to add dependencies to
    */
   protected void addComponentDependencies(Component c)
   {
      Class type = c.instance.getClass();
      List<Method> methods = ReflectionUtil.getAllMethods(type, Inject.class);
      c.injectionMethods.clear();
      c.injectionMethods.addAll(methods);
   }

   @SuppressWarnings("unchecked")
   protected void invokeInjectionMethod(Object o, Method m)
   {
      Class[] dependencies = m.getParameterTypes();
      Object[] params = new Object[dependencies.length];

      for (int i = 0; i < dependencies.length; i++)
      {
         params[i] = getOrCreateComponent(dependencies[i]);
      }

      ReflectionUtil.invokeAccessibly(o, m, params);
   }

   /**
    * Retrieves a component if one exists, and if not, attempts to find a factory capable of constructing the component
    * (factories annotated with the {@link DefaultFactoryFor} annotation that is capable of creating the component class).
    * <p/>
    * If an instance needs to be constructed, dependencies are then automatically wired into the instance, based on methods
    * on the component type annotated with {@link Inject}.
    * <p/>
    * Summing it up, component retrieval happens in the following order:<br />
    * 1.  Look for a component that has already been created and registered.
    * 2.  Look for an appropriate component that exists in the {@link Configuration} that may be injected from an external system.
    * 3.  Look for a class definition passed in to the {@link org.jboss.cache.config.Configuration} - such as an EvictionPolicy implementation
    * 4.  Attempt to create it by looking for an appropriate factory (annotated with {@link DefaultFactoryFor})
    * <p/>
    *
    * @param componentClass type of component to be retrieved.  Should not be null.
    * @return a fully wired component instance, or null if one cannot be found or constructed.
    * @throws ConfigurationException if there is a problem with consructing or wiring the instance.
    */
   protected <T> T getOrCreateComponent(Class<T> componentClass)
   {

      T component = getComponent(componentClass);

      if (component == null)
      {
         // first see if this has been injected externally.
         component = getFromConfiguration(componentClass);
         boolean attemptedFactoryConstruction = false;

         if (component == null)
         {
            // create this component and add it to the registry
            ComponentFactory factory = getFactory(componentClass);
            component = factory.construct(componentClass);
            attemptedFactoryConstruction = true;

         }

         if (component != null)
         {
            registerComponent(component, componentClass);
         }
         else if (attemptedFactoryConstruction)
         {
            if (trace) log.trace("Registering a null for component " + componentClass.getSimpleName());
            registerNullComponent(componentClass);
         }
      }

      return component;
   }

   /**
    * Retrieves a component factory instance capable of constructing components of a specified type.  If the factory doesn't
    * exist in the registry, one is created.
    *
    * @param componentClass type of component to construct
    * @return component factory capable of constructing such components
    */
   protected ComponentFactory getFactory(Class componentClass)
   {
      if (defaultFactories == null) scanDefaultFactories();
      Class<? extends ComponentFactory> cfClass = defaultFactories.get(componentClass);
      if (cfClass == null)
      {
         throw new ConfigurationException("No registered default factory for component " + componentClass + " found!");
      }
      // a component factory is a component too!  See if one has been created and exists in the registry
      ComponentFactory cf = getComponent(cfClass);
      if (cf == null)
      {
         // hasn't yet been created.  Create and put in registry
         cf = instantiateFactory(cfClass);
         if (cf == null)
         {
            throw new ConfigurationException("Unable to locate component factory for component " + componentClass);
         }
         // we simply register this factory.  Registration will take care of constructing any dependencies.
         registerComponent(cf, cfClass);
      }

      // ensure the component factory is in the STARTED state!
      Component c = componentLookup.get(cfClass.getName());
      if (c.instance != cf)
      {
         throw new ConfigurationException("Component factory " + cfClass + " incorrectly registered!");
      }
      return cf;
   }

   /**
    * Scans the class path for classes annotated with {@link org.jboss.cache.factories.annotations.DefaultFactoryFor}, and
    * analyses which components can be created by such factories.
    */
   void scanDefaultFactories()
   {
      defaultFactories = new HashMap<Class, Class<? extends ComponentFactory>>();

      Set<Class<? extends ComponentFactory>> factories = getHardcodedFactories();

      for (Class<? extends ComponentFactory> factory : factories)
      {
         DefaultFactoryFor dFFAnnotation = factory.getAnnotation(DefaultFactoryFor.class);
         for (Class targetClass : dFFAnnotation.classes()) defaultFactories.put(targetClass, factory);
      }
   }

   /**
    * No such thing as a meta factory yet.  Factories are created using this method which attempts to use an empty public
    * constructor.
    *
    * @param factory class of factory to be created
    * @return factory instance
    */
   ComponentFactory instantiateFactory(Class<? extends ComponentFactory> factory)
   {
      try
      {
         return factory.newInstance();
      }
      catch (Exception e)
      {
         // unable to get a hold of an instance!!
         throw new ConfigurationException("Unable to instantiate factory " + factory, e);
      }
   }

   /**
    * registers a special "null" component that has no dependencies.
    *
    * @param type type of component to register as a null
    */
   void registerNullComponent(Class type)
   {
      registerComponent(NULL_COMPONENT, type);
   }

   /**
    * Retrieves a component from the {@link Configuration} or {@link RuntimeConfig}.
    *
    * @param componentClass component type
    * @return component, or null if it cannot be found
    */
   @SuppressWarnings("unchecked")
   protected <T> T getFromConfiguration(Class<T> componentClass)
   {
      if (log.isDebugEnabled())
      {
         log.debug("Looking in configuration for an instance of " + componentClass + " that may have been injected from an external source.");
      }
      Method getter = BeanUtils.getterMethod(Configuration.class, componentClass);
      T returnValue = null;

      if (getter != null)
      {
         try
         {
            returnValue = (T) getter.invoke(getConfiguration());
         }
         catch (Exception e)
         {
            log.warn("Unable to invoke getter " + getter + " on Configuration.class!", e);
         }
      }

      // now try the RuntimeConfig - a legacy "registry" of sorts.
      if (returnValue == null)
      {
         getter = BeanUtils.getterMethod(RuntimeConfig.class, componentClass);
         if (getter != null)
         {
            try
            {
               returnValue = (T) getter.invoke(getConfiguration().getRuntimeConfig());
            }
            catch (Exception e)
            {
               log.warn("Unable to invoke getter " + getter + " on RuntimeConfig.class!", e);
            }
         }
      }
      return returnValue;
   }

   /**
    * Retrieves the configuration component.
    *
    * @return a Configuration object
    */
   protected Configuration getConfiguration()
   {
      // this is assumed to always be present as a part of the bootstrap/construction of a ComponentRegistry.
      return getComponent(Configuration.class);
   }

   /**
    * Retrieves a component of a specified type from the registry, or null if it cannot be found.
    *
    * @param type type to find
    * @return component, or null
    */
   @SuppressWarnings("unchecked")
   public <T> T getComponent(Class<T> type)
   {
      Component wrapper = componentLookup.get(type.getName());
      if (wrapper == null) return null;

      return (T) (wrapper.instance == NULL_COMPONENT ? null : wrapper.instance);
   }

   /**
    * Rewires components.  Can only be called if the current state is WIRED or STARTED.
    */
   public void rewire()
   {
      // need to re-inject everything again.
      for (Component c : new HashSet<Component>(componentLookup.values()))
      {
         // inject dependencies for this component
         c.injectDependencies();
      }
   }

   /**
    * Scans each registered component for lifecycle methods, and adds them to the appropriate lists, and then sorts them
    * by priority.
    */
   private void populateLifecycleMethods()
   {
      for (Component c : componentLookup.values())
      {
         if (!c.methodsScanned)
         {
            c.methodsScanned = true;
            c.startMethods.clear();
            c.stopMethods.clear();
            c.destroyMethods.clear();

            List<Method> methods = ReflectionUtil.getAllMethods(c.instance.getClass(), Start.class);
            for (Method m : methods)
            {
               PrioritizedMethod em = new PrioritizedMethod();
               em.component = c;
               em.method = m;
               em.priority = m.getAnnotation(Start.class).priority();
               c.startMethods.add(em);
            }

            methods = ReflectionUtil.getAllMethods(c.instance.getClass(), Stop.class);
            for (Method m : methods)
            {
               PrioritizedMethod em = new PrioritizedMethod();
               em.component = c;
               em.method = m;
               em.priority = m.getAnnotation(Stop.class).priority();
               c.stopMethods.add(em);
            }

            methods = ReflectionUtil.getAllMethods(c.instance.getClass(), Destroy.class);
            for (Method m : methods)
            {
               PrioritizedMethod em = new PrioritizedMethod();
               em.component = c;
               em.method = m;
               em.priority = m.getAnnotation(Destroy.class).priority();
               c.destroyMethods.add(em);
            }
         }
      }
   }

   /**
    * Removes any components not annotated as @NonVolatile.
    */
   public void resetNonVolatile()
   {
      // destroy all components to clean up resources
      for (Component c : new HashSet<Component>(componentLookup.values()))
      {
         // the component is volatile!!
         if (!c.nonVolatile)
         {
            componentLookup.remove(c.name);
         }
      }

      if (trace) log.trace("Reset volatile components.  Registry now contains " + componentLookup.keySet());
   }

   // ------------------------------ START: Publicly available lifecycle methods -----------------------------
   //   These methods perform a check for appropriate transition and then delegate to similarly named internal methods.

   /**
    * Creates the components needed by a cache instance and sets the cache status to {@link org.jboss.cache.CacheStatus#CREATED}
    * when it is done.
    */
   public void create()
   {
      if (!state.createAllowed())
      {
         if (state.needToDestroyFailedCache())
         {
            destroy();
         }
         else
         {
            return;
         }
      }

      try
      {
         internalCreate();
      }
      catch (Throwable t)
      {
         handleLifecycleTransitionFailure(t);
      }
   }

   /**
    * This starts the components in the cache, connecting to channels, starting service threads, etc.  If the cache is
    * not in the {@link org.jboss.cache.CacheStatus#CREATED} state, {@link #create()} will be invoked first.
    */
   public void start()
   {
      boolean createdInStart = false;
      if (!state.startAllowed())
      {
         if (state.needToDestroyFailedCache())
         {
            destroy(); // this will take us back to DESTROYED
         }

         if (state.needCreateBeforeStart())
         {
            create();
            createdInStart = true;
         }
         else
         {
            return;
         }
      }

      try
      {
         internalStart(createdInStart);
      }
      catch (Throwable t)
      {
         handleLifecycleTransitionFailure(t);
      }
   }

   /**
    * Stops the cache and sets the cache status to {@link org.jboss.cache.CacheStatus#STOPPED} once it is done.  If the cache is not in
    * the {@link org.jboss.cache.CacheStatus#STARTED} state, this is a no-op.
    */
   public void stop()
   {
      if (!state.stopAllowed())
      {
         return;
      }

      // Trying to stop() from FAILED is valid, but may not work
      boolean failed = state == CacheStatus.FAILED;

      try
      {
         internalStop();
      }
      catch (Throwable t)
      {
         if (failed)
         {
            log.warn("Attempted to stop() from FAILED state, but caught exception; try calling destroy()", t);
         }
         failed = true;
         handleLifecycleTransitionFailure(t);
      }
      finally
      {
         if (!failed) state = CacheStatus.STOPPED;
      }
   }

   /**
    * Destroys the cache and frees up any resources.  Sets the cache status to {@link CacheStatus#DESTROYED} when it is done.
    * <p/>
    * If the cache is in {@link org.jboss.cache.CacheStatus#STARTED} when this method is called, it will first call {@link #stop()}
    * to stop the cache.
    */
   public void destroy()
   {
      if (!state.destroyAllowed())
      {
         if (state.needStopBeforeDestroy())
         {
            try
            {
               stop();
            }
            catch (CacheException e)
            {
               log.warn("Needed to call stop() before destroying but stop() threw exception. Proceeding to destroy", e);
            }
         }
         else
         {
            return;
         }
      }

      try
      {
         internalDestroy();
      }
      finally
      {
         // We always progress to destroyed
         state = CacheStatus.DESTROYED;
      }
   }
   // ------------------------------ END: Publicly available lifecycle methods -----------------------------

   // ------------------------------ START: Actual internal lifecycle methods --------------------------------

   /**
    * Sets the cacheStatus to FAILED and rethrows the problem as one
    * of the declared types. Converts any non-RuntimeException Exception
    * to CacheException.
    *
    * @param t throwable thrown during failure
    */
   private void handleLifecycleTransitionFailure(Throwable t)
   {
      state = CacheStatus.FAILED;
      if (t instanceof CacheException)
      {
         throw (CacheException) t;
      }
      else if (t instanceof RuntimeException)
      {
         throw (RuntimeException) t;
      }
      else if (t instanceof Error)
      {
         throw (Error) t;
      }
      else
      {
         throw new CacheException(t);
      }
   }

   /**
    * The actual create implementation.
    */
   private void internalCreate()
   {
      state = CacheStatus.CREATING;
      resetNonVolatile();
      rewire();
      state = CacheStatus.CREATED;
   }

   private void internalStart(boolean createdInStart) throws CacheException, IllegalArgumentException
   {
      if (!createdInStart)
      {
         // re-wire all dependencies in case stuff has changed since the cache was created
         // remove any components whose construction may have depended upon a configuration that may have changed.
         resetNonVolatile();
         rewire();
      }

      state = CacheStatus.STARTING;

      // start all internal components
      // first cache all start, stop and destroy methods.
      populateLifecycleMethods();

      List<PrioritizedMethod> startMethods = new ArrayList<PrioritizedMethod>(componentLookup.size());
      for (Component c : componentLookup.values()) startMethods.addAll(c.startMethods);

      // sort the start methods by priority
      Collections.sort(startMethods);

      // fire all START methods according to priority


      for (PrioritizedMethod em : startMethods) em.invoke();

      addShutdownHook();

      log.info("JBoss Cache version: " + Version.printVersion());
      state = CacheStatus.STARTED;
   }

   private void addShutdownHook()
   {
      ArrayList al = MBeanServerFactory.findMBeanServer(null);
      boolean registerShutdownHook = (getConfiguration().getShutdownHookBehavior() == Configuration.ShutdownHookBehavior.DEFAULT && al.size() == 0)
            || getConfiguration().getShutdownHookBehavior() == Configuration.ShutdownHookBehavior.REGISTER;

      if (registerShutdownHook)
      {
         if (log.isTraceEnabled())
         {
            log.trace("Registering a shutdown hook.  Configured behavior = " + getConfiguration().getShutdownHookBehavior());
         }
         shutdownHook = new Thread()
         {
            @Override
            public void run()
            {
               try
               {
                  invokedFromShutdownHook = true;
                  ComponentRegistry.this.stop();
               }
               finally
               {
                  invokedFromShutdownHook = false;
               }
            }
         };

         Runtime.getRuntime().addShutdownHook(shutdownHook);
      }
      else
      {
         if (log.isTraceEnabled())
         {
            log.trace("Not registering a shutdown hook.  Configured behavior = " + getConfiguration().getShutdownHookBehavior());
         }
      }
   }

   /**
    * Actual stop
    */
   private void internalStop()
   {
      state = CacheStatus.STOPPING;
      // if this is called from a source other than the shutdown hook, deregister the shutdown hook.
      if (!invokedFromShutdownHook && shutdownHook != null) Runtime.getRuntime().removeShutdownHook(shutdownHook);

      List<PrioritizedMethod> stopMethods = new ArrayList<PrioritizedMethod>(componentLookup.size());
      for (Component c : componentLookup.values()) stopMethods.addAll(c.stopMethods);

      Collections.sort(stopMethods);

      // fire all STOP methods according to priority
      for (PrioritizedMethod em : stopMethods) em.invoke();

      state = CacheStatus.STOPPED;
   }

   /**
    * Actual destroy
    */
   private void internalDestroy()
   {

      state = CacheStatus.DESTROYING;

      resetNonVolatile();

      List<PrioritizedMethod> destroyMethods = new ArrayList<PrioritizedMethod>(componentLookup.size());
      for (Component c : componentLookup.values()) destroyMethods.addAll(c.destroyMethods);

      Collections.sort(destroyMethods);

      // fire all DESTROY methods according to priority
      for (PrioritizedMethod em : destroyMethods) em.invoke();

      state = CacheStatus.DESTROYED;
   }

   // ------------------------------ END: Actual internal lifecycle methods --------------------------------

   /**
    * Asserts whether invocations are allowed on the cache or not.  Returns <tt>true</tt> if invocations are to be allowed,
    * <tt>false</tt> otherwise.  If the origin of the call is remote and the cache status is {@link org.jboss.cache.CacheStatus#STARTING},
    * this method will block for up to {@link org.jboss.cache.config.Configuration#getStateRetrievalTimeout()} millis, checking
    * for a valid state.
    *
    * @param SkipBlockUntilStart true if the call should not wait for the cache to start (typical local use) and false if we should try and wait (typical remote lookup use)
    * @return true if invocations are allowed, false otherwise.
    */
   public boolean invocationsAllowed(boolean SkipBlockUntilStart)
   {
      if (trace) log.trace("Testing if invocations are allowed.");
      if (state.allowInvocations()) return true;

      // if this is a locally originating call and the cache is not in a valid state, return false.
      if (SkipBlockUntilStart) return false;

      if (trace) log.trace("Is remotely originating.");

      // else if this is a remote call and the status is STARTING, wait until the cache starts.
      if (statusCheckNecessary)
      {
         if (state == CacheStatus.STARTING)
         {
            if (trace) log.trace("Cache is starting; block.");
            try
            {
               blockUntilCacheStarts();
               return true;
            }
            catch (InterruptedException e)
            {
               Thread.currentThread().interrupt();
            }
         }
         else
         {
            log.warn("Received a remote call but the cache is not in STARTED state - ignoring call.");
         }
      }

      return false;
   }

   /**
    * Blocks until the current cache instance is in its {@link org.jboss.cache.CacheStatus#STARTED started} phase.  Blocks
    * for up to {@link org.jboss.cache.config.Configuration#getStateRetrievalTimeout()} milliseconds, throwing an IllegalStateException
    * if the cache doesn't reach this state even after this maximum wait time.
    *
    * @throws InterruptedException  if interrupted while waiting
    * @throws IllegalStateException if even after waiting the cache has not started.
    */
   private void blockUntilCacheStarts() throws InterruptedException, IllegalStateException
   {
      int pollFrequencyMS = 100;
      long startupWaitTime = getConfiguration().getStateRetrievalTimeout();
      long giveUpTime = System.currentTimeMillis() + startupWaitTime;

      while (System.currentTimeMillis() < giveUpTime)
      {
         if (state.allowInvocations()) break;
         Thread.sleep(pollFrequencyMS);
      }

      // check if we have started.
      if (!state.allowInvocations())
      {
         throw new IllegalStateException("Cache not in STARTED state, even after waiting " + getConfiguration().getStateRetrievalTimeout() + " millis.");
      }
   }

   /**
    * A wrapper representing a component in the registry
    */
   public class Component
   {
      /**
       * A reference to the object instance for this component.
       */
      Object instance;
      /**
       * The name of the component
       */
      String name;
      boolean methodsScanned;
      /**
       * List of injection methods used to inject dependencies into the component
       */
      List<Method> injectionMethods = new ArrayList<Method>(2);
      List<PrioritizedMethod> startMethods = new ArrayList<PrioritizedMethod>(2);
      List<PrioritizedMethod> stopMethods = new ArrayList<PrioritizedMethod>(2);
      List<PrioritizedMethod> destroyMethods = new ArrayList<PrioritizedMethod>(2);
      /**
       * If true, then this component is not flushed before starting the ComponentRegistry.
       */
      boolean nonVolatile;

      @Override
      public String toString()
      {
         return "Component{" +
               "instance=" + instance +
               ", name=" + name +
               ", nonVolatile=" + nonVolatile +
               '}';
      }

      /**
       * Injects dependencies into this component.
       */
      public void injectDependencies()
      {
         for (Method m : injectionMethods) invokeInjectionMethod(instance, m);
      }

      public Object getInstance()
      {
         return instance;
      }

      public String getName()
      {
         return name;
      }
   }


   /**
    * Wrapper to encapsulate a method along with a priority
    */
   static class PrioritizedMethod implements Comparable<PrioritizedMethod>
   {
      Method method;
      Component component;
      int priority;

      public int compareTo(PrioritizedMethod o)
      {
         return (priority < o.priority ? -1 : (priority == o.priority ? 0 : 1));
      }

      void invoke()
      {
         ReflectionUtil.invokeAccessibly(component.instance, method, null);
      }

      @Override
      public String toString()
      {
         return "PrioritizedMethod{" +
               "method=" + method +
               ", priority=" + priority +
               '}';
      }
   }

   /**
    * Returns an immutable set contating all the components that exists in the reporsitory at this moment.
    */
   public Set<Component> getRegisteredComponents()
   {
      HashSet<Component> defensiveCopy = new HashSet<Component>(componentLookup.values());
      return Collections.unmodifiableSet(defensiveCopy);
   }

   public void setStatusCheckNecessary(boolean statusCheckNecessary)
   {
      this.statusCheckNecessary = statusCheckNecessary;
   }
}
