KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > core > component > DefaultComponentKeeper


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.core.component;
19
20 import java.util.Collection JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.HashSet JavaDoc;
23 import java.util.LinkedList JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26
27 import org.sape.carbon.core.component.factory.ComponentFactory;
28 import org.sape.carbon.core.component.factory.ComponentFactoryConfiguration;
29 import org.sape.carbon.core.component.lifecycle.LifecycleInterceptor;
30 import org.sape.carbon.core.component.lifecycle.LifecycleStateEnum;
31 import org.sape.carbon.core.config.Config;
32 import org.sape.carbon.core.config.InvalidConfigurationException;
33 import org.sape.carbon.core.exception.ExceptionUtility;
34 import org.sape.carbon.core.exception.InvalidParameterException;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39
40 /**
41  * <p>The default implemetation of ComponentKeeper. This implementation uses
42  * a <code>HashMap</code> to store references to all live components. If
43  * requested component does not exist, it delegates to a
44  * <code>ComponentFactory</code> to create the requested component. It uses
45  * a <code>HashMap</code> of locks to ensure that a component is constructed
46  * only once.</p>
47  *
48  * Copyright 2002 Sapient
49  * @since carbon 1.0
50  * @author Douglas Voet, January 2002
51  * @version $Revision: 1.43 $($Author: dvoet $ / $Date: 2003/05/05 21:21:11 $)
52  * @stereotype implementationClass
53  */

54 public class DefaultComponentKeeper implements ComponentKeeper {
55
56     /** Provides handle to Apache-commons logger. */
57     private Log log = LogFactory.getLog(this.getClass());
58
59     /** <p>ComponentFactory used to build all components in the system.</p> */
60     private ComponentFactory componentFactory = null;
61
62     /**
63      * Map of locks used to ensure that component are built only once.
64      * Key = (String) logicalComponentName, Value = (Object) lock.
65      */

66     private Map JavaDoc creationLocks = new HashMap JavaDoc();
67
68     /**
69      * <p>Map of all <code>Component</code>s known to the system
70      * keyed by (String) logicalComponentName.</p>
71      * @inherit
72      *@link aggregation
73      * @associates <{Component}>
74      */

75     private Map JavaDoc componentMap = new HashMap JavaDoc();
76
77     /**
78      * Storage for components that have been created and intialized, but
79      * not configured or started.
80      * Used only in fetchNewComponent().
81      */

82     private Map JavaDoc nascentComponents = new HashMap JavaDoc();
83
84     /**
85      * <p>List of all component names known to the keeper
86      * in order of their creation. Used in destroyAllComponents
87      * to determine the order in which to destroy components.</p>
88      *
89      * <p>The List implementation used is LinkedList. This is because it
90      * is fast when it comes to appending or removing items. LikedLists are
91      * not optimized for Random access, but this will be rare (only when
92      * destroyComponent is called outside of destroyAllComponents).</p>
93      */

94     private List JavaDoc componentNameList = new LinkedList JavaDoc();
95
96     /**
97      * Maintains a reference to the thread for shutting down the component
98      * system on JVM exit.
99      */

100     private Thread JavaDoc shutdownHook;
101
102     /** @link dependency */
103     /*#ComponentKeeperConfiguration lnkComponentKeeperConfiguration;*/
104
105     /**
106      * protected constructor prevents direct instantiation but does not
107      * prevent extenstion, this is meant to be called by
108      * DefaultComponentKeeperFactory
109      *
110      * @param configuration the configuration of this component keeper
111      */

112     protected DefaultComponentKeeper(
113             ComponentKeeperConfiguration configuration) {
114
115         if (log.isTraceEnabled()) {
116             log.trace("Configuring the default component keeper");
117         }
118
119         configure(configuration);
120
121     }
122
123     /**
124      * <p>Creates a new shutdown hook, if one does not already exist, and
125      * registers it with the JVM shutdown hook.</p>
126      *
127      * @see java.lang.Runtime#addShutdownHook
128      */

129     protected void registerShutdownHook() {
130         if (log.isInfoEnabled()) {
131             log.info("Registering JVM shutdown hook for ComponentKeeper.");
132         }
133
134         if (this.shutdownHook == null) {
135             this.shutdownHook = new Thread JavaDoc(new Runnable JavaDoc() {
136                 public void run() {
137                     System.out.println(
138                         "System shutting down. Destroying all components.");
139                     try {
140                         destroyAllComponents();
141                     } catch (Exception JavaDoc e) {
142                         System.out.println(
143                             ExceptionUtility.printStackTracesToString(e));
144                     }
145                     System.out.println(
146                         "System shut down complete.");
147                 }
148             }, "Carbon Shutdown Thread");
149             this.shutdownHook.setName("Carbon Shutdown Hook");
150             Runtime.getRuntime().addShutdownHook(this.shutdownHook);
151         }
152     }
153
154     /**
155      * <p>Gets a reference to the component specified by the name parameter.
156      * Builds the component if it has not yet been created.</p>
157      *
158      * <p>This implementation delegates to a <code>ComponentFactory</code> to
159      * build components.</p>
160      *
161      * @param logicalComponentName the name of the component
162      *
163      * @return a reference to the component specified by name
164      *
165      * @throws ComponentNotFoundException if the component specified by
166      * logicalComponentName cannot be found in configuration.
167      * @throws InvalidParameterException if logicalComponentName is
168      * an empty string or logicalComponentName is null
169      */

170     public Component fetchComponent(String JavaDoc logicalComponentName) {
171         if (logicalComponentName == null || logicalComponentName.equals("")) {
172             throw new InvalidParameterException(this.getClass(),
173                 "logicalComponentName was an empty String");
174         }
175
176         if (log.isTraceEnabled()) {
177             log.trace("fetching component: ["
178                 + logicalComponentName
179                 + "]");
180         }
181
182         Component requestedComponent = getComponent(logicalComponentName);
183
184         if (requestedComponent != null
185             && ((LifecycleInterceptor) requestedComponent).getLifecycleState()
186                 == LifecycleStateEnum.DESTROYED) {
187
188             // the component was destroyed by other means than using the
189
// component keeper, remove it from the keeper by calling
190
// destroyComponent
191
destroyComponent(logicalComponentName);
192             // ComponentKeeper shouldn't return a DESTROYED component.
193
// setting requestedComponent to null will ensure it is rebuilt
194
// in the next if block
195
requestedComponent = null;
196         }
197
198         if (requestedComponent == null) {
199             // the object was not in the map so we need to build it
200
requestedComponent = fetchNewComponent(logicalComponentName);
201         }
202         return requestedComponent;
203     }
204
205     /**
206      * <p>Destroys all components.</p>
207      *
208      * <p>This implementation destroys components in the order in which they
209      * were created. Note that this will halt all requests for components
210      * until all existing components are destroyed.</p>
211      *
212      * @since carbon 1.0
213      */

214     public void destroyAllComponents() {
215         // block all requests until all components are destroyed
216
synchronized (this.componentMap) {
217             if (log.isTraceEnabled()) {
218                 log.trace("destroying all components");
219             }
220             while (!this.componentNameList.isEmpty()) {
221                 destroyComponent((String JavaDoc) this.componentNameList.get(0));
222             }
223         }
224     }
225
226     /**
227      * <p>Destroys the component specified by the name parameter.</p>
228      *
229      * @param logicalComponentName the name of the component
230      */

231     public void destroyComponent(String JavaDoc logicalComponentName) {
232
233         if (log.isTraceEnabled()) {
234             log.trace("destroying component: " + logicalComponentName);
235         }
236
237         LifecycleInterceptor requestedComponent;
238         // remove the component from the componentMap but maintain a
239
// reference to it in order to call its destroy lifecycle method
240
requestedComponent =
241             (LifecycleInterceptor) removeComponent(logicalComponentName);
242
243         if (requestedComponent != null) {
244             requestedComponent.destroyComponent();
245         }
246     }
247
248     /**
249      * This implementation returns a copy of the keySet of the internal
250      * map of components.
251      *
252      * @see ComponentKeeper#getComponentNames()
253      * @return a collection of all components in this keeper
254      */

255     public Collection JavaDoc getComponentNames() {
256         synchronized (this.componentMap) {
257             return new HashSet JavaDoc(this.componentMap.keySet());
258         }
259     }
260
261     /**
262      * <p>Lifecycle method to configure the keeper. This method instantiates
263      * <code>ComponentFactory</code>. This must be called prior to using
264      * the keeper.</p>
265      * @param keeperConfiguration the configuration of this component keeper
266      */

267     private void configure(ComponentKeeperConfiguration keeperConfiguration) {
268         ComponentFactoryConfiguration factoryConfiguration =
269             keeperConfiguration.getComponentFactoryConfiguration();
270
271         try {
272
273             this.componentFactory =
274                 (ComponentFactory) factoryConfiguration
275                     .getComponentFactoryClass()
276                     .newInstance();
277
278             if (log.isTraceEnabled()) {
279                 log.trace("Initialized ["
280                     + this.componentFactory.getClass().getName()
281                     + "] as component factory");
282             }
283
284         } catch (ClassCastException JavaDoc cce) {
285             throw new InvalidConfigurationException(
286                 this.getClass(),
287                 keeperConfiguration.getConfigurationName(),
288                 "ComponentFactoryClass",
289                 "Specifed ComponentFactory ["
290                     + factoryConfiguration.getComponentFactoryClass()
291                     + "] does not implement ComponentFactory",
292                 cce);
293
294         } catch (InstantiationException JavaDoc ie) {
295             throw new InvalidConfigurationException(
296                 this.getClass(),
297                 keeperConfiguration.getConfigurationName(),
298                 "ComponentFactoryClass",
299                 "Could not instantiate ComponentFactory ["
300                     + factoryConfiguration.getComponentFactoryClass()
301                     + "]",
302                 ie);
303
304         } catch (IllegalAccessException JavaDoc iae) {
305             throw new InvalidConfigurationException(
306                 this.getClass(),
307                 keeperConfiguration.getConfigurationName(),
308                 "ComponentFactoryClass",
309                 "Could not instantiate ComponentFactory ["
310                     + factoryConfiguration.getComponentFactoryClass()
311                     + "]",
312                 iae);
313         }
314
315         if (keeperConfiguration.isRegisterShutdownHook()) {
316             registerShutdownHook();
317         }
318     }
319
320     /**
321      * Gets a component from componentMap using logicalComponentName as the key.
322      * Syncronized on componentMap.
323      *
324      * @param logicalComponentName name of the component
325      * @return Component the requested component or null if it is not in
326      * componentMap
327      */

328     private Component getComponent(String JavaDoc logicalComponentName) {
329         synchronized (this.componentMap) {
330             return (Component) this.componentMap.get(logicalComponentName);
331         }
332     }
333
334     /**
335      * Puts a component into componentMap using logicalComponentName as the key
336      * and adds logicalComponentName to the end of componentNameList.
337      * Syncronized on componentMap.
338      *
339      * @param logicalComponentName name of the component
340      * @param component component object to add
341      */

342     private void addComponent(
343         String JavaDoc logicalComponentName,
344         Component component) {
345
346         synchronized (this.componentMap) {
347             // put the new component in the componentMap so other's
348
// can have access to it
349
this.componentMap.put(logicalComponentName, component);
350             // add to the componentNameList as well
351
this.componentNameList.add(logicalComponentName);
352         }
353     }
354
355     /**
356      * Removes the component keyed by logicalComponentName from componentMap
357      * and removes logicalComponentName from componentNameList.
358      * Syncronized on componentMap.
359      *
360      * @param logicalComponentName name of the component to remove
361      * @return Component the removed component or null if it is not in
362      * componentMap
363      */

364     private Component removeComponent(String JavaDoc logicalComponentName) {
365         synchronized (this.componentMap) {
366             this.componentNameList.remove(logicalComponentName);
367             return (Component) this.componentMap.remove(logicalComponentName);
368         }
369     }
370
371     /**
372      * <p>This method fetches a new component. It deals with the concurency
373      * issues associated with multiple threads requesting a component that has
374      * not yet been created at the same time. It ensures that only one thread
375      * will create the component. This is done through the follwing logic:</p>
376      * <ol>
377      * <li>Synchrnize on the lock for the requested component. Once this is
378      * done, it is guranteed that the current thread is the only one
379      * manipulating the component.</li>
380      * <li>Check the main component map (this.componentMap)
381      * to see if the requested component is
382      * there. If it is, another thread has created it and started it. Return
383      * the component from the main map.</li>
384      * <li>Check the nacentComponents to see the component is there. If it is,
385      * this is a circular dependency and the thread is reentering the method
386      * looking for the component for the nth time. The component has been
387      * created and initialized, but not configured or started. Return the
388      * component from nacentComponents</li>
389      * <li>If we have gotten this far, no other thread has created the component
390      * so build the component using the component factory.</li>
391      * <li>Place the component in nacentComponents so reentrant requests can
392      * see the component.</li>
393      * <li>Start the component (calls the lifecycle methods configure and
394      * start). This is where circular references may be initiated.</li>
395      * <li>Remove the component from nacentComponents, put it in the main
396      * component map and return the newly constructed component.</li>
397      * </ol>
398      *
399      * @param logicalComponentName the name of the new component
400      * @return the new component
401      */

402     private Component fetchNewComponent(String JavaDoc logicalComponentName) {
403
404         if (log.isTraceEnabled()) {
405             log.trace("fetching new component: " + logicalComponentName);
406         }
407
408         // get the lock used for this component, note this is syncronized
409
// on this.creationLocks - bottleneck for all component creations
410
Object JavaDoc requestedComponentLock = getCreationLock(logicalComponentName);
411
412         synchronized (requestedComponentLock) {
413             Component requestedComponent = getComponent(logicalComponentName);
414
415             if (requestedComponent == null) {
416                 // check nascentComponents first, it the component is there,
417
// return it.
418
synchronized (this.nascentComponents) {
419                     requestedComponent =
420                         (Component) this.nascentComponents.get(
421                             logicalComponentName);
422                 }
423
424                 if (requestedComponent == null) {
425                     // call componentFactory to build the component
426
requestedComponent =
427                         this.componentFactory.getInstance(logicalComponentName);
428
429                     // put the newly created component in nascentComponents
430
// so other component fetches on this thread can access it
431
synchronized (this.nascentComponents) {
432                         this.nascentComponents.put(
433                             logicalComponentName,
434                             requestedComponent);
435                     }
436
437                     // now start up the comonent
438
try {
439                         startComponent(
440                             logicalComponentName,
441                             requestedComponent);
442
443                     } finally {
444                         // remove component from nascentComponents, if it
445
// started successfully, it will be made available
446
// to everyone
447
synchronized (this.nascentComponents) {
448                             this.nascentComponents.remove(logicalComponentName);
449                         }
450                     }
451
452                     // add the component to the main map
453
addComponent(logicalComponentName, requestedComponent);
454                 }
455             }
456
457             return requestedComponent;
458         }
459
460     }
461
462     /**
463      * <p>Calls the Lifecycle subsystem to configure and start the component.
464      * When this call completes, the component should be in a
465      * LifecycleStateEnum.RUNNING state.</p>
466      *
467      * @param logicalComponentName the name of component to start
468      * @param component the component to start
469      */

470     private void startComponent(
471         String JavaDoc logicalComponentName,
472         Component component) {
473
474         if (log.isTraceEnabled()) {
475             log.trace("starting component: " + logicalComponentName);
476         }
477
478         LifecycleInterceptor lifecycleComponentView =
479             (LifecycleInterceptor) component;
480
481         ComponentConfiguration configuration =
482             (ComponentConfiguration) Config.getInstance().fetchConfiguration(
483                 logicalComponentName);
484         lifecycleComponentView.configureComponent(configuration);
485         lifecycleComponentView.startComponent();
486     }
487
488     /**
489      * Gets the lock object used to synchronize the creation of the component
490      * signified by the supplied name.
491      * @param name the name of the component to get the lock for
492      * @return Object the lock object used to synchronize the creation of any
493      * components with the given name
494      */

495     private Object JavaDoc getCreationLock(String JavaDoc name) {
496         synchronized (this.creationLocks) {
497             Object JavaDoc lock = this.creationLocks.get(name);
498             if (lock == null) {
499                 lock = new Object JavaDoc();
500                 this.creationLocks.put(name, lock);
501             }
502
503             return lock;
504         }
505     }
506 }
507
Popular Tags