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}