View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.di.impl;
17  
18  import java.util.HashSet;
19  import java.util.Set;
20  import java.util.concurrent.atomic.AtomicLong;
21  
22  import net.sf.jguiraffe.di.BeanContext;
23  import net.sf.jguiraffe.di.BeanContextClient;
24  import net.sf.jguiraffe.di.BeanCreationListener;
25  import net.sf.jguiraffe.di.BeanProvider;
26  import net.sf.jguiraffe.di.BeanStore;
27  import net.sf.jguiraffe.di.ClassLoaderProvider;
28  import net.sf.jguiraffe.di.Dependency;
29  import net.sf.jguiraffe.di.DependencyProvider;
30  import net.sf.jguiraffe.di.InjectionException;
31  
32  /**
33   * <p>
34   * A default implementation of the <code>BeanContext</code> interface.
35   * </p>
36   * <p>
37   * This class allows full access to all beans defined in a hierarchy of
38   * {@link BeanStore}s. Dependencies are dynamically resolved and injected.
39   * </p>
40   * <p>
41   * The class is thread-safe. When operating on a bean store hierarchy it
42   * implements transactional behavior as described in the documentation to the
43   * {@link BeanStore} interface, i.e. if two threads try to access a
44   * {@link BeanProvider} concurrently, one of the will be suspended until the
45   * other one has resolved all of its dependencies. This enables concurrent,
46   * synchronized access to bean stores in a read-only manner. However the bean
47   * stores should not be written at the same time.
48   * </p>
49   *
50   * @author Oliver Heger
51   * @version $Id: DefaultBeanContext.java 205 2012-01-29 18:29:57Z oheger $
52   */
53  public class DefaultBeanContext implements BeanContext
54  {
55      /** A counter for generating transaction IDs. */
56      private static final AtomicLong TX_COUNTER = new AtomicLong();
57  
58      /** Stores the default bean store. */
59      private volatile BeanStore defaultBeanStore;
60  
61      /** An object with the bean creation listeners registered at this context.*/
62      private final BeanCreationListenerSupport creationListeners;
63  
64      /**
65       * Stores an internally used dependency provider that is independent on a
66       * specific transaction. This provider will be used e.g. for resolving the
67       * classes of bean providers.
68       */
69      private final DependencyProvider internalDependencyProvider;
70  
71      /** Holds a reference to the class loader provider used by this context.*/
72      private volatile ClassLoaderProvider classLoaderProvider;
73  
74      /**
75       * Creates a new instance of {@code DefaultBeanContext}.
76       */
77      public DefaultBeanContext()
78      {
79          this(null);
80      }
81  
82      /**
83       * Creates a new instance of {@code DefaultBeanContext} and sets the default
84       * bean store. This parameter is optional. If no {@code BeanStore} is
85       * provided, a default store must be set later or only methods can be used
86       * that expect a store as argument.
87       *
88       * @param defStore the default bean store
89       */
90      public DefaultBeanContext(BeanStore defStore)
91      {
92          internalDependencyProvider = new DefaultDependencyProvider(this);
93          creationListeners = new BeanCreationListenerSupport(this);
94          classLoaderProvider = new DefaultClassLoaderProvider();
95          setDefaultBeanStore(defStore);
96      }
97  
98      /**
99       * Returns the {@code ClassLoaderProvider} used by this bean context.
100      *
101      * @return the {@code ClassLoaderProvider}
102      * @see #setClassLoaderProvider(ClassLoaderProvider)
103      */
104     public ClassLoaderProvider getClassLoaderProvider()
105     {
106         return classLoaderProvider;
107     }
108 
109     /**
110      * Sets the {@code ClassLoaderProvider} to be used by this bean context. The
111      * {@code ClassLoaderProvider} is needed when dependencies to beans are to
112      * be resolved that specified by class names. When a new {@code
113      * DefaultBeanContext} instance is created, a default {@code
114      * ClassLoaderProvider} is set. It is then possible to change this object
115      * using this method.
116      *
117      * @param classLoaderProvider the new {@code ClassLoaderProvider} (must not
118      *        be <b>null</b>)
119      * @throws IllegalArgumentException if the parameter is <b>null</b>
120      */
121     public void setClassLoaderProvider(ClassLoaderProvider classLoaderProvider)
122     {
123         if (classLoaderProvider == null)
124         {
125             throw new IllegalArgumentException(
126                     "Class loader provider must not be null!");
127         }
128         this.classLoaderProvider = classLoaderProvider;
129     }
130 
131     /**
132      * Obtains a list of the classes of the beans defined in the default bean
133      * store.
134      *
135      * @return the classes of the defined beans
136      */
137     public Set<Class<?>> beanClasses()
138     {
139         return beanClasses(getDefaultBeanStore());
140     }
141 
142     /**
143      * Obtains a list of the classes of the beans defined in the given store and
144      * its parents.
145      *
146      * @param store the store to start from
147      * @return the classes of the defined beans
148      */
149     public Set<Class<?>> beanClasses(BeanStore store)
150     {
151         Set<Class<?>> result = new HashSet<Class<?>>();
152         fetchBeanClasses(store, result);
153         return result;
154     }
155 
156     /**
157      * Returns a set with the names of the beans defined in the default bean
158      * store.
159      *
160      * @return the names of the defined beans
161      */
162     public Set<String> beanNames()
163     {
164         return beanNames(getDefaultBeanStore());
165     }
166 
167     /**
168      * Returns a set with the names of the beans defined in the given bean store
169      * (or its parent).
170      *
171      * @param store the bean store
172      * @return a set with the names of the defined beans
173      */
174     public Set<String> beanNames(BeanStore store)
175     {
176         Set<String> names = new HashSet<String>();
177         fetchBeanNames(store, names);
178         return names;
179     }
180 
181     /**
182      * Tests whether a bean with the given name can be found in the default
183      * store.
184      *
185      * @param name the name of the bean
186      * @return a flag whether this bean can be found
187      */
188     public boolean containsBean(String name)
189     {
190         return containsBean(name, getDefaultBeanStore());
191     }
192 
193     /**
194      * Tests whether a bean with the given name can be found in the specified
195      * bean store. <b>null</b> can be specified for both the name and the bean
196      * store; result will then be <b>false</b>.
197      *
198      * @param name the name of the bean
199      * @param store the bean store
200      * @return a flag whether this bean can be found in this store
201      */
202     public boolean containsBean(String name, BeanStore store)
203     {
204         return (name != null) ? containsDependency(NameDependency
205                 .getInstance(name), store) : false;
206     }
207 
208     /**
209      * Tests whether a bean with the given bean class can be found in the
210      * default bean store.
211      *
212      * @param beanClass the class of the bean
213      * @return a flag whether this bean can be found
214      */
215     public boolean containsBean(Class<?> beanClass)
216     {
217         return containsBean(beanClass, getDefaultBeanStore());
218     }
219 
220     /**
221      * Tests whether a bean with the given class can be found in the specified
222      * bean store. <b>null</b> can be specified for both the name and the bean
223      * store; result will then be <b>false</b>.
224      *
225      * @param beanClass the class of the bean
226      * @param store the bean store
227      * @return a flag whether this bean can be found in this store
228      */
229     public boolean containsBean(Class<?> beanClass, BeanStore store)
230     {
231         return (beanClass != null) ? containsDependency(ClassDependency
232                 .getInstance(beanClass), store) : false;
233     }
234 
235     /**
236      * Returns the bean with the specified name from the default store.
237      *
238      * @param name the name of the bean to retrieve
239      * @return the bean with this name
240      * @throws InjectionException if an error occurs
241      */
242     public Object getBean(String name)
243     {
244         return getBean(name, getDefaultBeanStore());
245     }
246 
247     /**
248      * Returns the bean with the specified name from the given bean store.
249      *
250      * @param name the name of the bean to retrieve
251      * @param store the bean store
252      * @return the bean with this name
253      * @throws InjectionException if an error occurs
254      */
255     public Object getBean(String name, BeanStore store)
256     {
257         if (name == null)
258         {
259             throw new InjectionException("Bean name must not be null!");
260         }
261         return getBean(NameDependency.getInstance(name), store);
262     }
263 
264     /**
265      * Returns the bean with the specified class from the default bean store.
266      *
267      * @param <T> the type of the bean to be retrieved
268      * @param beanCls the class of the bean to be retrieved
269      * @return the bean with this class
270      * @throws InjectionException if an error occurs
271      */
272     public <T> T getBean(Class<T> beanCls)
273     {
274         return getBean(beanCls, getDefaultBeanStore());
275     }
276 
277     /**
278      * Returns the bean with the specified class from the given bean store.
279      *
280      * @param <T> the type of the bean to be retrieved
281      * @param beanCls the class of the bean to be retrieved
282      * @param store the bean store
283      * @return the bean with this class
284      * @throws InjectionException if an error occurs
285      */
286     public <T> T getBean(Class<T> beanCls, BeanStore store)
287     {
288         if (beanCls == null)
289         {
290             throw new InjectionException("Bean class must not be null!");
291         }
292         return beanCls
293                 .cast(getBean(ClassDependency.getInstance(beanCls), store));
294     }
295 
296     /**
297      * Returns the default bean store.
298      *
299      * @return the default bean store
300      */
301     public BeanStore getDefaultBeanStore()
302     {
303         return defaultBeanStore;
304     }
305 
306     /**
307      * Searches for the specified {@code BeanProvider} in the accessible bean
308      * stores (starting with the default bean store) and the returns the name,
309      * under which it is registered.
310      *
311      * @param beanProvider the {@code BeanProvider} in question
312      * @return the corresponding bean name or <b>null</b> if it cannot be
313      *         resolved
314      */
315     public String beanNameFor(BeanProvider beanProvider)
316     {
317         return beanNameFor(beanProvider, getDefaultBeanStore());
318     }
319 
320     /**
321      * Searches for the specified {@code BeanProvider} in the accessible bean
322      * stores (starting with the specified bean store) and the returns the name,
323      * under which it is registered.
324      *
325      * @param beanProvider the {@code BeanProvider} in question
326      * @param store the {@code BeanStore}
327      * @return the corresponding bean name or <b>null</b> if it cannot be
328      *         resolved
329      */
330     public String beanNameFor(BeanProvider beanProvider, BeanStore store)
331     {
332         if (store == null)
333         {
334             return null;
335         }
336 
337         else
338         {
339             for (String s : store.providerNames())
340             {
341                 if (beanProvider == store.getBeanProvider(s))
342                 {
343                     return s;
344                 }
345             }
346 
347             return beanNameFor(beanProvider, store.getParent());
348         }
349     }
350 
351     /**
352      * Closes this {@code BeanContext}. This is just an empty dummy
353      * implementation. There are no resources to be freed.
354      */
355     public void close()
356     {
357     }
358 
359     /**
360      * Sets the default bean store.
361      *
362      * @param store the new default bean store
363      */
364     public void setDefaultBeanStore(BeanStore store)
365     {
366         defaultBeanStore = store;
367     }
368 
369     /**
370      * Adds the specified {@code BeanCreationListener} to this context.
371      *
372      * @param l the listener to add (must not be <b>null</b>)
373      * @throws IllegalArgumentException if the listener is <b>null</b>
374      */
375     public void addBeanCreationListener(BeanCreationListener l)
376     {
377         creationListeners.addBeanCreationListener(l);
378     }
379 
380     /**
381      * Removes the specified {@code BeanCreationListener} from this context.
382      *
383      * @param l the listener to be removed
384      */
385     public void removeBeanCreationListener(BeanCreationListener l)
386     {
387         creationListeners.removeBeanCreationListener(l);
388     }
389 
390     /**
391      * Obtains the bean from the {@link BeanProvider} specified by the given
392      * {@link Dependency}. This method is called by the other
393      * <code>getBean()</code> methods. It does the real work.
394      *
395      * @param dependency the dependency to be resolved
396      * @param store the current store
397      * @return the bean managed by the specified provider
398      * @throws InjectionException if an error occurs while resolving the
399      *         dependency
400      */
401     protected Object getBean(Dependency dependency, BeanStore store)
402     {
403         BeanStore root = fetchRootStore(store);
404         DefaultDependencyProvider depProvider = new DefaultDependencyProvider(this);
405 
406         synchronized (root)
407         {
408             try
409             {
410                 while (!depProvider.initialize(dependency, store))
411                 {
412                     // wait until we can lock all dependent providers
413                     waitForTx(root);
414                 }
415             }
416             catch (InterruptedException iex)
417             {
418                 throw new InjectionException(iex);
419             }
420 
421             // lock the whole dependency graph
422             depProvider.lock(nextTxID());
423         }
424 
425         try
426         {
427             return depProvider.getDependentBean(dependency);
428         }
429         finally
430         {
431             try
432             {
433                 // perform postponed initialization
434                 depProvider.invokeInitializers();
435             }
436 
437             finally
438             {
439                 synchronized (root)
440                 {
441                     // unlock dependency graph
442                     depProvider.lock(null);
443                     // resume a waiting transaction
444                     root.notify();
445                 }
446             }
447         }
448     }
449 
450     /**
451      * Waits at the specified bean store until the current transaction finishes.
452      * This method is called when a transaction affects a bean provider that is
453      * already locked by another transaction. In this case this other
454      * transaction has to finish first. This implementation calls
455      * <code>wait()</code> on the given bean store.
456      *
457      * @param root the root bean store
458      * @throws InterruptedException if the thread is interrupted
459      */
460     protected void waitForTx(BeanStore root) throws InterruptedException
461     {
462         root.wait();
463     }
464 
465     /**
466      * Returns the next transaction ID. Each starting transaction is assigned a
467      * new ID.
468      *
469      * @return the next transaction ID
470      */
471     static Long nextTxID()
472     {
473         return TX_COUNTER.incrementAndGet();
474     }
475 
476     /**
477      * Resets the counter for transaction IDs. This method is mainly used for
478      * testing purposes.
479      */
480     static void resetTxID()
481     {
482         TX_COUNTER.set(0);
483     }
484 
485     /**
486      * Returns the internally used dependency provider.
487      *
488      * @return the internal dependency provider
489      */
490     DependencyProvider getInternalDependencyProvider()
491     {
492         return internalDependencyProvider;
493     }
494 
495     /**
496      * Notifies this context about the creation of a bean. This method is called
497      * by the dependency provider when a {@code BeanProvider} indicates the
498      * creation of a new bean. It performs some post processing of the bean (for
499      * instance, it checks whether the bean implements certain interfaces
500      * evaluated by the framework) and notifies the {@code BeanCreationListener}
501      * s registered at this context.
502      *
503      * @param bean the bean that was created
504      * @param beanProvider the responsible {@code BeanProvider}
505      * @param depProvider the current {@code DependencyProvider}
506      */
507     void beanCreated(Object bean, BeanProvider beanProvider,
508             DefaultDependencyProvider depProvider)
509     {
510         depProvider.setCreationBeanContext(this);
511         creationListeners
512                 .fireBeanCreationEvent(beanProvider, depProvider, bean);
513 
514         if (bean instanceof BeanContextClient)
515         {
516             ((BeanContextClient) bean).setBeanContext(depProvider
517                     .getCreationBeanContext());
518         }
519     }
520 
521     /**
522      * Helper method for collecting the classes of the defined beans.
523      *
524      * @param store the current bean store
525      * @param clsSet the set where the classes are stored
526      */
527     private void fetchBeanClasses(BeanStore store, Set<Class<?>> clsSet)
528     {
529         if (store != null)
530         {
531             for (String n : store.providerNames())
532             {
533                 BeanProvider p = store.getBeanProvider(n);
534                 clsSet.add(p.getBeanClass(getInternalDependencyProvider()));
535             }
536             fetchBeanClasses(store.getParent(), clsSet);
537         }
538     }
539 
540     /**
541      * Helper method for collecting the names of the defined beans.
542      *
543      * @param store the current bean store
544      * @param names the set, in which to store the names
545      */
546     private void fetchBeanNames(BeanStore store, Set<String> names)
547     {
548         if (store != null)
549         {
550             names.addAll(store.providerNames());
551             fetchBeanNames(store.getParent(), names);
552         }
553     }
554 
555     /**
556      * Tries to resolve the given dependency in the specified bean store.
557      *
558      * @param dep the dependency to resolve
559      * @param store the store
560      * @return a flag whether the dependency could be resolved
561      */
562     private boolean containsDependency(Dependency dep, BeanStore store)
563     {
564         if (store == null)
565         {
566             return false;
567         }
568 
569         try
570         {
571             dep.resolve(store, getInternalDependencyProvider());
572             return true;
573         }
574         catch (InjectionException iex)
575         {
576             return false;
577         }
578     }
579 
580     /**
581      * Obtains the root store of the given bean store. Navigates through the
582      * hierarchy of parent stores until the root is reached. If the passed in
583      * store is already <b>null</b>, an exception is thrown.
584      *
585      * @param store the current store
586      * @return the root store of this hierarchy
587      * @throws InjectionException if the store is <b>null</b>
588      */
589     private BeanStore fetchRootStore(BeanStore store)
590     {
591         BeanStore current = store;
592         BeanStore result = null;
593 
594         while (current != null)
595         {
596             result = current;
597             current = current.getParent();
598         }
599 
600         if (result == null)
601         {
602             throw new InjectionException("Store must not be null!");
603         }
604         return result;
605     }
606 }