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
241
242
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
493
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 }