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 }