/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.component; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.servlet.ServletContext; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.Lifecycle; import org.springframework.context.Phased; import org.springframework.jmx.export.MBeanExporter; import org.springframework.jmx.support.JmxUtils; import org.springframework.web.context.ServletContextAware; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.component.rest.RestComponents; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.ReflectionUtils; /** * A repository for OpenGamma components. * <p> * The OpenGamma logical architecture consists of a set of components. * This repository manages the components. * <p> * This class uses concurrent collections, but instances are intended to be created * from a single thread at startup. */ public class ComponentRepository implements Lifecycle, ServletContextAware { /** * The key used to refer to repository in the servlet context. */ public static final String SERVLET_CONTEXT_KEY = ComponentRepository.class.getName(); /** * The thread-local instance. */ private static final ThreadLocal<ComponentRepository> s_threadRepo = new InheritableThreadLocal<>(); /** * The logger to use. */ private final ComponentLogger _logger; /** * The map of info by type. */ private final ConcurrentMap<Class<?>, ComponentTypeInfo> _infoMap = new ConcurrentHashMap<>(); /** * The repository of component instances. */ private final ConcurrentMap<ComponentKey, Object> _instanceMap = new ConcurrentHashMap<>(); /** * The repository of RESTful published components. */ private final RestComponents _restPublished = new RestComponents(); /** * The objects with {@code Lifecycle}. */ private final Map<Integer, List<Lifecycle>> _lifecycles = new TreeMap<>(); /** * The objects with {@code ServletContextAware}. */ private final List<ServletContextAware> _servletContextAware = new ArrayList<>(); /** * The objects that should be registered as managed resources. */ private final Map<ObjectName, Object> _managedResources = new TreeMap<>(); /** * The objects that have already been initialized. */ private final List<InitializingBean> _initialized = new ArrayList<>(); /** * The status. */ private final AtomicReference<Status> _status = new AtomicReference<>(Status.CREATING); /** * Creates an instance. * * @param logger the logger, null means no logging */ public ComponentRepository(final ComponentLogger logger) { _logger = (logger != null ? logger : ComponentLogger.Sink.INSTANCE); } //------------------------------------------------------------------------- /** * Gets the logger. * * @return the logger, not null */ public ComponentLogger getLogger() { return _logger; } /** * Gets all the instances. * <p> * This method will return all registered instances. * * @return the instance map, not null */ public Map<ComponentKey, Object> getInstanceMap() { return new HashMap<ComponentKey, Object>(_instanceMap); } /** * Gets all the instances for the specified type. * <p> * This method will return all registered instances of the specified type. * * @param <T> the type * @param type the type to get, not null * @return the modifiable instances, not null */ public <T> Collection<T> getInstances(Class<T> type) { ArgumentChecker.notNull(type, "type"); List<T> result = new ArrayList<T>(); for (ComponentInfo info : getInfos(type)) { result.add(type.cast(getInstance(info))); } return result; } /** * Gets all the component information objects for the specified type. * <p> * This method will return all registered information objects for the specified type. * * @param type the type to get, not null * @return the component informations, not null */ public Collection<ComponentInfo> getInfos(Class<?> type) { ArgumentChecker.notNull(type, "type"); ComponentTypeInfo info = findTypeInfo(type); ArrayList<ComponentInfo> result = new ArrayList<ComponentInfo>(); if (info != null) { result.addAll(info.getInfoMap().values()); } return result; } //------------------------------------------------------------------------- /** * Gets an instance of a component. * <p> * This finds an instance that matches the specified type. * * @param <T> the type * @param type the type to get, not null * @param classifier the classifier that distinguishes the component, not null * @return the component instance, not null * @throws IllegalArgumentException if no component is available */ public <T> T getInstance(Class<T> type, String classifier) { ArgumentChecker.notNull(type, "type"); ComponentKey key = ComponentKey.of(type, classifier); Object result = _instanceMap.get(key); if (result == null) { throw new IllegalArgumentException("No component available: " + key); } return type.cast(result); } /** * Gets an instance of a component. * <p> * This finds an instance that matches the specified information. * * @param info the component info, not null * @return the component instance, not null * @throws IllegalArgumentException if no component is available */ public Object getInstance(ComponentInfo info) { ArgumentChecker.notNull(info, "info"); ComponentKey key = info.toComponentKey(); Object result = _instanceMap.get(key); if (result == null) { throw new IllegalArgumentException("No component available: " + key); } return result; } //------------------------------------------------------------------------- /** * Gets all the available type information. * * @return the component type information, not null * @throws IllegalArgumentException if no component is available */ public Collection<ComponentTypeInfo> getTypeInfo() { return new ArrayList<ComponentTypeInfo>(_infoMap.values()); } /** * Gets type information by type. * * @param type the type to get, not null * @return the component type information, not null * @throws IllegalArgumentException if no component is available */ public ComponentTypeInfo getTypeInfo(Class<?> type) { ComponentTypeInfo typeInfo = _infoMap.get(type); if (typeInfo == null) { throw new IllegalArgumentException("Unknown component: " + type); } return typeInfo; } //------------------------------------------------------------------------- /** * Gets component information by type and classifier. * * @param type the type to get, not null * @param classifier the classifier that distinguishes the component, not null * @return the component information, not null * @throws IllegalArgumentException if no component is available */ public ComponentInfo getInfo(Class<?> type, String classifier) { ArgumentChecker.notNull(type, "type"); ComponentTypeInfo typeInfo = getTypeInfo(type); return typeInfo.getInfo(classifier); } /** * Gets component information for an instance. * * @param instance the instance to find info for, not null * @return the component information, not null * @throws IllegalArgumentException if no component is available */ public ComponentInfo getInfo(Object instance) { for (Entry<ComponentKey, Object> entry : _instanceMap.entrySet()) { if (entry.getValue() == instance) { return getInfo(entry.getKey().getType(), entry.getKey().getClassifier()); } } throw new IllegalArgumentException("Unknown component instance: " + instance); } //------------------------------------------------------------------------- /** * Finds the type information for the component. * <p> * This method is lenient, ignoring case and matching both full and simple type names. * * @param typeName the name of the type to get, case insensitive, not null * @return the component type information, null if not found */ public ComponentTypeInfo findTypeInfo(String typeName) { ArgumentChecker.notNull(typeName, "type"); for (Class<?> realType : _infoMap.keySet()) { if (realType.getName().equalsIgnoreCase(typeName) || realType.getSimpleName().equalsIgnoreCase(typeName)) { return getTypeInfo(realType); } } return null; } /** * Finds the type information for the component. * <p> * This method is lenient, returning null if not found. * * @param type the type to get, not null * @return the component information, null if not found */ public ComponentTypeInfo findTypeInfo(Class<?> type) { ArgumentChecker.notNull(type, "type"); return _infoMap.get(type); } //------------------------------------------------------------------------- /** * Finds component information from type name and classifier. * <p> * This method is lenient, ignoring case and matching both full and simple type names. * * @param typeName the simple name of the type to get, case insensitive, not null * @param classifier the classifier that distinguishes the component, case insensitive, not null * @return the component information, null if not found */ public ComponentInfo findInfo(String typeName, String classifier) { ArgumentChecker.notNull(typeName, "type"); ComponentTypeInfo typeInfo = findTypeInfo(typeName); if (typeInfo != null) { for (String realClassifier : typeInfo.getInfoMap().keySet()) { if (realClassifier.equalsIgnoreCase(classifier)) { return typeInfo.getInfo(realClassifier); } } } return null; } /** * Finds the component information from type and classifier. * <p> * This method is lenient, ignoring case and matching both full and simple type names. * * @param type the type to get, not null * @param classifier the classifier that distinguishes the component, case insensitive, not null * @return the component information, null if not found */ public ComponentInfo findInfo(Class<?> type, String classifier) { ArgumentChecker.notNull(type, "type"); ComponentTypeInfo typeInfo = findTypeInfo(type); if (typeInfo != null) { for (String realClassifier : typeInfo.getInfoMap().keySet()) { if (realClassifier.equalsIgnoreCase(classifier)) { return typeInfo.getInfo(realClassifier); } } } return null; } /** * Finds component information from the combined type name and classifier, such as 'Foo::bar'. * <p> * This method is lenient, ignoring case and matching both full and simple type names. * * @param componentKey the type name and classifier, such as 'Foo::bar', not null * @return the component information, null if not found */ public ComponentInfo findInfo(String componentKey) { ArgumentChecker.notNull(componentKey, "qualifiedClassifier"); if (componentKey.contains("::") == false) { return null; } String type = StringUtils.substringBefore(componentKey, "::"); String classifier = StringUtils.substringAfter(componentKey, "::"); return findInfo(type, classifier); } /** * Finds component information from the combined type name and classifier, * such as 'Foo::bar', keyed by an arbitary name. * <p> * The returned map only contains valid components as per {@link #findInfo(String)}. * It is intended to be used in a {@code ComponentFactory} being passed the map of configuration. * * @param namedComponentKeys the map of name to component key , not null * @return the map of name to component information, not null */ public LinkedHashMap<String, ComponentInfo> findInfos(Map<String, String> namedComponentKeys) { ArgumentChecker.notNull(namedComponentKeys, "namedComponentKeys"); LinkedHashMap<String, ComponentInfo> infos = new LinkedHashMap<>(); for (Entry<String, String> entry : namedComponentKeys.entrySet()) { ComponentInfo info = findInfo(entry.getValue()); if (info != null) { infos.put(entry.getKey(), info); } } return infos; } //------------------------------------------------------------------------- /** * Finds a single instance of the specified component type. * <p> * This method searches for a single instance of the specified type. * If there are no instances or multiple instances, then null is returned. * * @param <T> the type * @param type the type to get, not null * @return the component information, null if not found */ public <T> T findInstance(Class<T> type) { ArgumentChecker.notNull(type, "type"); ComponentTypeInfo typeInfo = findTypeInfo(type); if (typeInfo == null) { return null; } if (typeInfo.getInfoMap().size() != 1) { return null; } Object result = getInstance(typeInfo.getInfoMap().values().iterator().next()); return type.cast(result); } /** * Gets an instance of a component. * <p> * This finds an instance that matches the specified type. * * @param <T> the type * @param type the type to get, not null * @param classifier the classifier that distinguishes the component, not null * @return the component instance, null if not found */ public <T> T findInstance(Class<T> type, String classifier) { return type.cast(_instanceMap.get(ComponentKey.of(type, classifier))); } /** * Gets the RESTful components. * <p> * To publish a component over REST, use the methods on this class. * * @return the published components, not null */ public RestComponents getRestComponents() { return _restPublished; } //------------------------------------------------------------------------- /** * Initializes the specified object. * <p> * This initializes the object as per the standard {@code InitializingBean} contract. * It can be called manually using this method, however it is normally invoked * automatically by registering a component. Thus this method is primarily * useful for objects that need initialization but are not components. * <p> * The object is retained to ensure that it is not initialized twice. * * @param object the object to initialize, not null * @throws OpenGammaRuntimeException if the object cannot be initialized */ public void initialize(InitializingBean object) { if (isInitialized(object) == false) { try { object.afterPropertiesSet(); } catch (Exception ex) { throw new OpenGammaRuntimeException(ex.getMessage(), ex); } _initialized.add(object); } } /** * Checks if the object has been initialized. * * @return true if initialized */ private boolean isInitialized(InitializingBean object) { for (InitializingBean initialized : _initialized) { if (initialized == object) { return true; } } return false; } /** * Initializes an {@code InitializingBean} instance. * * @param object the object to check, not null */ private void initialize0(Object object) { if (object instanceof InitializingBean) { initialize((InitializingBean) object); } } //------------------------------------------------------------------------- /** * Registers the component specifying the info that describes it. * <p> * Certain interfaces are automatically detected. * If the component implements {@code Lifecycle}, it will be registered as * though using {@link #registerLifecycle(Lifecycle)}. * If it implements {@code ServletContextAware}, then it will be registered * as though using {@link #registerServletContextAware(ServletContextAware)}. * If it implements {@code InitializingBean}, then it will be initialized * as though using {@link #initialize(InitializingBean)}. * <p> * If the component implements {@code FactoryBean}, it will be checked for the * automatically detected interfaces before the factory is evaluated. * The evaluated factory will then be registered, and the resulting object * will again be checked for automatically detected interfaces. * * @param info the component info to register, not null * @param instance the component instance to register, not null * @throws OpenGammaRuntimeException if an error occurs */ public void registerComponent(ComponentInfo info, Object instance) { ArgumentChecker.notNull(info, "info"); ArgumentChecker.notNull(instance, "instance"); checkStatus(Status.CREATING); ComponentKey key = info.toComponentKey(); try { // initialize initialize0(instance); registerInstanceInterfaces0(instance); // handle factories if (instance instanceof FactoryBean<?>) { try { instance = ((FactoryBean<?>) instance).getObject(); } catch (Exception ex) { throw new OpenGammaRuntimeException("FactoryBean threw exception", ex); } initialize0(instance); registerInstanceInterfaces0(instance); } // register into data structures Object current = _instanceMap.putIfAbsent(key, instance); if (current != null) { throw new ComponentConfigException("Component already registered for specified information: " + key); } _infoMap.putIfAbsent(info.getType(), new ComponentTypeInfo(info.getType())); ComponentTypeInfo typeInfo = getTypeInfo(info.getType()); typeInfo.getInfoMap().put(info.getClassifier(), info); registeredComponent(info, instance); // If the component being registered is also an MBean, then register it as such if (JmxUtils.isMBean(instance.getClass())) { registerMBean(instance); } } catch (RuntimeException ex) { _status.set(Status.FAILED); throw new OpenGammaRuntimeException("Failed during registration: " + key, ex); } } /** * Registers the component automatically creating the info that describes it. * <p> * Certain interfaces are automatically detected. * If the component implements {@code Lifecycle}, it will be registered as * though using {@link #registerLifecycle(Lifecycle)}. * If it implements {@code ServletContextAware}, then it will be registered * as though using {@link #registerServletContextAware(ServletContextAware)}. * If it implements {@code InitializingBean}, then it will be initialized * as though using {@link #initialize(InitializingBean)}. * * @param <T> the type * @param type the type to register under, not null * @param classifier the classifier that distinguishes the component, empty for default, not null * @param instance the component instance to register, not null * @throws OpenGammaRuntimeException if an error occurs */ public <T> void registerComponent(Class<T> type, String classifier, T instance) { ArgumentChecker.notNull(type, "type"); ArgumentChecker.notNull(classifier, "classifier"); ArgumentChecker.notNull(instance, "instance"); ComponentInfo info = new ComponentInfo(type, classifier); registerComponent(info, instance); } /** * Called whenever an instance is registered. * * @param info the key or info of the instance that was registered, null if not a component * @param registeredObject the instance that was registered, may be null */ protected void registeredComponent(ComponentInfo info, Object registeredObject) { if (info.getAttributes().isEmpty()) { _logger.logDebug(" Registered component: " + info.toComponentKey()); } else { _logger.logDebug(" Registered component: " + info.toComponentKey() + " " + info.getAttributes()); } } /** * Registers the {@code Lifecycle}, {@code Phased} and * {@code ServletContextAware} aspects of the specified instance. * * @param instance the object to examine, not null */ private void registerInstanceInterfaces0(Object instance) { if (instance instanceof Lifecycle) { registerLifecycle0((Lifecycle) instance); } else { findAndRegisterLifeCycle(instance); } if (instance instanceof ServletContextAware) { registerServletContextAware0((ServletContextAware) instance); } } /** * Examines an object and sets up a {@code Lifecycle} instance if it can be closed. * If it implements {@code Phased} then the phase is used. * * @param instance the object to examine, not null */ private void findAndRegisterLifeCycle(final Object instance) { if (ReflectionUtils.isCloseable(instance.getClass())) { registerLifecycle0(new PhasedLifecycle() { @Override public void stop() { ReflectionUtils.close(instance); } @Override public void start() { } @Override public boolean isRunning() { return false; } @Override public int getPhase() { if (instance instanceof Phased) { return ((Phased) instance).getPhase(); } return 0; } @Override public String toString() { return instance.getClass().getSimpleName() + ":" + instance.toString(); } }); } } //------------------------------------------------------------------------- /** * Registers a non-component object that requires closing or shutdown when * the repository stops. * <p> * Certain interfaces are automatically detected. * If it implements {@code InitializingBean}, then it will be initialized * as though using {@link #initialize(InitializingBean)}. * If it implements {@code Phased} then the phase is used. * * @param obj the object to close/shutdown, not null * @param methodName the method name to call, not null */ public void registerLifecycleStop(final Object obj, final String methodName) { ArgumentChecker.notNull(obj, "object"); ArgumentChecker.notNull(methodName, "methodName"); checkStatus(Status.CREATING); initialize0(obj); registerLifecycle0(new PhasedLifecycle() { @Override public void stop() { ReflectionUtils.invokeNoArgsNoException(obj, methodName); } @Override public void start() { } @Override public boolean isRunning() { return false; } @Override public int getPhase() { if (obj instanceof Phased) { return ((Phased) obj).getPhase(); } return 0; } @Override public String toString() { return obj.getClass().getSimpleName() + ":" + obj.toString(); } }); _logger.logDebug(" Registered lifecycle-stop: " + obj); } /** * Registers a non-component object implementing {@code Lifecycle}. * <p> * Certain interfaces are automatically detected. * If it implements {@code InitializingBean}, then it will be initialized * as though using {@link #initialize(InitializingBean)}. * If it implements {@code Phased} then the phase is used. * * @param lifecycleObject the object that has a lifecycle, not null * @throws OpenGammaRuntimeException if an error occurs */ public void registerLifecycle(Lifecycle lifecycleObject) { ArgumentChecker.notNull(lifecycleObject, "lifecycleObject"); checkStatus(Status.CREATING); try { initialize0(lifecycleObject); registerLifecycle0(lifecycleObject); _logger.logDebug(" Registered lifecycle: " + lifecycleObject); } catch (RuntimeException ex) { _status.set(Status.FAILED); throw new OpenGammaRuntimeException("Failed during registering Lifecycle: " + lifecycleObject, ex); } } /** * Registers a {@code Lifecycle} instance. * * @param lifecycleObject the object that has a lifecycle, not null */ private void registerLifecycle0(Lifecycle lifecycleObject) { Integer phase = 0; if (lifecycleObject instanceof Phased) { phase = ((Phased) lifecycleObject).getPhase(); } List<Lifecycle> list = _lifecycles.get(phase); if (list == null) { list = new ArrayList<>(); _lifecycles.put(phase, list); } list.add(lifecycleObject); } //------------------------------------------------------------------------- /** * Registers a non-component object implementing {@code Lifecycle}. * <p> * Certain interfaces are automatically detected. * If it implements {@code InitializingBean}, then it will be initialized * as though using {@link #initialize(InitializingBean)}. * * @param servletContextAware the object that requires a servlet context, not null * @throws OpenGammaRuntimeException if an error occurs */ public void registerServletContextAware(ServletContextAware servletContextAware) { ArgumentChecker.notNull(servletContextAware, "servletContextAware"); checkStatus(Status.CREATING); try { initialize0(servletContextAware); registerServletContextAware0(servletContextAware); _logger.logDebug(" Registered lifecycle-stop: " + servletContextAware); } catch (RuntimeException ex) { _status.set(Status.FAILED); throw new OpenGammaRuntimeException("Failed during registering ServletContextAware: " + servletContextAware, ex); } } /** * Registers a {@code ServletContextAware} instance. * * @param servletContextAware the object that requires a servlet context, not null */ private void registerServletContextAware0(ServletContextAware servletContextAware) { _servletContextAware.add(servletContextAware); } //------------------------------------------------------------------------- /** * Registers an instance that should be treated as a JMX Managed Resource. * * @param managedResource the object that should be treated as an MBean, not null */ public void registerMBean(Object managedResource) { ArgumentChecker.notNull(managedResource, "managedResource"); checkStatus(Status.CREATING); registerMBean(managedResource, generateObjectName(managedResource)); } /** * Registers an instance that should be treated as a JMX Managed Resource. * * @param managedResource the object that should be treated as an MBean * @param name The fully qualified JMX ObjectName * @throws OpenGammaRuntimeException if an error occurs */ public void registerMBean(Object managedResource, ObjectName name) { ArgumentChecker.notNull(managedResource, "managedResource"); ArgumentChecker.notNull(name, "name"); checkStatus(Status.CREATING); try { initialize0(managedResource); registerMBean0(managedResource, name); _logger.logDebug(" Registered mbean: " + managedResource); } catch (RuntimeException ex) { _status.set(Status.FAILED); throw new OpenGammaRuntimeException("Failed during registering ManagedResource: " + managedResource, ex); } } /** * Registers an instance that should be treated as a JMX Managed Resource. * * @param managedResource the object that should be treated as an MBean, not null */ private void registerMBean0(Object managedResource, ObjectName name) { _managedResources.put(name, managedResource); } /** * Creates an object name for the MBean. * * @param managedResource the object that should be treated as an MBean, not null * @return the object name, not null * @throws OpenGammaRuntimeException if an error occurs */ private ObjectName generateObjectName(Object managedResource) { ObjectName objectName; try { objectName = new ObjectName("com.opengamma:name=" + managedResource.getClass().getSimpleName()); } catch (Exception ex) { throw new OpenGammaRuntimeException("Could not generate object name for " + managedResource, ex); } return objectName; } //------------------------------------------------------------------------- /** * Marks this repository as complete and ready for use. * <p> * This will start any {@code Lifecycle} instances. * They will be started in {@code Phased} order (smallest to largest), * then in order of {@code Lifecycle} registration. * <p> * JMX resources are also started. */ @Override public void start() { Status status = _status.get(); if (status == Status.STARTING) { return; // already starting } checkStatus(status, Status.CREATING); if (_status.compareAndSet(status, Status.STARTING) == false) { return; // another thread just beat this one } try { // Spring interfaces for (List<Lifecycle> list : _lifecycles.values()) { for (Lifecycle obj : list) { obj.start(); } } // JMX managed resources MBeanServer jmxServer = findInstance(MBeanServer.class); if (jmxServer != null) { MBeanExporter exporter = new MBeanExporter(); exporter.setServer(jmxServer); for (Map.Entry<ObjectName, Object> resourceEntry : _managedResources.entrySet()) { exporter.registerManagedResource(resourceEntry.getValue(), resourceEntry.getKey()); } } _status.set(Status.RUNNING); } catch (RuntimeException ex) { _status.set(Status.FAILED); throw ex; } finally { // reduce memory usage and avoid memory leaks _managedResources.clear(); _initialized.clear(); } } /** * Stops this repository. */ @Override public void stop() { Status status = _status.get(); if (status == Status.STOPPING || status == Status.STOPPED) { return; // nothing to stop in this thread } if (_status.compareAndSet(status, Status.STOPPING) == false) { return; // another thread just beat this one } for (List<Lifecycle> list : Lists.reverse(ImmutableList.copyOf(_lifecycles.values()))) { for (Lifecycle obj : Lists.reverse(list)) { try { obj.stop(); } catch (Exception ex) { // ignore } } } _status.set(Status.STOPPED); } /** * Checks if this repository is running (started). * * @return true if running */ @Override public boolean isRunning() { return _status.get() == Status.RUNNING; } //------------------------------------------------------------------------- /** * Sets the {@code ServletContext}. * * @param servletContext the servlet context, not null */ @Override public void setServletContext(ServletContext servletContext) { servletContext.setAttribute(SERVLET_CONTEXT_KEY, this); for (ServletContextAware obj : _servletContextAware) { obj.setServletContext(servletContext); } } //------------------------------------------------------------------------- /** * Sets the thread-loal instance. */ public void pushThreadLocal() { s_threadRepo.set(this); } /** * Gets the thread-local instance. * * @return the thread-local instance, not null */ public static ComponentRepository getThreadLocal() { ComponentRepository repo = s_threadRepo.get(); if (repo == null) { throw new IllegalStateException("ComponentRepository thread-local not set"); } return repo; } /** * Gets the repository instance stored in the {@code ServletContext}. * <p> * This method should be used in preference to relying on the thread-local. * * @param servletContext the servlet context, not null * @return the instance stored in the servlet context, not null */ public static ComponentRepository getFromServletContext(ServletContext servletContext) { ArgumentChecker.notNull(servletContext, "servletContext"); ComponentRepository repo = (ComponentRepository) servletContext.getAttribute(SERVLET_CONTEXT_KEY); ArgumentChecker.notNull(repo, "ComponentRepository"); return repo; } //------------------------------------------------------------------------- private void checkStatus(Status required) { checkStatus(_status.get(), required); } private void checkStatus(Status current, Status required) { if (current != required) { throw new IllegalStateException("Invalid repository status, expected " + required + " but was " + current); } } private static enum Status { CREATING, STARTING, RUNNING, STOPPING, STOPPED, FAILED, } //------------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(1024); buf.append(getClass().getSimpleName()).append(_instanceMap.keySet()); return buf.toString(); } }