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 }