View Javadoc

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