ComponentContainer.java
package org.andromda.core.common;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
/**
* <p> This handles all registration and retrieval of components within the
* framework. The purpose of this container is so that we can register default
* services in a consistent manner by creating a component interface and then
* placing the file which defines the default implementation in the
* 'META-INF/services/' directory found on the classpath.
* </p>
* <p> In order to create a new component that can be registered/found through
* this container you must perform the following steps:
* <ol>
* <li>Create the component interface (i.e.
* org.andromda.core.repository.RepositoryFacade)</li>
* <li>Create the component implementation (i.e.
* org.andromda.repositories.mdr.MDRepositoryFacade)</li>
* <li>Create a file with the exact same name as the fully qualified name of
* the component (i.e. org.andromda.core.repository.RepositoryFacade) that
* contains the name of the implementation class (i.e.
* org.andromda.repositories.mdr.MDRepostioryFacade) and place this in the
* META-INF/services/ directory within the core.</li>
* </ol>
* <p>After you perform the above steps, the component can be found by the methods
* within this class. See each below method for more information on how each
* performs lookup/retrieval of the components.
* </p>
*
* @author Chad Brandon
* @author Bob Fields
*/
public class ComponentContainer
{
private static final Logger LOGGER = Logger.getLogger(ComponentContainer.class);
/**
* Where all component default implementations are found.
*/
private static final String SERVICES = "META-INF/services/";
/**
* The container instance
*/
private final Map<Object, Object> container = new LinkedHashMap<Object, Object>();
/**
* The shared instance.
*/
private static ComponentContainer instance = null;
/**
* Gets the shared instance of this ComponentContainer.
*
* @return PluginDiscoverer the static instance.
*/
public static ComponentContainer instance()
{
if (instance == null)
{
instance = new ComponentContainer();
}
return instance;
}
/**
* Finds the component with the specified <code>key</code>.
*
* @param key the unique key of the component as an Object.
* @return Object the component instance.
*/
public Object findComponent(final Object key)
{
return this.container.get(key);
}
/**
* Creates a new component of the given <code>implementation</code> (if it
* isn't null or empty), otherwise attempts to find the default
* implementation of the given <code>type</code> by searching the
* <code>META-INF/services</code> directory for the default
* implementation.
*
* @param implementation the fully qualified name of the implementation
* class.
* @param type the type to retrieve if the implementation is empty.
* @return a new instance of the given <code>type</code>
*/
public Object newComponent(
String implementation,
final Class type)
{
Object component;
if (StringUtils.isBlank(implementation))
{
component = this.newDefaultComponent(type);
}
else
{
component = ClassUtils.newInstance(StringUtils.trimToEmpty(implementation));
}
return component;
}
/**
* Creates a new component of the given <code>implementation</code> (if it
* isn't null or empty), otherwise attempts to find the default
* implementation of the given <code>type</code> by searching the
* <code>META-INF/services</code> directory for the default
* implementation.
*
* @param implementation the implementation class.
* @param type the type to retrieve if the implementation is empty.
* @return a new instance of the given <code>type</code>
*/
public Object newComponent(
final Class implementation,
final Class type)
{
Object component;
if (implementation == null)
{
component = this.newDefaultComponent(type);
}
else
{
component = ClassUtils.newInstance(implementation);
}
return component;
}
/**
* Creates a new component of the given <code>type</code> by searching the
* <code>META-INF/services</code> directory and finding its default
* implementation.
*
* @param type
* @return a new instance of the given <code>type</code>
*/
public Object newDefaultComponent(final Class type)
{
ExceptionUtils.checkNull("type", type);
Object component;
try
{
final String implementation = this.getDefaultImplementation(type);
if (StringUtils.isBlank(implementation))
{
throw new ComponentContainerException(
"Default configuration file '" + this.getComponentDefaultConfigurationPath(type) +
"' could not be found");
}
component = ClassUtils.loadClass(implementation).newInstance();
}
catch (final Throwable throwable)
{
throw new ComponentContainerException(throwable);
}
return component;
}
/**
* Returns the expected path to the component's default configuration file.
*
* @param type the component type.
* @return the path to the component configuration file.
*/
protected final String getComponentDefaultConfigurationPath(final Class type)
{
ExceptionUtils.checkNull("type", type);
return SERVICES + type.getName();
}
/**
* Finds the component with the specified Class <code>key</code>. If the
* component wasn't explicitly registered then the META-INF/services
* directory on the classpath will be searched in order to find the default
* component implementation.
*
* @param key the unique key as a Class.
* @return Object the component instance.
*/
public Object findComponent(final Class key)
{
ExceptionUtils.checkNull("key", key);
return this.findComponent(null, key);
}
/**
* Attempts to Find the component with the specified <code>type</code>,
* throwing a {@link ComponentContainerException} exception if one can not
* be found.
*
* @param key the unique key of the component as an Object.
* @return Object the component instance.
*/
public Object findRequiredComponent(final Class key)
{
final Object component = this.findComponent(key);
if (component == null)
{
throw new ComponentContainerException(
"No implementation could be found for component '" + key.getName() +
"', please make sure you have a '" + this.getComponentDefaultConfigurationPath(key) +
"' file on your classpath");
}
return component;
}
/**
* Attempts to find the component with the specified unique <code>key</code>,
* if it can't be found, the default of the specified <code>type</code> is
* returned, if no default is set, null is returned. The default is the
* service found within the META-INF/services directory on your classpath.
*
* @param key the unique key of the component.
* @param type the default type to retrieve if the component can not be
* found.
* @return Object the component instance.
*/
public Object findComponent(
final String key,
final Class type)
{
ExceptionUtils.checkNull("type", type);
try
{
Object component = this.findComponent(key);
if (component == null)
{
final String typeName = type.getName();
component = this.findComponent(typeName);
// if the component doesn't have a default already
// (i.e. component == null), then see if we can find the default
// configuration file.
if (component == null)
{
final String defaultImplementation = this.getDefaultImplementation(type);
if (StringUtils.isNotBlank(defaultImplementation))
{
component =
this.registerDefaultComponent(
ClassUtils.loadClass(typeName),
ClassUtils.loadClass(defaultImplementation));
}
else
{
LOGGER.warn(
"WARNING! Component's default configuration file '" +
getComponentDefaultConfigurationPath(type) + "' could not be found");
}
}
}
return component;
}
catch (final Throwable throwable)
{
throw new ComponentContainerException(throwable);
}
}
/**
* Attempts to find the default configuration file from the
* <code>META-INF/services</code> directory. Returns an empty String if
* none is found.
*
* @param type the type (i.e. org.andromda.core.templateengine.TemplateEngine)
* @return the default implementation for the argument Class or the empty string if none is found
*/
private String getDefaultImplementation(final Class type)
{
final String contents = ResourceUtils.getContents(this.getComponentDefaultConfigurationPath(type));
return StringUtils.trimToEmpty(contents);
}
// TODO Convert to generic type checking using template values Collection<T>
/**
* Finds all components having the given <code>type</code>.
* @param type the component type.
* @return Collection all components
*/
public Collection findComponentsOfType(final Class type)
{
final Collection<Object> components = new ArrayList<Object>(this.container.values());
final Collection<Object> containerInstances = this.container.values();
for (final Object component : containerInstances)
{
if (component instanceof ComponentContainer)
{
components.addAll(((ComponentContainer) component).container.values());
}
}
final Collection<Object> componentsOfType = new ArrayList<Object>();
for (final Object component : components)
{
if (type.isInstance(component))
{
componentsOfType.add(component);
}
}
return componentsOfType;
}
/*
* Finds all components having the given <code>type</code>.
* @param type the component type.
* @return Collection all components
public Collection<T> findComponentsOfType(final T type)
{
final Collection<Object> components = new ArrayList<Object>(this.container.values());
final Collection<Object> containerInstances = this.container.values();
for (final Object component : containerInstances)
{
if (component instanceof ComponentContainer)
{
components.addAll(((ComponentContainer) component).container.values());
}
}
final Collection<T> componentsOfType = new ArrayList<T>();
for (final Object component : components)
{
if (component.getClass().equals(type.getClass()))
{
componentsOfType.add((T) component);
}
}
return componentsOfType;
}
*/
/**
* Unregisters the component in this container with a unique (within this
* container) <code>key</code>.
*
* @param key the unique key.
* @return Object the registered component.
*/
public Object unregisterComponent(final String key)
{
ExceptionUtils.checkEmpty("key", key);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("unregistering component with key --> '" + key + '\'');
}
return container.remove(key);
}
/**
* Finds a component in this container with a unique (within this container)
* <code>key</code> registered by the specified <code>namespace</code>.
*
* @param namespace the namespace for which to search.
* @param key the unique key.
* @return the found component, or null.
*/
public Object findComponentByNamespace(
final String namespace,
final Object key)
{
ExceptionUtils.checkEmpty("namespace", namespace);
ExceptionUtils.checkNull("key", key);
Object component = null;
final ComponentContainer namespaceContainer = this.getNamespaceContainer(namespace);
if (namespaceContainer != null)
{
component = namespaceContainer.findComponent(key);
}
return component;
}
/**
* Gets an instance of the container for the given <code>namespace</code>
* or returns null if one can not be found.
*
* @param namespace the name of the namespace.
* @return the namespace container.
*/
private ComponentContainer getNamespaceContainer(final String namespace)
{
return (ComponentContainer)this.findComponent(namespace);
}
/**
* Registers true (false otherwise) if the component in this container with
* a unique (within this container) <code>key</code> is registered by the
* specified <code>namespace</code>.
*
* @param namespace the namespace for which to register the component.
* @param key the unique key.
* @return boolean true/false depending on whether or not it is registerd.
*/
public boolean isRegisteredByNamespace(
final String namespace,
final Object key)
{
ExceptionUtils.checkEmpty("namespace", namespace);
ExceptionUtils.checkNull("key", key);
final ComponentContainer namespaceContainer = this.getNamespaceContainer(namespace);
return namespaceContainer != null && namespaceContainer.isRegistered(key);
}
/**
* Registers true (false otherwise) if the component in this container with
* a unique (within this container) <code>key</code> is registered.
*
* @param key the unique key.
* @return boolean true/false depending on whether or not it is registered.
*/
public boolean isRegistered(final Object key)
{
return this.findComponent(key) != null;
}
/**
* Registers the component in this container with a unique (within this
* container) <code>key</code> by the specified <code>namespace</code>.
*
* @param namespace the namespace for which to register the component.
* @param key the unique key.
* @param component
*/
public void registerComponentByNamespace(
final String namespace,
final Object key,
final Object component)
{
ExceptionUtils.checkEmpty("namespace", namespace);
ExceptionUtils.checkNull("component", component);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("registering component '" + component + "' with key --> '" + key + '\'');
}
ComponentContainer namespaceContainer = this.getNamespaceContainer(namespace);
if (namespaceContainer == null)
{
namespaceContainer = new ComponentContainer();
this.registerComponent(namespace, namespaceContainer);
}
namespaceContainer.registerComponent(key, component);
}
/**
* Registers the component in this container with a unique (within this
* container) <code>key</code>.
*
* @param key the unique key.
* @param component
* @return Object the registered component.
*/
public Object registerComponent(
final Object key,
final Object component)
{
ExceptionUtils.checkNull("component", component);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("registering component '" + component + "' with key --> '" + key + '\'');
}
return this.container.put(key, component);
}
/**
* Registers the "default" for the specified componentInterface.
*
* @param componentInterface the interface for the component.
* @param defaultTypeName the name of the "default" type of the
* implementation to use for the componentInterface. Its expected
* that this is the name of a class.
*/
public void registerDefaultComponent(
final Class componentInterface,
final String defaultTypeName)
{
ExceptionUtils.checkNull("componentInterface", componentInterface);
ExceptionUtils.checkEmpty("defaultTypeName", defaultTypeName);
try
{
this.registerDefaultComponent(
componentInterface,
ClassUtils.loadClass(defaultTypeName));
}
catch (final Throwable throwable)
{
throw new ComponentContainerException(throwable);
}
}
/**
* Registers the "default" for the specified componentInterface.
*
* @param componentInterface the interface for the component.
* @param defaultType the "default" implementation to use for the
* componentInterface.
* @return Object the registered component.
*/
public Object registerDefaultComponent(
final Class componentInterface,
final Class defaultType)
{
ExceptionUtils.checkNull("componentInterface", componentInterface);
ExceptionUtils.checkNull("defaultType", defaultType);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(
"registering default for component '" + componentInterface + "' as type --> '" + defaultType + '\'');
}
try
{
final String interfaceName = componentInterface.getName();
// check and unregister the component if its registered
// so that we can register a new default component.
if (this.isRegistered(interfaceName))
{
this.unregisterComponent(interfaceName);
}
final Object component = defaultType.newInstance();
this.container.put(interfaceName, component);
return component;
}
catch (final Throwable throwable)
{
throw new ComponentContainerException(throwable);
}
}
/**
* Registers the component of the specified <code>type</code>.
*
* @param type the type Class.
*/
public void registerComponentType(final Class type)
{
ExceptionUtils.checkNull("type", type);
try
{
this.container.put(
type,
type.newInstance());
}
catch (final Throwable throwable)
{
throw new ComponentContainerException(throwable);
}
}
/**
* Registers the components of the specified <code>type</code>.
*
* @param type the name of a type (must have be able to be instantiated into
* a Class instance)
* @return Object an instance of the type registered.
*/
public Object registerComponentType(final String type)
{
ExceptionUtils.checkNull("type", type);
try
{
return this.registerComponent(
type,
ClassUtils.loadClass(type).newInstance());
}
catch (final Throwable throwable)
{
throw new ComponentContainerException(throwable);
}
}
/**
* Shuts down this container instance.
*/
public void shutdown()
{
this.container.clear();
ComponentContainer.instance = null;
}
}