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.HashMap;
19  import java.util.LinkedList;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Queue;
23  import java.util.Set;
24  
25  import net.sf.jguiraffe.di.BeanContext;
26  import net.sf.jguiraffe.di.BeanInitializer;
27  import net.sf.jguiraffe.di.BeanProvider;
28  import net.sf.jguiraffe.di.BeanStore;
29  import net.sf.jguiraffe.di.ClassLoaderProvider;
30  import net.sf.jguiraffe.di.Dependency;
31  import net.sf.jguiraffe.di.DependencyProvider;
32  import net.sf.jguiraffe.di.InjectionException;
33  import net.sf.jguiraffe.di.InvocationHelper;
34  
35  /**
36   * <p>
37   * An internally used implementation of the <code>DependencyProvider</code>
38   * interface.
39   * </p>
40   * <p>
41   * An instance of this class is created by the default {@link BeanContext}
42   * implementation at the beginning of a transaction. Its task is to obtain and
43   * cache all dependencies of the currently requested bean. This can fail if a
44   * dependency cannot be resolved or one of the dependent bean providers is
45   * already locked by another transaction. In this case the transaction has to be
46   * suspended.
47   * </p>
48   * <p>
49   * If an instance could be successfully initialized, it allows access to all
50   * dependent bean providers - and no more. So the
51   * <code>getDependentBean()</code> method can be implemented in a meaningful
52   * way.
53   * </p>
54   * <p>
55   * This class works closely together with the default bean context
56   * implementation. From there it also obtains a map with the registered class
57   * loaders. Instances are confined to a single thread, so there is no need of
58   * being thread-safe.
59   * </p>
60   *
61   * @author Oliver Heger
62   * @version $Id: DefaultDependencyProvider.java 205 2012-01-29 18:29:57Z oheger $
63   */
64  class DefaultDependencyProvider implements DependencyProvider
65  {
66      /**
67       * Holds a reference to the associated bean context implementation.
68       */
69      private final DefaultBeanContext beanContext;
70  
71      /** Stores the resolved dependencies. */
72      private Map<Dependency, BeanProvider> dependencyMap;
73  
74      /** A list with the registered bean initializers. */
75      private List<BeanInitializer> initializers;
76  
77      /** The context that is responsible for a bean creation event. */
78      private BeanContext creationBeanContext;
79  
80      /** The invocation helper used by this instance. */
81      private InvocationHelper invocationHelper;
82  
83      /**
84       * Creates a new instance of {@code DefaultDependencyProvider} and
85       * initializes it with the bean context implementation it assists.
86       *
87       * @param context the associated bean context
88       */
89      public DefaultDependencyProvider(DefaultBeanContext context)
90      {
91          beanContext = context;
92      }
93  
94      /**
95       * Returns the {@code BeanContext} that is responsible for a bean creation.
96       *
97       * @return the {@code BeanContext} responsible for a bean creation
98       */
99      public BeanContext getCreationBeanContext()
100     {
101         return (creationBeanContext != null) ? creationBeanContext
102                 : getBeanContext();
103     }
104 
105     /**
106      * Sets the {@code BeanContext} that is responsible for a bean creation.
107      * This method is intended to be called by a bean creation listener to set
108      * the correct context when there is a complex structure of combined and
109      * wrapped bean contexts.
110      *
111      * @param creationBeanContext the creation bean context
112      */
113     public void setCreationBeanContext(BeanContext creationBeanContext)
114     {
115         this.creationBeanContext = creationBeanContext;
116     }
117 
118     /**
119      * Initializes this object. This method resolves the specified dependency
120      * and, recursively, all dependencies it depends on. The found bean
121      * providers are stored in an internal data structure. If a dependency
122      * cannot be resolved, a <code>InjectionException</code> exception is
123      * thrown. The return value indicates whether all found bean providers are
124      * unlocked. If a locked provider is found, the method is aborted, and
125      * <b>false</b> is returned.
126      *
127      * @param dependency the initial dependency to resolve
128      * @param store the starting bean store
129      * @return a flag whether all dependencies can be locked
130      * @throws InjectionException if a dependency cannot be resolved
131      */
132     public boolean initialize(Dependency dependency, BeanStore store)
133     {
134         Map<Dependency, BeanProvider> depMap = new HashMap<Dependency, BeanProvider>();
135         Queue<Dependency> q = new LinkedList<Dependency>();
136         q.add(dependency);
137 
138         while (!q.isEmpty())
139         {
140             Dependency d = q.remove();
141             if (!depMap.containsKey(d))
142             {
143                 // not yet processed => resolve this dependency
144                 BeanProvider provider = d.resolve(store, this);
145                 if (provider.getLockID() != null)
146                 {
147                     // already locked, initialization fails
148                     return false;
149                 }
150                 depMap.put(d, provider);
151 
152                 // Process the dependencies of this provider
153                 Set<Dependency> dependencies = provider.getDependencies();
154                 if (dependencies != null)
155                 {
156                     q.addAll(dependencies);
157                 }
158             }
159         }
160 
161         dependencyMap = depMap;
162         initInvocationHelper(store);
163         return true;
164     }
165 
166     /**
167      * Returns the associated bean context implementation.
168      *
169      * @return the bean context
170      */
171     public DefaultBeanContext getBeanContext()
172     {
173         return beanContext;
174     }
175 
176     /**
177      * Marks all resolved dependencies as locked. This method can be called
178      * after a successful invocation of <code>initialize()</code>. It sets the
179      * lock IDs for all involved <code>BeanProvider</code>s, so that they cannot
180      * take part in another concurrent transaction. A second call to this method
181      * with the argument <b>null</b> releases all locks.
182      *
183      * @param lockID the lock ID
184      */
185     public void lock(Long lockID)
186     {
187         for (BeanProvider p : getDependencyMap().values())
188         {
189             p.setLockID(lockID);
190         }
191     }
192 
193     /**
194      * Returns the bean provider specified by the given dependency. This
195      * dependency must belong to the set of dependencies fetched during the
196      * <code>initialize()</code> method.
197      *
198      * @param dependency the dependency
199      * @return the bean provider specified by this dependency
200      * @throws InjectionException if the dependency cannot be resolved
201      */
202     public BeanProvider getDependentProvider(Dependency dependency)
203     {
204         BeanProvider result = dependencyMap.get(dependency);
205         if (result == null)
206         {
207             throw new InjectionException(
208                     "Invalid dependency! This dependency does not belong to "
209                             + "the current transaction: " + dependency);
210         }
211         return result;
212     }
213 
214     /**
215      * Returns the bean of the bean provider specified by the given dependency.
216      * This dependency must belong to the set of dependencies fetched during the
217      * <code>initialize()</code> method.
218      *
219      * @param dependency the dependency
220      * @return the bean from the bean provider specified by this dependency
221      * @throws InjectionException if the dependency cannot be resolved
222      */
223     public Object getDependentBean(Dependency dependency)
224     {
225         return getDependentProvider(dependency).getBean(this);
226     }
227 
228     /**
229      * Returns a set with the names of all registered class loaders. This
230      * implementation delegates to the internal <code>ClassLoaderProvider</code>
231      * .
232      *
233      * @return a set with the names of the registered class loaders
234      */
235     public Set<String> classLoaderNames()
236     {
237         return getCLP().classLoaderNames();
238     }
239 
240     /**
241      * Returns the class loader with the given symbolic name. This
242      * implementation delegates to the internal <code>ClassLoaderProvider</code>
243      * .
244      *
245      * @param name the name of the class loader
246      * @return the class loader with this name
247      * @throws InjectionException if the class loader cannot be resolved
248      */
249     public ClassLoader getClassLoader(String name)
250     {
251         return getCLP().getClassLoader(name);
252     }
253 
254     /**
255      * Returns the default class loader name. This implementation delegates to
256      * the internal <code>ClassLoaderProvider</code>.
257      *
258      * @return the default class loader name
259      */
260     public String getDefaultClassLoaderName()
261     {
262         return getCLP().getDefaultClassLoaderName();
263     }
264 
265     /**
266      * Registers a class loader under a symbolic name. This implementation
267      * delegates to the internal <code>ClassLoaderProvider</code>.
268      *
269      * @param name the symbolic name for the class loader
270      * @param loader the class loader to register
271      */
272     public void registerClassLoader(String name, ClassLoader loader)
273     {
274         getCLP().registerClassLoader(name, loader);
275     }
276 
277     /**
278      * Sets the name of the default class loader. This implementation delegates
279      * to the internal <code>ClassLoaderProvider</code>.
280      *
281      * @param loaderName the new default class loader name
282      */
283     public void setDefaultClassLoaderName(String loaderName)
284     {
285         getCLP().setDefaultClassLoaderName(loaderName);
286     }
287 
288     /**
289      * Loads the class with the specified name using the class loader identified
290      * by the given symbolic reference. This implementation delegates to the
291      * internal <code>ClassLoaderProvider</code>.
292      *
293      * @param name the class of the name to be loaded
294      * @param loaderRef determines the class loader to be used
295      * @return the loaded class
296      * @throws InjectionException if the class cannot be loaded
297      */
298     public Class<?> loadClass(String name, String loaderRef)
299     {
300         return getCLP().loadClass(name, loaderRef);
301     }
302 
303     /**
304      * Adds a bean initializer. This initializer will be invoked by the
305      * <code>invokeInitializers()</code> method.
306      *
307      * @param initializer the initializer to be added (may be <b>null</b>, then
308      *        this operation has no effect)
309      */
310     public void addInitializer(BeanInitializer initializer)
311     {
312         if (initializer != null)
313         {
314             if (initializers == null)
315             {
316                 initializers = new LinkedList<BeanInitializer>();
317             }
318             initializers.add(initializer);
319         }
320     }
321 
322     /**
323      * Invokes all registered bean initializers. If an initializer throws an
324      * exception, it is caught and saved. The remaining initializers will be
325      * invoked. After that the exception is re-thrown.
326      *
327      * @throws InjectionException if an error occurs
328      */
329     public void invokeInitializers()
330     {
331         if (initializers != null)
332         {
333             InjectionException thrownEx = null;
334 
335             for (BeanInitializer init : initializers)
336             {
337                 try
338                 {
339                     init.initialize(this);
340                 }
341                 catch (InjectionException iex)
342                 {
343                     if (thrownEx == null)
344                     {
345                         thrownEx = iex;
346                     }
347                 }
348                 catch (Exception ex)
349                 {
350                     if (thrownEx == null)
351                     {
352                         thrownEx = new InjectionException(ex);
353                     }
354                 }
355             }
356 
357             if (thrownEx != null)
358             {
359                 throw thrownEx;
360             }
361         }
362     }
363 
364     /**
365      * Tests whether a bean is available.
366      *
367      * @param dependency the dependency
368      * @return a flag whether this dependency can be currently resolved
369      */
370     public boolean isBeanAvailable(Dependency dependency)
371     {
372         return getDependentProvider(dependency).isBeanAvailable();
373     }
374 
375     /**
376      * Returns the {@code InvocationHelper} object. This implementation obtains
377      * the helper object from the associated bean context.
378      *
379      * @return the {@code InvocationHelper} object
380      */
381     public InvocationHelper getInvocationHelper()
382     {
383         return invocationHelper;
384     }
385 
386     /**
387      * Notifies this dependency provider about the creation of a new bean. This
388      * method is called by a {@code BeanProvider} when it has to create a bean
389      * to satisfy the current request.
390      *
391      * @param bean the new bean
392      * @param provider the {@code BeanProvider} responsible for the creation
393      */
394     public void beanCreated(Object bean, BeanProvider provider)
395     {
396         getBeanContext().beanCreated(bean, provider, this);
397     }
398 
399     /**
400      * Returns a map with the resolved dependencies.
401      *
402      * @return the internal dependency map
403      */
404     Map<Dependency, BeanProvider> getDependencyMap()
405     {
406         return dependencyMap;
407     }
408 
409     /**
410      * Convenience method for obtaining the class loader provider from the
411      * associated bean context.
412      *
413      * @return the class loader provider
414      */
415     private ClassLoaderProvider getCLP()
416     {
417         return getBeanContext().getClassLoaderProvider();
418     }
419 
420     /**
421      * Initializes the internal {@link InvocationHelper}. This method is called
422      * after a successful initialization.
423      *
424      * @param store the current bean store
425      */
426     private void initInvocationHelper(BeanStore store)
427     {
428         invocationHelper =
429                 new InvocationHelper(DefaultBeanStore.fetchConversionHelper(
430                         store, true));
431     }
432 }