001: package org.andromda.core.common;
002:
003: import java.util.ArrayList;
004: import java.util.Collection;
005: import java.util.Iterator;
006: import java.util.LinkedHashMap;
007: import java.util.Map;
008:
009: import org.apache.log4j.Logger;
010:
011: /**
012: * <p/> This handles all registration and retrieval of components within the
013: * framework. The purpose of this container is so that we can register default
014: * services in a consistent manner by creating a component interface and then
015: * placing the file which defines the default implementation in the
016: * 'META-INF/services/' directory found on the classpath.
017: * </p>
018: * <p/> In order to create a new component that can be registered/found through
019: * this container you must perform the following steps:
020: * <ol>
021: * <li>Create the component interface (i.e.
022: * org.andromda.core.repository.RepositoryFacade)</li>
023: * <li>Create the component implementation (i.e.
024: * org.andromda.repositories.mdr.MDRepositoryFacade)</li>
025: * <li>Create a file with the exact same name as the fully qualified name of
026: * the component (i.e. org.andromda.core.repository.RepositoryFacade) that
027: * contains the name of the implementation class (i.e.
028: * org.andromda.repositories.mdr.MDRepostioryFacade) and place this in the
029: * META-INF/services/ directory within the core.</li>
030: * </ol>
031: * After you perform the above steps, the component can be found by the methods
032: * within this class. See each below method for more information on how each
033: * performs lookup/retrieval of the components.
034: * </p>
035: *
036: * @author Chad Brandon
037: */
038: public class ComponentContainer {
039: private static Logger logger = Logger
040: .getLogger(ComponentContainer.class);
041:
042: /**
043: * Where all component default implementations are found.
044: */
045: private static final String SERVICES = "META-INF/services/";
046:
047: /**
048: * The container instance
049: */
050: private final Map container = new LinkedHashMap();
051:
052: /**
053: * The shared instance.
054: */
055: private static ComponentContainer instance = null;
056:
057: /**
058: * Gets the shared instance of this ComponentContainer.
059: *
060: * @return PluginDiscoverer the static instance.
061: */
062: public static ComponentContainer instance() {
063: if (instance == null) {
064: instance = new ComponentContainer();
065: }
066: return instance;
067: }
068:
069: /**
070: * Finds the component with the specified <code>key</code>.
071: *
072: * @param key the unique key of the component as an Object.
073: * @return Object the component instance.
074: */
075: public Object findComponent(final Object key) {
076: return this .container.get(key);
077: }
078:
079: /**
080: * Creates a new component of the given <code>implementation</code> (if it
081: * isn't null or empty), otherwise attempts to find the default
082: * implementation of the given <code>type</code> by searching the
083: * <code>META-INF/services</code> directory for the default
084: * implementation.
085: *
086: * @param implementation the fully qualified name of the implementation
087: * class.
088: * @param type the type to retrieve if the implementation is empty.
089: * @return a new instance of the given <code>type</code>
090: */
091: public Object newComponent(String implementation, final Class type) {
092: Object component;
093: implementation = implementation != null ? implementation.trim()
094: : "";
095: if (implementation.length() == 0) {
096: component = this .newDefaultComponent(type);
097: } else {
098: component = ClassUtils.newInstance(implementation);
099: }
100: return component;
101: }
102:
103: /**
104: * Creates a new component of the given <code>implementation</code> (if it
105: * isn't null or empty), otherwise attempts to find the default
106: * implementation of the given <code>type</code> by searching the
107: * <code>META-INF/services</code> directory for the default
108: * implementation.
109: *
110: * @param implementation the implementation class.
111: * @param type the type to retrieve if the implementation is empty.
112: * @return a new instance of the given <code>type</code>
113: */
114: public Object newComponent(final Class implementation,
115: final Class type) {
116: Object component;
117: if (implementation == null) {
118: component = this .newDefaultComponent(type);
119: } else {
120: component = ClassUtils.newInstance(implementation);
121: }
122: return component;
123: }
124:
125: /**
126: * Creates a new component of the given <code>type</code> by searching the
127: * <code>META-INF/services</code> directory and finding its default
128: * implementation.
129: *
130: * @return a new instance of the given <code>type</code>
131: */
132: public Object newDefaultComponent(final Class type) {
133: ExceptionUtils.checkNull("type", type);
134: Object component;
135: try {
136: final String implementation = this
137: .getDefaultImplementation(type);
138: if (implementation.trim().length() == 0) {
139: throw new ComponentContainerException(
140: "Default configuration file '"
141: + this
142: .getComponentDefaultConfigurationPath(type)
143: + "' could not be found");
144: }
145: component = ClassUtils.loadClass(implementation)
146: .newInstance();
147: } catch (final Throwable throwable) {
148: throw new ComponentContainerException(throwable);
149: }
150: return component;
151: }
152:
153: /**
154: * Returns the expected path to the component's default configuration file.
155: *
156: * @param type the component type.
157: * @return the path to the component configuration file.
158: */
159: protected final String getComponentDefaultConfigurationPath(
160: final Class type) {
161: ExceptionUtils.checkNull("type", type);
162: return SERVICES + type.getName();
163: }
164:
165: /**
166: * Finds the component with the specified Class <code>key</code>. If the
167: * component wasn't explicitly registered then the META-INF/services
168: * directory on the classpath will be searched in order to find the default
169: * component implementation.
170: *
171: * @param key the unique key as a Class.
172: * @return Object the component instance.
173: */
174: public Object findComponent(final Class key) {
175: ExceptionUtils.checkNull("key", key);
176: return this .findComponent(null, key);
177: }
178:
179: /**
180: * Attempts to Find the component with the specified <code>type</code>,
181: * throwing a {@link ComponentContainerException} exception if one can not
182: * be found.
183: *
184: * @param key the unique key of the component as an Object.
185: * @return Object the component instance.
186: */
187: public Object findRequiredComponent(final Class key) {
188: final Object component = this .findComponent(key);
189: if (component == null) {
190: throw new ComponentContainerException(
191: "No implementation could be found for component '"
192: + key.getName()
193: + "', please make sure you have a '"
194: + this
195: .getComponentDefaultConfigurationPath(key)
196: + "' file on your classpath");
197: }
198: return component;
199: }
200:
201: /**
202: * Attempts to find the component with the specified unique <code>key</code>,
203: * if it can't be found, the default of the specified <code>type</code> is
204: * returned, if no default is set, null is returned. The default is the
205: * service found within the META-INF/services directory on your classpath.
206: *
207: * @param key the unique key of the component.
208: * @param type the default type to retrieve if the component can not be
209: * found.
210: * @return Object the component instance.
211: */
212: public Object findComponent(final String key, final Class type) {
213: ExceptionUtils.checkNull("type", type);
214: try {
215: Object component = this .findComponent(key);
216: if (component == null) {
217: final String typeName = type.getName();
218: component = this .findComponent(typeName);
219:
220: // if the component doesn't have a default already
221: // (i.e. component == null), then see if we can find the default
222: // configuration file.
223: if (component == null) {
224: final String defaultImplementation = this
225: .getDefaultImplementation(type);
226: if (defaultImplementation.trim().length() > 0) {
227: component = this
228: .registerDefaultComponent(
229: ClassUtils.loadClass(typeName),
230: ClassUtils
231: .loadClass(defaultImplementation));
232: } else {
233: logger
234: .warn("WARNING! Component's default configuration file '"
235: + getComponentDefaultConfigurationPath(type)
236: + "' could not be found");
237: }
238: }
239: }
240: return component;
241: } catch (final Throwable throwable) {
242: throw new ComponentContainerException(throwable);
243: }
244: }
245:
246: /**
247: * Attempts to find the default configuration file from the
248: * <code>META-INF/services</code> directory. Returns an empty String if
249: * none is found.
250: *
251: * @param type the type (i.e. org.andromda.core.templateengine.TemplateEngine)
252: * @return the default implementation for the argument Class or the empty string if none is found
253: */
254: private String getDefaultImplementation(final Class type) {
255: final String contents = ResourceUtils.getContents(this
256: .getComponentDefaultConfigurationPath(type));
257: return contents != null ? contents.trim() : "";
258: }
259:
260: /**
261: * Finds all components having the given <code>type</code>.
262: *
263: * @param type the component type.
264: * @return Collection all components
265: */
266: public Collection findComponentsOfType(final Class type) {
267: final Collection components = new ArrayList(this .container
268: .values());
269: final Collection containerInstances = this .container.values();
270: for (final Iterator iterator = containerInstances.iterator(); iterator
271: .hasNext();) {
272: final Object component = iterator.next();
273: if (component instanceof ComponentContainer) {
274: components
275: .addAll(((ComponentContainer) component).container
276: .values());
277: }
278: }
279: final Collection componentsOfType = new ArrayList();
280: for (final Iterator iterator = components.iterator(); iterator
281: .hasNext();) {
282: final Object component = iterator.next();
283: if (type.isInstance(component)) {
284: componentsOfType.add(component);
285: }
286: }
287: return componentsOfType;
288: }
289:
290: /**
291: * Unregisters the component in this container with a unique (within this
292: * container) <code>key</code>.
293: *
294: * @param key the unique key.
295: * @return Object the registered component.
296: */
297: public Object unregisterComponent(final String key) {
298: ExceptionUtils.checkEmpty("key", key);
299: if (logger.isDebugEnabled()) {
300: logger.debug("unregistering component with key --> '" + key
301: + "'");
302: }
303: return container.remove(key);
304: }
305:
306: /**
307: * Finds a component in this container with a unique (within this container)
308: * <code>key</code> registered by the specified <code>namespace</code>.
309: *
310: * @param namespace the namespace for which to search.
311: * @param key the unique key.
312: * @return the found component, or null.
313: */
314: public Object findComponentByNamespace(final String namespace,
315: final Object key) {
316: ExceptionUtils.checkEmpty("namespace", namespace);
317: ExceptionUtils.checkNull("key", key);
318:
319: Object component = null;
320: final ComponentContainer namespaceContainer = this
321: .getNamespaceContainer(namespace);
322: if (namespaceContainer != null) {
323: component = namespaceContainer.findComponent(key);
324: }
325: return component;
326: }
327:
328: /**
329: * Gets an instance of the container for the given <code>namespace</code>
330: * or returns null if one can not be found.
331: *
332: * @param namespace the name of the namespace.
333: * @return the namespace container.
334: */
335: private ComponentContainer getNamespaceContainer(
336: final String namespace) {
337: return (ComponentContainer) this .findComponent(namespace);
338: }
339:
340: /**
341: * Registers true (false otherwise) if the component in this container with
342: * a unique (within this container) <code>key</code> is registered by the
343: * specified <code>namespace</code>.
344: *
345: * @param namespace the namespace for which to register the component.
346: * @param key the unique key.
347: * @return boolean true/false depending on whether or not it is registerd.
348: */
349: public boolean isRegisteredByNamespace(final String namespace,
350: final Object key) {
351: ExceptionUtils.checkEmpty("namespace", namespace);
352: ExceptionUtils.checkNull("key", key);
353: final ComponentContainer namespaceContainer = this
354: .getNamespaceContainer(namespace);
355: return namespaceContainer != null
356: && namespaceContainer.isRegistered(key);
357: }
358:
359: /**
360: * Registers true (false otherwise) if the component in this container with
361: * a unique (within this container) <code>key</code> is registered.
362: *
363: * @param key the unique key.
364: * @return boolean true/false depending on whether or not it is registerd.
365: */
366: public boolean isRegistered(final Object key) {
367: return this .findComponent(key) != null;
368: }
369:
370: /**
371: * Registers the component in this container with a unique (within this
372: * container) <code>key</code> by the specified <code>namespace</code>.
373: *
374: * @param namespace the namespace for which to register the component.
375: * @param key the unique key.
376: */
377: public void registerComponentByNamespace(final String namespace,
378: final Object key, final Object component) {
379: ExceptionUtils.checkEmpty("namespace", namespace);
380: ExceptionUtils.checkNull("component", component);
381: if (logger.isDebugEnabled()) {
382: logger.debug("registering component '" + component
383: + "' with key --> '" + key + "'");
384: }
385: ComponentContainer namespaceContainer = this
386: .getNamespaceContainer(namespace);
387: if (namespaceContainer == null) {
388: namespaceContainer = new ComponentContainer();
389: this .registerComponent(namespace, namespaceContainer);
390: }
391: namespaceContainer.registerComponent(key, component);
392: }
393:
394: /**
395: * Registers the component in this container with a unique (within this
396: * container) <code>key</code>.
397: *
398: * @param key the unique key.
399: * @return Object the registered component.
400: */
401: public Object registerComponent(final Object key,
402: final Object component) {
403: ExceptionUtils.checkNull("component", component);
404: if (logger.isDebugEnabled()) {
405: logger.debug("registering component '" + component
406: + "' with key --> '" + key + "'");
407: }
408: return this .container.put(key, component);
409: }
410:
411: /**
412: * Registers the "default" for the specified componentInterface.
413: *
414: * @param componentInterface the interface for the component.
415: * @param defaultTypeName the name of the "default" type of the
416: * implementation to use for the componentInterface. Its expected
417: * that this is the name of a class.
418: */
419: public void registerDefaultComponent(
420: final Class componentInterface, final String defaultTypeName) {
421: ExceptionUtils.checkNull("componentInterface",
422: componentInterface);
423: ExceptionUtils.checkEmpty("defaultTypeName", defaultTypeName);
424: try {
425: this .registerDefaultComponent(componentInterface,
426: ClassUtils.loadClass(defaultTypeName));
427: } catch (final Throwable throwable) {
428: throw new ComponentContainerException(throwable);
429: }
430: }
431:
432: /**
433: * Registers the "default" for the specified componentInterface.
434: *
435: * @param componentInterface the interface for the component.
436: * @param defaultType the "default" implementation to use for the
437: * componentInterface.
438: * @return Object the registered component.
439: */
440: public Object registerDefaultComponent(
441: final Class componentInterface, final Class defaultType) {
442: ExceptionUtils.checkNull("componentInterface",
443: componentInterface);
444: ExceptionUtils.checkNull("defaultType", defaultType);
445: if (logger.isDebugEnabled()) {
446: logger.debug("registering default for component '"
447: + componentInterface + "' as type --> '"
448: + defaultType + "'");
449: }
450: try {
451: final String interfaceName = componentInterface.getName();
452:
453: // check and unregister the component if its registered
454: // so that we can register a new default component.
455: if (this .isRegistered(interfaceName)) {
456: this .unregisterComponent(interfaceName);
457: }
458: final Object component = defaultType.newInstance();
459: this .container.put(interfaceName, component);
460: return component;
461: } catch (final Throwable throwable) {
462: throw new ComponentContainerException(throwable);
463: }
464: }
465:
466: /**
467: * Registers the component of the specified <code>type</code>.
468: *
469: * @param type the type Class.
470: */
471: public void registerComponentType(final Class type) {
472: ExceptionUtils.checkNull("type", type);
473: try {
474: this .container.put(type, type.newInstance());
475: } catch (final Throwable throwable) {
476: throw new ComponentContainerException(throwable);
477: }
478: }
479:
480: /**
481: * Registers the components of the specified <code>type</code>.
482: *
483: * @param type the name of a type (must have be able to be instantiated into
484: * a Class instance)
485: * @return Object an instance of the type registered.
486: */
487: public Object registerComponentType(final String type) {
488: ExceptionUtils.checkNull("type", type);
489: try {
490: return this .registerComponent(type, ClassUtils.loadClass(
491: type).newInstance());
492: } catch (final Throwable throwable) {
493: throw new ComponentContainerException(throwable);
494: }
495: }
496:
497: /**
498: * Shuts down this container instance.
499: */
500: public void shutdown() {
501: this.container.clear();
502: instance = null;
503: }
504: }
|