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);
}
}
}