001package org.andromda.core.common;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.LinkedHashMap;
006import java.util.Map;
007import org.apache.commons.lang.StringUtils;
008import org.apache.log4j.Logger;
009
010/**
011 * <p> This handles all registration and retrieval of components within the
012 * framework. The purpose of this container is so that we can register default
013 * services in a consistent manner by creating a component interface and then
014 * placing the file which defines the default implementation in the
015 * 'META-INF/services/' directory found on the classpath.
016 * </p>
017 * <p> In order to create a new component that can be registered/found through
018 * this container you must perform the following steps:
019 * <ol>
020 * <li>Create the component interface (i.e.
021 * org.andromda.core.repository.RepositoryFacade)</li>
022 * <li>Create the component implementation (i.e.
023 * org.andromda.repositories.mdr.MDRepositoryFacade)</li>
024 * <li>Create a file with the exact same name as the fully qualified name of
025 * the component (i.e. org.andromda.core.repository.RepositoryFacade) that
026 * contains the name of the implementation class (i.e.
027 * org.andromda.repositories.mdr.MDRepostioryFacade) and place this in the
028 * META-INF/services/ directory within the core.</li>
029 * </ol>
030 * <p>After you perform the above steps, the component can be found by the methods
031 * within this class. See each below method for more information on how each
032 * performs lookup/retrieval of the components.
033 * </p>
034 *
035 * @author Chad Brandon
036 * @author Bob Fields
037 */
038public class ComponentContainer
039{
040    private static final Logger LOGGER = Logger.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<Object, Object> container = new LinkedHashMap<Object, Object>();
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    {
064        if (instance == null)
065        {
066            instance = new ComponentContainer();
067        }
068        return instance;
069    }
070
071    /**
072     * Finds the component with the specified <code>key</code>.
073     *
074     * @param key the unique key of the component as an Object.
075     * @return Object the component instance.
076     */
077    public Object findComponent(final Object key)
078    {
079        return this.container.get(key);
080    }
081
082    /**
083     * Creates a new component of the given <code>implementation</code> (if it
084     * isn't null or empty), otherwise attempts to find the default
085     * implementation of the given <code>type</code> by searching the
086     * <code>META-INF/services</code> directory for the default
087     * implementation.
088     *
089     * @param implementation the fully qualified name of the implementation
090     *        class.
091     * @param type the type to retrieve if the implementation is empty.
092     * @return a new instance of the given <code>type</code>
093     */
094    public Object newComponent(
095        String implementation,
096        final Class type)
097    {
098        Object component;
099        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}