Package org.jboss.cache.factories

Source Code of org.jboss.cache.factories.ComponentRegistry$Bootstrap

package org.jboss.cache.factories;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheImpl;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.ConfigurationException;
import org.jboss.cache.config.RuntimeConfig;
import static org.jboss.cache.factories.ComponentRegistry.State.*;
import org.jboss.cache.factories.annotations.CacheInjectionMethods;
import org.jboss.cache.factories.annotations.ComponentName;
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.Start;
import org.jboss.cache.factories.annotations.Stop;
import org.jboss.cache.invocation.RemoteCacheInvocationDelegate;
import org.jboss.cache.util.BeanUtils;
import org.jboss.cache.util.reflect.CachedMethod;
import org.jboss.cache.util.reflect.ReflectionUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
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.  When retrieving components from the registry, callers may specify a component name or just the type
* of component needed (in which case the fully qualified class name is used as the component name).
* <p/>
* Components can be retrieved from the registry using {@link #getComponent(Class)}, or they can be constructed using
* {@link #getOrCreateComponent(String, Class)} which will scan for default factories and attempt to use such
* factpries to create the component if needed.
* <p/>
* Default factories are treated as components too and will need to be wired and started before being used.
* <p/>
* Components can exist in one of 4 states, as defined by the {@link State} enumeration.  The component registry also
* has a state for the overall component set.  While some components may move to the {@link org.jboss.cache.factories.ComponentRegistry.State#STARTED}
* state before others (such as factories to create other components) the ComponentRegistry's overall state depicts the lowest
* possible form for all components.
* <p/>
* In terms of the cache, overall state changes in the following manner:
* <ul>
* <li>CONSTRUCTED - when created using the DefaultCacheFactory</li>
* <li>WIRED - when {@link org.jboss.cache.Cache#create()} is called</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>
* </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@jboss.org">manik@jboss.org</a>)
* @since 2.1.0
*/
public class ComponentRegistry
{
   static final Object NULL_COMPONENT = new Object();

   State overallState = CONSTRUCTED;

   /**
    * The registry of components.  Components are stored under their name.
    */
   Map<String, Component> componentLookup = new HashMap<String, Component>();

   /**
    * Contains class definitions of component factories that can be used to construct certain components
    */
   Map<Class, Class<? extends ComponentFactory>> defaultFactories = null;

   private static Log log = LogFactory.getLog(ComponentRegistry.class);
   private Bootstrap bootstrap;

   // cache of reflection methods to call during injections.  These will be emptied when start() is called.
   Map<Class, List<CachedMethod>> shortTermMethodCache = null;
   // these will hang around longer - for components that are frequently created during normal operation.
   Map<Class, List<CachedMethod>> longTermMethodCache = null;

   /**
    * Creates an instance of the component registry.  The configuration passed in is automatically registered.
    *
    * @param configuration
    */
   public ComponentRegistry(Configuration configuration)
   {
      // bootstrap.
      registerDefaultClassLoader(null);
      registerComponent(this, ComponentRegistry.class);
      registerComponent(configuration, Configuration.class);
   }

   /**
    * 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("deployerClassLoader", loader == null ? getClass().getClassLoader() : loader, ClassLoader.class);
   }


   public State getOverallState()
   {
      return overallState;
   }

   /**
    * 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(BuddyManagerFactory.class);
      s.add(EmptyConstructorFactory.class);
      s.add(InterceptorChainFactory.class);
      s.add(RuntimeConfigAwareFactory.class);
      s.add(TransactionManagerFactory.class);
      s.add(ReplicationQueueFactory.class);
      return s;
   }


   /**
    * Adds a singleton instance to the registry.  Note that if an instance of this component already exists in the
    * registry it is overwritten.  The instance is registered under type <tt>component.getClass()</tt>.  If the component
    * implements an interface or extends an abstract class, it may make more sense to use {@link #registerComponent(String, Object, Class)}
    * <p/>
    * The status of the component is updated th the overall state of the registry, which may involve calling of methods annotated
    * with {@link org.jboss.cache.factories.annotations.Inject}, {@link org.jboss.cache.factories.annotations.Start} or
    * {@link org.jboss.cache.factories.annotations.Stop}.
    * <p/>
    *
    * @param component component to add to the registry.
    */
   public void registerComponent(Object component, Class type)
   {
      registerComponent(type.getName(), component, type);
   }

   /**
    * Adds an instance component to the registry, registering the component under the given name.  If an instance already
    * exists in the registry under the given name, it will be overwritten.
    * <p/>
    * The status of the component is updated th the overall state of the registry, which may involve calling of methods annotated
    * with {@link org.jboss.cache.factories.annotations.Inject}, {@link org.jboss.cache.factories.annotations.Start} or
    * {@link org.jboss.cache.factories.annotations.Stop}.
    * <p/>
    *
    * @param name      name of the instance
    * @param component component to add
    */
   public void registerComponent(String name, Object component, Class type)
   {
      // this will make sure all dependent components are stopped or set to CONSTRUCTED so they can be re-wired later.
      Component c = new Component(name, component, type);
      Component old = componentLookup.get(name);
      if (log.isTraceEnabled())
         log.trace("Registering component " + c + " under name " + name + " replacing old component " + old);
      if (old != null)
      {
         // if they are equal don't bother
         if (old.instance.equals(component))
         {
            if (log.isTraceEnabled())
               log.trace("Attempting to register a component equal to one that already exists under the same name (" + name + ").  Not doing anything.");
            return;
         }
         // unregister the old component so that components that depend on it can be stopped if necessary
         unregisterComponent(name);
         // components that depended on the old component should now depend on the new one.
         c.dependencyFor.addAll(old.dependencyFor);
      }
      componentLookup.put(name, c);

      addComponentDependencies(c, old == null);

      State stateToMoveTo = overallState == null ? CONSTRUCTED : overallState;
      c.changeState(stateToMoveTo);

      // make sure any other omponents that have inadvertently been stopped are now restarted.
      if (old != null)
      {
         for (Component comp : old.dependencyFor)
         {
            if (comp.state != stateToMoveTo) comp.changeState(stateToMoveTo);
         }
      }
   }

   protected void addComponentDependencies(Component c, boolean firstTimeAdded)
   {
      // build any dependent components if necessary
      for (Component d : c.dependencies)
      {
         getOrCreateComponent(d.name, d.type);
         Component dependencyComponent = componentLookup.get(d.name);
         if (dependencyComponent != null) dependencyComponent.dependencyFor.add(c);
      }

      if (firstTimeAdded)
      {
         // loop through all other components already registered and make sure the current component's dependencyFor map is accurate
         for (Component other : componentLookup.values())
         {
            if (other.dependencies.contains(c)) c.dependencyFor.add(other);
         }
      }

   }

   public <T> T getComponent(Class<T> c)
   {
      return getComponent(c.getName(), c);
   }

   /**
    * Retrieves a named component which can be assigned to the type passed in.  Will return a null if no such component
    * is registered
    *
    * @param c type to be able to assign component to.
    * @return component, if found, or a null otherwise.
    */
   @SuppressWarnings("unchecked")
   public <T> T getComponent(String name, Class<T> c)
   {
      Component wrapper = componentLookup.get(name);
      if (wrapper == null) return null;

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

      if (component == null || c.isAssignableFrom(component.getClass())) return component;
      else
         throw new ConfigurationException("Component registered under " + name + " is of type " + component.getClass() + " and cannot be assigned to " + c);
   }

   /**
    * Retrieves a named component if one exists, and if not, attempts to find a factory capable of constructing the component
    * (factories annotated with the {@link org.jboss.cache.factories.annotations.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 org.jboss.cache.factories.annotations.Inject}.
    * <p/>
    * Summing it up, component retrieval happens in the following order:<br />
    * 1.  Look for an appropriate component that exists in the {@link Configuration} that may be injected from an external system.
    * 2.  Look for a class definition passed in to the {@link org.jboss.cache.config.Configuration} - such as an EvictionPolicy implementation
    * 3.  Attempt to create it by looking for an appropriate factory (annotated with {@link org.jboss.cache.factories.annotations.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.
    */
   public <T> T getOrCreateComponent(Class<T> componentClass)
   {
      return getOrCreateComponent(null, componentClass);
   }

   /**
    * Retrieves a named component if one exists, and if not, attempts to find a factory capable of constructing the component
    * (factories annotated with the {@link org.jboss.cache.factories.annotations.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 org.jboss.cache.factories.annotations.Inject}.
    * <p/>
    * Summing it up, component retrieval happens in the following order:<br />
    * 1.  Look for an appropriate component that exists in the {@link Configuration} that may be injected from an external system.
    * 2.  Look for a class definition passed in to the {@link org.jboss.cache.config.Configuration} - such as an EvictionPolicy implementation
    * 3.  Attempt to create it by looking for an appropriate factory (annotated with {@link org.jboss.cache.factories.annotations.DefaultFactoryFor})
    * <p/>
    *
    * @param componentName  name of component to be created.  If null, uses the fully qualified class name as component name.
    * @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.
    */
   public <T> T getOrCreateComponent(String componentName, Class<T> componentClass)
   {
      T component = getComponent(componentName == null ? componentClass.getName() : componentName, componentClass);

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

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

         }

         String componentNameToUse = componentName == null ? componentClass.getName() : componentName;

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

   // registers a special "null" component that has no dependencies.
   void registerNullComponent(String componentName, Class type)
   {
      registerComponent(componentName, NULL_COMPONENT, type);
   }

   private boolean isNonBootstrapClass(Class<?> componentClass)
   {
      return !(componentClass.equals(CacheSPI.class) || componentClass.equals(CacheImpl.class) || componentClass.equals(Cache.class)
            || componentClass.equals(ComponentRegistry.class) || componentClass.equals(Configuration.class));
   }

   @SuppressWarnings("unchecked")
         <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;
   }

   Configuration getConfiguration()
   {
      // this is assumed to always be present as a part of the bootstrap/construction of a ComponentRegistry.
      return getComponent(Configuration.class);
   }

   /**
    * Updates (re-injects) any dependencies needed by all components already in the registry.
    */
   public void updateDependencies()
   {
      State originalState = overallState;
      moveComponentsToState(overallState == STARTED ? STOPPED : CONSTRUCTED);
      moveComponentsToState(originalState);
      // re- add a few key components - this is a hack for now
      CacheImpl ci = getComponent(CacheImpl.class);
      CacheSPI rcid = getComponent("remoteDelegate", RemoteCacheInvocationDelegate.class);
      CacheSPI spi = getComponent(CacheSPI.class.getName(), CacheSPI.class);

      unregisterComponent(CacheImpl.class);
      unregisterComponent(CacheSPI.class.getName());
      unregisterComponent("remoteDelegate");

      overallState = CONSTRUCTED;

      registerComponent(CacheImpl.class.getName(), ci, CacheImpl.class);
      registerComponent(CacheSPI.class.getName(), spi, CacheSPI.class);
      registerComponent("remoteDelegate", rcid, RemoteCacheInvocationDelegate.class);

      overallState = originalState;
      moveComponentsToState(overallState);
   }

   /**
    * Removes a component from the registry.  If the component has already been injected into other components, you should
    * call {@link #updateDependencies()} to ensure dependencies are updated.
    *
    * @param clazz class of component to remove.
    */
   public void unregisterComponent(Class<?> clazz)
   {
      unregisterComponent(clazz.getName());
   }

   /**
    * Removes a component from the registry.  If the component has already been injected into other components, you should
    * call {@link #updateDependencies()} to ensure dependencies are updated.
    *
    * @param name name of the component to remove.
    */
   public void unregisterComponent(String name)
   {
      Component c = componentLookup.remove(name);
      if (c != null) c.changeState(c.state == STARTED ? STOPPED : CONSTRUCTED);
   }

   /**
    * 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
   {
      //if (log.isTraceEnabled()) log.trace("Inspecting class " + target.getClass());
      try
      {
         // look in caches first
         List<CachedMethod> methods = lookupInjectionMethods(target.getClass());
         //if (log.isTraceEnabled()) log.trace("Found method set containing " + methods.size() + " methods that need injection: " + methods);

         // search for anything we need to inject
         for (CachedMethod method : methods)
         {
            //if (log.isTraceEnabled()) log.trace("Method " + method + " needs some other components injected!");
            performInjection(method, target);
         }
      }
      catch (Exception e)
      {
         throw new ConfigurationException("Unable to configure component (type: " + target.getClass() + ", instance " + target + ")", e);
      }
   }

   private List<CachedMethod> lookupInjectionMethods(Class type)
   {
      if (longTermMethodCache != null && longTermMethodCache.containsKey(type)) return longTermMethodCache.get(type);
      if (shortTermMethodCache != null && shortTermMethodCache.containsKey(type)) return shortTermMethodCache.get(type);

      List<CachedMethod> methods = ReflectionUtil.getAllCachedMethods(type, Inject.class);
      if (type.isAnnotationPresent(CacheInjectionMethods.class))
      {
         if (longTermMethodCache == null) longTermMethodCache = new HashMap<Class, List<CachedMethod>>();
         longTermMethodCache.put(type, methods);
      }
      else
      {
         if (shortTermMethodCache == null) shortTermMethodCache = new HashMap<Class, List<CachedMethod>>();
         shortTermMethodCache.put(type, methods);
      }

      return methods;
   }

   /**
    * Looks through the parameter list of the given method, attempts to locate parameters that fit the types that may
    * exist in the {@link ComponentRegistry}, and then calls the method on the target object with the necessary parameters.
    *
    * @param method Method to call
    * @param target Instance on which to call the method
    * @throws IllegalAccessException if the method cannot be called
    * @throws java.lang.reflect.InvocationTargetException
    *                                if the method cannot be called
    */
   @SuppressWarnings("unchecked")
   private <T> void performInjection(CachedMethod method, T target) throws IllegalAccessException, InvocationTargetException
   {
      Class[] parameterTypes = method.getParameterTypes();
      List<Component> componentsToInject = getDeclaredDependencies(method);

      Object[] parameters = new Object[parameterTypes.length];

      for (int i = 0; i < parameterTypes.length; i++)
      {
         parameters[i] = getComponent(componentsToInject.get(i).name, parameterTypes[i]);
      }

      Method reflectMethod = method.getMethod();
      // make sure we set this method to be accessible, so we can call private, package and protected
      // methods rather than just public ones.
      reflectMethod.setAccessible(true);

      // invoke the method with the parameters we've worked out.
      reflectMethod.invoke(target, parameters);
   }

   private String extractComponentName(Annotation[] annotationsOnParameter)
   {
      for (Annotation a : annotationsOnParameter)
      {
         if (a instanceof ComponentName)
         {
            ComponentName cn = (ComponentName) a;
            return cn.value();
         }
      }
      return null;
   }

   private List<Component> getDeclaredDependencies(CachedMethod method)
   {
      List<Component> dependencies = new LinkedList<Component>();
      Class[] parameterTypes = method.getParameterTypes();
      Annotation[][] annotationsOnParams = method.getParameterAnnotations();
      for (int i = 0; i < parameterTypes.length; i++)
      {
         String componentName = extractComponentName(annotationsOnParams[i]);
         String componentNameToUse = componentName == null ? parameterTypes[i].getName() : componentName;
         // if the component exists in the lookup, use it.  Otherwise use a placeholder which will be constructed later, on demand.
         Component d = componentLookup.containsKey(componentNameToUse) ? componentLookup.get(componentNameToUse) : new Component(componentNameToUse, parameterTypes[i]);
         dependencies.add(d);
      }
      return dependencies;
   }

   /**
    * 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.  Always changes the state of any factory to {@link State#STARTED} before returning.
    *
    * @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)
         {
            // we simply register this factory.  Registration will take care of constructing any dependencies.
            registerComponent(cf, cfClass);
         }
      }

      if (cf == null)
         throw new ConfigurationException("Unable to locate component factory for component " + componentClass);

      // 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!");
      c.changeState(STARTED);
      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);
      }
   }

   /**
    * Wipes everything in the registry.  Use with care.
    */
   public void reset()
   {
      // the bootstrap classes
      Component deployerClassLoader = componentLookup.get("deployerClassLoader");
      Component spi = componentLookup.get(CacheSPI.class.getName());
      Component impl = componentLookup.get(CacheImpl.class.getName());
      Component conf = componentLookup.get(Configuration.class.getName());
      Component cr = componentLookup.get(ComponentRegistry.class.getName());

      // destroy all components to clean up resources
      for (Component c : componentLookup.values()) c.changeState(DESTROYED);

      // remove from componentLookup
      componentLookup.clear();

      // bootstrap components
      deployerClassLoader.changeState(CONSTRUCTED);
      spi.changeState(CONSTRUCTED);
      impl.changeState(CONSTRUCTED);
      conf.changeState(CONSTRUCTED);
      cr.changeState(CONSTRUCTED);

      bootstrap = new Bootstrap((ClassLoader) deployerClassLoader.instance, (CacheImpl) impl.instance, (CacheSPI) spi.instance,
            (ComponentRegistry) cr.instance, (Configuration) conf.instance);

      overallState = null;
   }

   /**
    * Starts all components that contain the {@link Start} annotation.
    */
   public void start()
   {
      moveComponentsToState(STARTED);
      shortTermMethodCache = null;
   }

   /**
    * Stops all components that contain the {@link Stop} annotation.
    */
   public void stop()
   {
      moveComponentsToState(STOPPED);
   }

   /**
    * Injects dependencies to all components that require injection.
    */
   public void wire()
   {
      moveComponentsToState(WIRED);
   }

   void moveComponentsToState(State state)
   {
      if (overallState == null && bootstrap != null && !bootstrap.isBootstrapped())
      {
         // we have been destroyed!  Need to bootstrap again.
         bootstrap.bootstrap();
      }

      for (Component c : componentLookup.values())
      {
         c.changeState(state);
      }
      overallState = state;
   }

   /**
    * Represents the state of a component
    */
   public enum State
   {
      DESTROYED, STOPPED, CONSTRUCTED, WIRED, STARTED;

      /**
       * Tests whether the current state is "greater" than the state passed in
       *
       * @param other state to compare with
       * @return true if the current state is "greater" than the state passed in
       */
      boolean isGreaterThan(State other)
      {
         return this.ordinal() > other.ordinal();
      }

      /**
       * Tests whether the current state is "less" than the state passed in
       *
       * @param other state to compare with
       * @return true if the current state is "less" than the state passed in
       */
      boolean isLessThan(State other)
      {
         return this.ordinal() < other.ordinal();
      }

      /**
       * @param other state to compare with
       * @return the absolute difference in ordinal places between 2 states
       */
      int absoluteDifference(State other)
      {
         return Math.abs(this.ordinal() - other.ordinal());
      }
   }

   /**
    * Represents a component in the registry, along with state and dependencies.
    */
   class Component
   {
      Object instance;
      String name;
      Class type;
      State state = CONSTRUCTED;
      Set<Component> dependencies = new HashSet<Component>(3);
      Set<Component> dependencyFor = new HashSet<Component>(3);
      boolean deepRecursionDetector = false;

      public Component(String name, Class type)
      {
         this.name = name;
         this.type = type;
      }

      /**
       * Constructs a Component out of an instance and a name.  Scans instance for dependencies and populates
       * the "dependencies" and "dependencyFor" collections.
       *
       * @param name     name of component
       * @param instance component instance
       */
      public Component(String name, Object instance, Class type)
      {
         this(name, type);
         this.instance = instance;

         // now scan the instance for all dependencies.
         List<CachedMethod> injectionMethods = lookupInjectionMethods(instance.getClass());

         // now for each injection method, get dependencies
         for (CachedMethod m : injectionMethods) dependencies.addAll(getDeclaredDependencies(m));
      }

      /**
       * Changes the state of a component - along with all dependent components - to a new state.  This method is recursion
       * and cyclic dependency safe.
       *
       * @param newState new state to move component to
       */
      void changeState(State newState)
      {
         // Deep recursion is when this component depends on another component which in turn may depend on this component again.
         // if this is encountered then break out of the recursive loop.
         if (state != newState && !deepRecursionDetector)
         {
            // Step by step.  If the new state is > 1 step away from the current state, change state gradually.
            boolean increase = newState.isGreaterThan(state);
            int numSteps = newState.absoluteDifference(state);

            while (numSteps > 1)
            {
               changeState(State.values()[state.ordinal() + (increase ? 1 : -1)]);
               numSteps = newState.absoluteDifference(state);
            }

            // now we update the state of dependent components accordingly.
            Set<Component> dependentComponents = new HashSet<Component>();
            Set<Component> shallowCyclic = new HashSet<Component>();

            if (increase)
            {
               // if I am moving to a "higher" state make sure all components that I depend on have moved to that state as well.
               dependentComponents.addAll(dependencies);
            }
            else
            {
               // if I am moving to a "lower" state make sure all components depend on me have moved to that state as well.
               dependentComponents.addAll(dependencyFor);
            }

            // switch on the deep recursion detector
            deepRecursionDetector = true;
            for (Component d : dependentComponents)
            {
               if (d != null)
               {
                  if (d.instance == null)
                  {
                     // this is a "hollow" component that has not been constructed yet.  Another "constructed" version probably exists in the
                     // componentLookup.  Make sure we replace this.
                     Component c = componentLookup.get(d.name);
                     if (increase)
                     {
                        dependencies.remove(d);
                        dependencies.add(c);
                     }
                     else
                     {
                        dependencyFor.remove(d);
                        dependencies.add(c);
                     }
                     d = c;
                  }

                  if (d != null)
                  {
                     if (isShallowCyclic(d))
                     {
                        // don't process shallow cyclic deps here - shoud do that after we set our state.
                        shallowCyclic.add(d);
                     }
                     else
                     {
                        // of we are "moving up" - only do this if the component is lower than what is needed.
                        if ((increase && newState.isGreaterThan(d.state)) || (!increase && newState.isLessThan(d.state)))
                        {
                           d.changeState(newState);
                        }
                     }
                  }
               }
            }

            // NOW we update our state.
            switch (newState)
            {
               case STOPPED:
                  stop();
                  break;
               case WIRED:
                  if (increase) wire();
                  break;
               case STARTED:
                  start();
                  break;
               case DESTROYED:
                  destroy();
                  break;
               case CONSTRUCTED:
                  // nothing to do here.
            }

            state = newState;

            // now process the shallow cyclic deps
            for (Component c : shallowCyclic)
            {
               // of we are "moving up" - only do this if the component is lower than what is needed.
               if ((increase && newState.isGreaterThan(c.state)) || (!increase && newState.isLessThan(c.state)))
                  c.changeState(newState);
            }

            // make sure we switch off the deep recursion detector now.
            deepRecursionDetector = false;
         }
      }

      /**
       * Tests whether there is an immediate cyclic dependency (i.e., both components depend on each other) between the current component and another one.
       *
       * @param c other component to test with
       * @return true if there is an immediate cyclic dependency between the 2 components
       */
      private boolean isShallowCyclic(Component c)
      {
         return dependencies.contains(c) && c.dependencies.contains(this);
      }

      /**
       * Used to wire dependencies into this component instance.
       */
      void wire()
      {
         try
         {
            List<CachedMethod> methods = lookupInjectionMethods(instance.getClass());

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

      /**
       * Used to call all methods annotated with {@link Start} on component instance
       */
      void start()
      {
         invokeMethods(Start.class);
      }

      /**
       * Used to call all methods annotated with {@link Stop} on component instance
       */
      void stop()
      {
         invokeMethods(Stop.class);
      }

      /**
       * Used to call all methods annotated with {@link org.jboss.cache.factories.annotations.Destroy} on component instance
       */
      void destroy()
      {
         invokeMethods(Destroy.class);
      }

      private void invokeMethods(Class<? extends Annotation> annotation)
      {
         List<Method> methods = ReflectionUtil.getAllMethods(instance.getClass(), annotation);
         for (Method m : methods)
         {
            try
            {
               m.setAccessible(true);
               m.invoke(instance);
            }
            catch (Exception e)
            {
               log.warn("Unable to invoke annotated method " + m, e);
            }
         }
      }

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

      @Override
      public int hashCode()
      {
         return 31 * name.hashCode();
      }

      @Override
      public boolean equals(Object other)
      {
         return other instanceof Component && name.equals(((Component) other).name);
      }

   }

   class Bootstrap
   {
      CacheImpl cacheImpl;
      CacheSPI cacheSPI;
      ComponentRegistry componentRegistry;
      Configuration configuration;
      private ClassLoader deployerClassLoader;

      Bootstrap(ClassLoader deployerClassLoader, CacheImpl cacheImpl, CacheSPI cacheSPI, ComponentRegistry componentRegistry, Configuration configuration)
      {
         this.deployerClassLoader = deployerClassLoader;
         this.cacheImpl = cacheImpl;
         this.cacheSPI = cacheSPI;
         this.componentRegistry = componentRegistry;
         this.configuration = configuration;
      }

      boolean isBootstrapped()
      {
         return componentLookup.containsKey(Configuration.class.getName()) &&
               componentLookup.containsKey(CacheImpl.class.getName()) &&
               componentLookup.containsKey(CacheSPI.class.getName()) &&
               componentLookup.containsKey(ComponentRegistry.class.getName()) &&
               componentLookup.containsKey("deployerClassLoader");
      }

      void bootstrap()
      {
         overallState = CONSTRUCTED;
         registerComponent("deployerClassLoader", deployerClassLoader, ClassLoader.class);
         registerComponent(Configuration.class.getName(), configuration, Configuration.class);
         registerComponent(ComponentRegistry.class.getName(), componentRegistry, ComponentRegistry.class);
         registerComponent(CacheImpl.class.getName(), cacheImpl, CacheImpl.class);
         registerComponent(CacheSPI.class.getName(), cacheSPI, CacheSPI.class);
      }
   }
}
TOP

Related Classes of org.jboss.cache.factories.ComponentRegistry$Bootstrap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.