View Javadoc
1   package org.andromda.core.common;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.LinkedHashMap;
6   import java.util.Map;
7   import org.apache.commons.lang.StringUtils;
8   import org.apache.log4j.Logger;
9   
10  /**
11   * <p> This handles all registration and retrieval of components within the
12   * framework. The purpose of this container is so that we can register default
13   * services in a consistent manner by creating a component interface and then
14   * placing the file which defines the default implementation in the
15   * 'META-INF/services/' directory found on the classpath.
16   * </p>
17   * <p> In order to create a new component that can be registered/found through
18   * this container you must perform the following steps:
19   * <ol>
20   * <li>Create the component interface (i.e.
21   * org.andromda.core.repository.RepositoryFacade)</li>
22   * <li>Create the component implementation (i.e.
23   * org.andromda.repositories.mdr.MDRepositoryFacade)</li>
24   * <li>Create a file with the exact same name as the fully qualified name of
25   * the component (i.e. org.andromda.core.repository.RepositoryFacade) that
26   * contains the name of the implementation class (i.e.
27   * org.andromda.repositories.mdr.MDRepostioryFacade) and place this in the
28   * META-INF/services/ directory within the core.</li>
29   * </ol>
30   * <p>After you perform the above steps, the component can be found by the methods
31   * within this class. See each below method for more information on how each
32   * performs lookup/retrieval of the components.
33   * </p>
34   *
35   * @author Chad Brandon
36   * @author Bob Fields
37   */
38  public class ComponentContainer
39  {
40      private static final Logger LOGGER = Logger.getLogger(ComponentContainer.class);
41  
42      /**
43       * Where all component default implementations are found.
44       */
45      private static final String SERVICES = "META-INF/services/";
46  
47      /**
48       * The container instance
49       */
50      private final Map<Object, Object> container = new LinkedHashMap<Object, Object>();
51  
52      /**
53       * The shared instance.
54       */
55      private static ComponentContainer instance = null;
56  
57      /**
58       * Gets the shared instance of this ComponentContainer.
59       *
60       * @return PluginDiscoverer the static instance.
61       */
62      public static ComponentContainer instance()
63      {
64          if (instance == null)
65          {
66              instance = new ComponentContainer();
67          }
68          return instance;
69      }
70  
71      /**
72       * Finds the component with the specified <code>key</code>.
73       *
74       * @param key the unique key of the component as an Object.
75       * @return Object the component instance.
76       */
77      public Object findComponent(final Object key)
78      {
79          return this.container.get(key);
80      }
81  
82      /**
83       * Creates a new component of the given <code>implementation</code> (if it
84       * isn't null or empty), otherwise attempts to find the default
85       * implementation of the given <code>type</code> by searching the
86       * <code>META-INF/services</code> directory for the default
87       * implementation.
88       *
89       * @param implementation the fully qualified name of the implementation
90       *        class.
91       * @param type the type to retrieve if the implementation is empty.
92       * @return a new instance of the given <code>type</code>
93       */
94      public Object newComponent(
95          String implementation,
96          final Class type)
97      {
98          Object component;
99          if (StringUtils.isBlank(implementation))
100         {
101             component = this.newDefaultComponent(type);
102         }
103         else
104         {
105             component = ClassUtils.newInstance(StringUtils.trimToEmpty(implementation));
106         }
107         return component;
108     }
109 
110     /**
111      * Creates a new component of the given <code>implementation</code> (if it
112      * isn't null or empty), otherwise attempts to find the default
113      * implementation of the given <code>type</code> by searching the
114      * <code>META-INF/services</code> directory for the default
115      * implementation.
116      *
117      * @param implementation the implementation class.
118      * @param type the type to retrieve if the implementation is empty.
119      * @return a new instance of the given <code>type</code>
120      */
121     public Object newComponent(
122         final Class implementation,
123         final Class type)
124     {
125         Object component;
126         if (implementation == null)
127         {
128             component = this.newDefaultComponent(type);
129         }
130         else
131         {
132             component = ClassUtils.newInstance(implementation);
133         }
134         return component;
135     }
136 
137     /**
138      * Creates a new component of the given <code>type</code> by searching the
139      * <code>META-INF/services</code> directory and finding its default
140      * implementation.
141      *
142      * @param type
143      * @return a new instance of the given <code>type</code>
144      */
145     public Object newDefaultComponent(final Class type)
146     {
147         ExceptionUtils.checkNull("type", type);
148         Object component;
149         try
150         {
151             final String implementation = this.getDefaultImplementation(type);
152             if (StringUtils.isBlank(implementation))
153             {
154                 throw new ComponentContainerException(
155                     "Default configuration file '" + this.getComponentDefaultConfigurationPath(type) +
156                     "' could not be found");
157             }
158             component = ClassUtils.loadClass(implementation).newInstance();
159         }
160         catch (final Throwable throwable)
161         {
162             throw new ComponentContainerException(throwable);
163         }
164         return component;
165     }
166 
167     /**
168      * Returns the expected path to the component's default configuration file.
169      *
170      * @param type the component type.
171      * @return the path to the component configuration file.
172      */
173     protected final String getComponentDefaultConfigurationPath(final Class type)
174     {
175         ExceptionUtils.checkNull("type", type);
176         return SERVICES + type.getName();
177     }
178 
179     /**
180      * Finds the component with the specified Class <code>key</code>. If the
181      * component wasn't explicitly registered then the META-INF/services
182      * directory on the classpath will be searched in order to find the default
183      * component implementation.
184      *
185      * @param key the unique key as a Class.
186      * @return Object the component instance.
187      */
188     public Object findComponent(final Class key)
189     {
190         ExceptionUtils.checkNull("key", key);
191         return this.findComponent(null, key);
192     }
193 
194     /**
195      * Attempts to Find the component with the specified <code>type</code>,
196      * throwing a {@link ComponentContainerException} exception if one can not
197      * be found.
198      *
199      * @param key the unique key of the component as an Object.
200      * @return Object the component instance.
201      */
202     public Object findRequiredComponent(final Class key)
203     {
204         final Object component = this.findComponent(key);
205         if (component == null)
206         {
207             throw new ComponentContainerException(
208                 "No implementation could be found for component '" + key.getName() +
209                 "', please make sure you have a '" + this.getComponentDefaultConfigurationPath(key) +
210                 "' file on your classpath");
211         }
212         return component;
213     }
214 
215     /**
216      * Attempts to find the component with the specified unique <code>key</code>,
217      * if it can't be found, the default of the specified <code>type</code> is
218      * returned, if no default is set, null is returned. The default is the
219      * service found within the META-INF/services directory on your classpath.
220      *
221      * @param key the unique key of the component.
222      * @param type the default type to retrieve if the component can not be
223      *        found.
224      * @return Object the component instance.
225      */
226     public Object findComponent(
227         final String key,
228         final Class type)
229     {
230         ExceptionUtils.checkNull("type", type);
231         try
232         {
233             Object component = this.findComponent(key);
234             if (component == null)
235             {
236                 final String typeName = type.getName();
237                 component = this.findComponent(typeName);
238 
239                 // if the component doesn't have a default already
240                 // (i.e. component == null), then see if we can find the default
241                 // configuration file.
242                 if (component == null)
243                 {
244                     final String defaultImplementation = this.getDefaultImplementation(type);
245                     if (StringUtils.isNotBlank(defaultImplementation))
246                     {
247                         component =
248                             this.registerDefaultComponent(
249                                 ClassUtils.loadClass(typeName),
250                                 ClassUtils.loadClass(defaultImplementation));
251                     }
252                     else
253                     {
254                         LOGGER.warn(
255                             "WARNING! Component's default configuration file '" +
256                             getComponentDefaultConfigurationPath(type) + "' could not be found");
257                     }
258                 }
259             }
260             return component;
261         }
262         catch (final Throwable throwable)
263         {
264             throw new ComponentContainerException(throwable);
265         }
266     }
267 
268     /**
269      * Attempts to find the default configuration file from the
270      * <code>META-INF/services</code> directory. Returns an empty String if
271      * none is found.
272      *
273      * @param type the type (i.e. org.andromda.core.templateengine.TemplateEngine)
274      * @return the default implementation for the argument Class or the empty string if none is found
275      */
276     private String getDefaultImplementation(final Class type)
277     {
278         final String contents = ResourceUtils.getContents(this.getComponentDefaultConfigurationPath(type));
279         return StringUtils.trimToEmpty(contents);
280     }
281 
282     // TODO Convert to generic type checking using template values Collection<T>
283     /**
284      * Finds all components having the given <code>type</code>.
285      * @param type the component type.
286      * @return Collection all components
287      */
288     public Collection findComponentsOfType(final Class type)
289     {
290         final Collection<Object> components = new ArrayList<Object>(this.container.values());
291         final Collection<Object> containerInstances = this.container.values();
292         for (final Object component : containerInstances)
293         {
294             if (component instanceof ComponentContainer)
295             {
296                 components.addAll(((ComponentContainer) component).container.values());
297             }
298         }
299         final Collection<Object> componentsOfType = new ArrayList<Object>();
300         for (final Object component : components)
301         {
302             if (type.isInstance(component))
303             {
304                 componentsOfType.add(component);
305             }
306         }
307         return componentsOfType;
308     }
309 
310     /*
311      * Finds all components having the given <code>type</code>.
312      * @param type the component type.
313      * @return Collection all components
314     public Collection<T> findComponentsOfType(final T type)
315     {
316         final Collection<Object> components = new ArrayList<Object>(this.container.values());
317         final Collection<Object> containerInstances = this.container.values();
318         for (final Object component : containerInstances)
319         {
320             if (component instanceof ComponentContainer)
321             {
322                 components.addAll(((ComponentContainer) component).container.values());
323             }
324         }
325         final Collection<T> componentsOfType = new ArrayList<T>();
326         for (final Object component : components)
327         {
328             if (component.getClass().equals(type.getClass()))
329             {
330                 componentsOfType.add((T) component);
331             }
332         }
333         return componentsOfType;
334     }
335      */
336 
337     /**
338      * Unregisters the component in this container with a unique (within this
339      * container) <code>key</code>.
340      *
341      * @param key the unique key.
342      * @return Object the registered component.
343      */
344     public Object unregisterComponent(final String key)
345     {
346         ExceptionUtils.checkEmpty("key", key);
347         if (LOGGER.isDebugEnabled())
348         {
349             LOGGER.debug("unregistering component with key --> '" + key + '\'');
350         }
351         return container.remove(key);
352     }
353 
354     /**
355      * Finds a component in this container with a unique (within this container)
356      * <code>key</code> registered by the specified <code>namespace</code>.
357      *
358      * @param namespace the namespace for which to search.
359      * @param key the unique key.
360      * @return the found component, or null.
361      */
362     public Object findComponentByNamespace(
363         final String namespace,
364         final Object key)
365     {
366         ExceptionUtils.checkEmpty("namespace", namespace);
367         ExceptionUtils.checkNull("key", key);
368 
369         Object component = null;
370         final ComponentContainer namespaceContainer = this.getNamespaceContainer(namespace);
371         if (namespaceContainer != null)
372         {
373             component = namespaceContainer.findComponent(key);
374         }
375         return component;
376     }
377 
378     /**
379      * Gets an instance of the container for the given <code>namespace</code>
380      * or returns null if one can not be found.
381      *
382      * @param namespace the name of the namespace.
383      * @return the namespace container.
384      */
385     private ComponentContainer getNamespaceContainer(final String namespace)
386     {
387         return (ComponentContainer)this.findComponent(namespace);
388     }
389 
390     /**
391      * Registers true (false otherwise) if the component in this container with
392      * a unique (within this container) <code>key</code> is registered by the
393      * specified <code>namespace</code>.
394      *
395      * @param namespace the namespace for which to register the component.
396      * @param key the unique key.
397      * @return boolean true/false depending on whether or not it is registerd.
398      */
399     public boolean isRegisteredByNamespace(
400         final String namespace,
401         final Object key)
402     {
403         ExceptionUtils.checkEmpty("namespace", namespace);
404         ExceptionUtils.checkNull("key", key);
405         final ComponentContainer namespaceContainer = this.getNamespaceContainer(namespace);
406         return namespaceContainer != null && namespaceContainer.isRegistered(key);
407     }
408 
409     /**
410      * Registers true (false otherwise) if the component in this container with
411      * a unique (within this container) <code>key</code> is registered.
412      *
413      * @param key the unique key.
414      * @return boolean true/false depending on whether or not it is registered.
415      */
416     public boolean isRegistered(final Object key)
417     {
418         return this.findComponent(key) != null;
419     }
420 
421     /**
422      * Registers the component in this container with a unique (within this
423      * container) <code>key</code> by the specified <code>namespace</code>.
424      *
425      * @param namespace the namespace for which to register the component.
426      * @param key the unique key.
427      * @param component
428      */
429     public void registerComponentByNamespace(
430         final String namespace,
431         final Object key,
432         final Object component)
433     {
434         ExceptionUtils.checkEmpty("namespace", namespace);
435         ExceptionUtils.checkNull("component", component);
436         if (LOGGER.isDebugEnabled())
437         {
438             LOGGER.debug("registering component '" + component + "' with key --> '" + key + '\'');
439         }
440         ComponentContainer namespaceContainer = this.getNamespaceContainer(namespace);
441         if (namespaceContainer == null)
442         {
443             namespaceContainer = new ComponentContainer();
444             this.registerComponent(namespace, namespaceContainer);
445         }
446         namespaceContainer.registerComponent(key, component);
447     }
448 
449     /**
450      * Registers the component in this container with a unique (within this
451      * container) <code>key</code>.
452      *
453      * @param key the unique key.
454      * @param component
455      * @return Object the registered component.
456      */
457     public Object registerComponent(
458         final Object key,
459         final Object component)
460     {
461         ExceptionUtils.checkNull("component", component);
462         if (LOGGER.isDebugEnabled())
463         {
464             LOGGER.debug("registering component '" + component + "' with key --> '" + key + '\'');
465         }
466         return this.container.put(key, component);
467     }
468 
469     /**
470      * Registers the "default" for the specified componentInterface.
471      *
472      * @param componentInterface the interface for the component.
473      * @param defaultTypeName the name of the "default" type of the
474      *        implementation to use for the componentInterface. Its expected
475      *        that this is the name of a class.
476      */
477     public void registerDefaultComponent(
478         final Class componentInterface,
479         final String defaultTypeName)
480     {
481         ExceptionUtils.checkNull("componentInterface", componentInterface);
482         ExceptionUtils.checkEmpty("defaultTypeName", defaultTypeName);
483         try
484         {
485             this.registerDefaultComponent(
486                 componentInterface,
487                 ClassUtils.loadClass(defaultTypeName));
488         }
489         catch (final Throwable throwable)
490         {
491             throw new ComponentContainerException(throwable);
492         }
493     }
494 
495     /**
496      * Registers the "default" for the specified componentInterface.
497      *
498      * @param componentInterface the interface for the component.
499      * @param defaultType the "default" implementation to use for the
500      *        componentInterface.
501      * @return Object the registered component.
502      */
503     public Object registerDefaultComponent(
504         final Class componentInterface,
505         final Class defaultType)
506     {
507         ExceptionUtils.checkNull("componentInterface", componentInterface);
508         ExceptionUtils.checkNull("defaultType", defaultType);
509         if (LOGGER.isDebugEnabled())
510         {
511             LOGGER.debug(
512                 "registering default for component '" + componentInterface + "' as type --> '" + defaultType + '\'');
513         }
514         try
515         {
516             final String interfaceName = componentInterface.getName();
517 
518             // check and unregister the component if its registered
519             // so that we can register a new default component.
520             if (this.isRegistered(interfaceName))
521             {
522                 this.unregisterComponent(interfaceName);
523             }
524             final Object component = defaultType.newInstance();
525             this.container.put(interfaceName, component);
526             return component;
527         }
528         catch (final Throwable throwable)
529         {
530             throw new ComponentContainerException(throwable);
531         }
532     }
533 
534     /**
535      * Registers the component of the specified <code>type</code>.
536      *
537      * @param type the type Class.
538      */
539     public void registerComponentType(final Class type)
540     {
541         ExceptionUtils.checkNull("type", type);
542         try
543         {
544             this.container.put(
545                 type,
546                 type.newInstance());
547         }
548         catch (final Throwable throwable)
549         {
550             throw new ComponentContainerException(throwable);
551         }
552     }
553 
554     /**
555      * Registers the components of the specified <code>type</code>.
556      *
557      * @param type the name of a type (must have be able to be instantiated into
558      *        a Class instance)
559      * @return Object an instance of the type registered.
560      */
561     public Object registerComponentType(final String type)
562     {
563         ExceptionUtils.checkNull("type", type);
564         try
565         {
566             return this.registerComponent(
567                 type,
568                 ClassUtils.loadClass(type).newInstance());
569         }
570         catch (final Throwable throwable)
571         {
572             throw new ComponentContainerException(throwable);
573         }
574     }
575 
576     /**
577      * Shuts down this container instance.
578      */
579     public void shutdown()
580     {
581         this.container.clear();
582         ComponentContainer.instance = null;
583     }
584 }