1 /**
2 * Copyright (C) 2006 Google Inc.
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
17 package com.opensymphony.xwork2.inject;
18
19 import com.opensymphony.xwork2.inject.util.ReferenceCache;
20
21 import java.lang.annotation.Annotation;
22 import java.lang.reflect.AccessibleObject;
23 import java.lang.reflect.AnnotatedElement;
24 import java.lang.reflect.Constructor;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Member;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.Map.Entry;
40 import java.io.Serializable;
41
42 /**
43 * Default {@link Container} implementation.
44 *
45 * @see ContainerBuilder
46 * @author crazybob@google.com (Bob Lee)
47 */
48 class ContainerImpl implements Container {
49
50 final Map<Key<?>, InternalFactory<?>> factories;
51 final Map<Class<?>,Set<String>> factoryNamesByType;
52
53 ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {
54 this.factories = factories;
55 Map<Class<?>,Set<String>> map = new HashMap<Class<?>,Set<String>>();
56 for (Key<?> key : factories.keySet()) {
57 Set<String> names = map.get(key.getType());
58 if (names == null) {
59 names = new HashSet<String>();
60 map.put(key.getType(), names);
61 }
62 names.add(key.getName());
63 }
64
65 for (Entry<Class<?>,Set<String>> entry : map.entrySet()) {
66 entry.setValue(Collections.unmodifiableSet(entry.getValue()));
67 }
68
69 this.factoryNamesByType = Collections.unmodifiableMap(map);
70 }
71
72 @SuppressWarnings("unchecked")
73 <T> InternalFactory<? extends T> getFactory(Key<T> key) {
74 return (InternalFactory<T>) factories.get(key);
75 }
76
77 /**
78 * Field and method injectors.
79 */
80 final Map<Class<?>, List<Injector>> injectors =
81 new ReferenceCache<Class<?>, List<Injector>>() {
82 protected List<Injector> create(Class<?> key) {
83 List<Injector> injectors = new ArrayList<Injector>();
84 addInjectors(key, injectors);
85 return injectors;
86 }
87 };
88
89 /**
90 * Recursively adds injectors for fields and methods from the given class to
91 * the given list. Injects parent classes before sub classes.
92 */
93 void addInjectors(Class clazz, List<Injector> injectors) {
94 if (clazz == Object.class) {
95 return;
96 }
97
98 // Add injectors for superclass first.
99 addInjectors(clazz.getSuperclass(), injectors);
100
101 // TODO (crazybob): Filter out overridden members.
102 addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
103 addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
104 }
105
106 void injectStatics(List<Class<?>> staticInjections) {
107 final List<Injector> injectors = new ArrayList<Injector>();
108
109 for (Class<?> clazz : staticInjections) {
110 addInjectorsForFields(clazz.getDeclaredFields(), true, injectors);
111 addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors);
112 }
113
114 callInContext(new ContextualCallable<Void>() {
115 public Void call(InternalContext context) {
116 for (Injector injector : injectors) {
117 injector.inject(context, null);
118 }
119 return null;
120 }
121 });
122 }
123
124 void addInjectorsForMethods(Method[] methods, boolean statics,
125 List<Injector> injectors) {
126 addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
127 new InjectorFactory<Method>() {
128 public Injector create(ContainerImpl container, Method method,
129 String name) throws MissingDependencyException {
130 return new MethodInjector(container, method, name);
131 }
132 });
133 }
134
135 void addInjectorsForFields(Field[] fields, boolean statics,
136 List<Injector> injectors) {
137 addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
138 new InjectorFactory<Field>() {
139 public Injector create(ContainerImpl container, Field field,
140 String name) throws MissingDependencyException {
141 return new FieldInjector(container, field, name);
142 }
143 });
144 }
145
146 <M extends Member & AnnotatedElement> void addInjectorsForMembers(
147 List<M> members, boolean statics, List<Injector> injectors,
148 InjectorFactory<M> injectorFactory) {
149 for (M member : members) {
150 if (isStatic(member) == statics) {
151 Inject inject = member.getAnnotation(Inject.class);
152 if (inject != null) {
153 try {
154 injectors.add(injectorFactory.create(this, member, inject.value()));
155 } catch (MissingDependencyException e) {
156 if (inject.required()) {
157 throw new DependencyException(e);
158 }
159 }
160 }
161 }
162 }
163 }
164
165 interface InjectorFactory<M extends Member & AnnotatedElement> {
166 Injector create(ContainerImpl container, M member, String name)
167 throws MissingDependencyException;
168 }
169
170 private boolean isStatic(Member member) {
171 return Modifier.isStatic(member.getModifiers());
172 }
173
174 static class FieldInjector implements Injector {
175
176 final Field field;
177 final InternalFactory<?> factory;
178 final ExternalContext<?> externalContext;
179
180 public FieldInjector(ContainerImpl container, Field field, String name)
181 throws MissingDependencyException {
182 this.field = field;
183 field.setAccessible(true);
184
185 Key<?> key = Key.newInstance(field.getType(), name);
186 factory = container.getFactory(key);
187 if (factory == null) {
188 throw new MissingDependencyException(
189 "No mapping found for dependency " + key + " in " + field + ".");
190 }
191
192 this.externalContext = ExternalContext.newInstance(field, key, container);
193 }
194
195 public void inject(InternalContext context, Object o) {
196 ExternalContext<?> previous = context.getExternalContext();
197 context.setExternalContext(externalContext);
198 try {
199 field.set(o, factory.create(context));
200 } catch (IllegalAccessException e) {
201 throw new AssertionError(e);
202 } finally {
203 context.setExternalContext(previous);
204 }
205 }
206 }
207
208 /**
209 * Gets parameter injectors.
210 *
211 * @param member to which the parameters belong
212 * @param annotations on the parameters
213 * @param parameterTypes parameter types
214 * @return injections
215 */
216 <M extends AccessibleObject & Member> ParameterInjector<?>[]
217 getParametersInjectors(M member,
218 Annotation[][] annotations, Class[] parameterTypes, String defaultName)
219 throws MissingDependencyException {
220 List<ParameterInjector<?>> parameterInjectors =
221 new ArrayList<ParameterInjector<?>>();
222
223 Iterator<Annotation[]> annotationsIterator =
224 Arrays.asList(annotations).iterator();
225 for (Class<?> parameterType : parameterTypes) {
226 Inject annotation = findInject(annotationsIterator.next());
227 String name = annotation == null ? defaultName : annotation.value();
228 Key<?> key = Key.newInstance(parameterType, name);
229 parameterInjectors.add(createParameterInjector(key, member));
230 }
231
232 return toArray(parameterInjectors);
233 }
234
235 <T> ParameterInjector<T> createParameterInjector(
236 Key<T> key, Member member) throws MissingDependencyException {
237 InternalFactory<? extends T> factory = getFactory(key);
238 if (factory == null) {
239 throw new MissingDependencyException(
240 "No mapping found for dependency " + key + " in " + member + ".");
241 }
242
243 ExternalContext<T> externalContext =
244 ExternalContext.newInstance(member, key, this);
245 return new ParameterInjector<T>(externalContext, factory);
246 }
247
248 @SuppressWarnings("unchecked")
249 private ParameterInjector<?>[] toArray(
250 List<ParameterInjector<?>> parameterInjections) {
251 return parameterInjections.toArray(
252 new ParameterInjector[parameterInjections.size()]);
253 }
254
255 /**
256 * Finds the {@link Inject} annotation in an array of annotations.
257 */
258 Inject findInject(Annotation[] annotations) {
259 for (Annotation annotation : annotations) {
260 if (annotation.annotationType() == Inject.class) {
261 return Inject.class.cast(annotation);
262 }
263 }
264 return null;
265 }
266
267 static class MethodInjector implements Injector {
268
269 final Method method;
270 final ParameterInjector<?>[] parameterInjectors;
271
272 public MethodInjector(ContainerImpl container, Method method, String name)
273 throws MissingDependencyException {
274 this.method = method;
275 method.setAccessible(true);
276
277 Class<?>[] parameterTypes = method.getParameterTypes();
278 if (parameterTypes.length == 0) {
279 throw new DependencyException(
280 method + " has no parameters to inject.");
281 }
282 parameterInjectors = container.getParametersInjectors(
283 method, method.getParameterAnnotations(), parameterTypes, name);
284 }
285
286 public void inject(InternalContext context, Object o) {
287 try {
288 method.invoke(o, getParameters(method, context, parameterInjectors));
289 } catch (Exception e) {
290 throw new RuntimeException(e);
291 }
292 }
293 }
294
295 Map<Class<?>, ConstructorInjector> constructors =
296 new ReferenceCache<Class<?>, ConstructorInjector>() {
297 @SuppressWarnings("unchecked")
298 protected ConstructorInjector<?> create(Class<?> implementation) {
299 return new ConstructorInjector(ContainerImpl.this, implementation);
300 }
301 };
302
303 static class ConstructorInjector<T> {
304
305 final Class<T> implementation;
306 final List<Injector> injectors;
307 final Constructor<T> constructor;
308 final ParameterInjector<?>[] parameterInjectors;
309
310 ConstructorInjector(ContainerImpl container, Class<T> implementation) {
311 this.implementation = implementation;
312
313 constructor = findConstructorIn(implementation);
314 constructor.setAccessible(true);
315
316 MissingDependencyException exception = null;
317 Inject inject = null;
318 ParameterInjector<?>[] parameters = null;
319
320 try {
321 inject = constructor.getAnnotation(Inject.class);
322 parameters = constructParameterInjector(inject, container, constructor);
323 } catch (MissingDependencyException e) {
324 exception = e;
325 }
326 parameterInjectors = parameters;
327
328 if ( exception != null) {
329 if ( inject != null && inject.required()) {
330 throw new DependencyException(exception);
331 }
332 }
333 injectors = container.injectors.get(implementation);
334 }
335
336 ParameterInjector<?>[] constructParameterInjector(
337 Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException{
338 return constructor.getParameterTypes().length == 0
339 ? null // default constructor.
340 : container.getParametersInjectors(
341 constructor,
342 constructor.getParameterAnnotations(),
343 constructor.getParameterTypes(),
344 inject.value()
345 );
346 }
347
348 @SuppressWarnings("unchecked")
349 private Constructor<T> findConstructorIn(Class<T> implementation) {
350 Constructor<T> found = null;
351 Constructor<T>[] declaredConstructors = (Constructor<T>[]) implementation
352 .getDeclaredConstructors();
353 for(Constructor<T> constructor : declaredConstructors) {
354 if (constructor.getAnnotation(Inject.class) != null) {
355 if (found != null) {
356 throw new DependencyException("More than one constructor annotated"
357 + " with @Inject found in " + implementation + ".");
358 }
359 found = constructor;
360 }
361 }
362 if (found != null) {
363 return found;
364 }
365
366 // If no annotated constructor is found, look for a no-arg constructor
367 // instead.
368 try {
369 return implementation.getDeclaredConstructor();
370 } catch (NoSuchMethodException e) {
371 throw new DependencyException("Could not find a suitable constructor"
372 + " in " + implementation.getName() + ".");
373 }
374 }
375
376 /**
377 * Construct an instance. Returns {@code Object} instead of {@code T}
378 * because it may return a proxy.
379 */
380 Object construct(InternalContext context, Class<? super T> expectedType) {
381 ConstructionContext<T> constructionContext =
382 context.getConstructionContext(this);
383
384 // We have a circular reference between constructors. Return a proxy.
385 if (constructionContext.isConstructing()) {
386 // TODO (crazybob): if we can't proxy this object, can we proxy the
387 // other object?
388 return constructionContext.createProxy(expectedType);
389 }
390
391 // If we're re-entering this factory while injecting fields or methods,
392 // return the same instance. This prevents infinite loops.
393 T t = constructionContext.getCurrentReference();
394 if (t != null) {
395 return t;
396 }
397
398 try {
399 // First time through...
400 constructionContext.startConstruction();
401 try {
402 Object[] parameters =
403 getParameters(constructor, context, parameterInjectors);
404 t = constructor.newInstance(parameters);
405 constructionContext.setProxyDelegates(t);
406 } finally {
407 constructionContext.finishConstruction();
408 }
409
410 // Store reference. If an injector re-enters this factory, they'll
411 // get the same reference.
412 constructionContext.setCurrentReference(t);
413
414 // Inject fields and methods.
415 for (Injector injector : injectors) {
416 injector.inject(context, t);
417 }
418
419 return t;
420 } catch (InstantiationException e) {
421 throw new RuntimeException(e);
422 } catch (IllegalAccessException e) {
423 throw new RuntimeException(e);
424 } catch (InvocationTargetException e) {
425 throw new RuntimeException(e);
426 } finally {
427 constructionContext.removeCurrentReference();
428 }
429 }
430 }
431
432 static class ParameterInjector<T> {
433
434 final ExternalContext<T> externalContext;
435 final InternalFactory<? extends T> factory;
436
437 public ParameterInjector(ExternalContext<T> externalContext,
438 InternalFactory<? extends T> factory) {
439 this.externalContext = externalContext;
440 this.factory = factory;
441 }
442
443 T inject(Member member, InternalContext context) {
444 ExternalContext<?> previous = context.getExternalContext();
445 context.setExternalContext(externalContext);
446 try {
447 return factory.create(context);
448 } finally {
449 context.setExternalContext(previous);
450 }
451 }
452 }
453
454 private static Object[] getParameters(Member member, InternalContext context,
455 ParameterInjector[] parameterInjectors) {
456 if (parameterInjectors == null) {
457 return null;
458 }
459
460 Object[] parameters = new Object[parameterInjectors.length];
461 for (int i = 0; i < parameters.length; i++) {
462 parameters[i] = parameterInjectors[i].inject(member, context);
463 }
464 return parameters;
465 }
466
467 void inject(Object o, InternalContext context) {
468 List<Injector> injectors = this.injectors.get(o.getClass());
469 for (Injector injector : injectors) {
470 injector.inject(context, o);
471 }
472 }
473
474 <T> T inject(Class<T> implementation, InternalContext context) {
475 try {
476 ConstructorInjector<T> constructor = getConstructor(implementation);
477 return implementation.cast(
478 constructor.construct(context, implementation));
479 } catch (Exception e) {
480 throw new RuntimeException(e);
481 }
482 }
483
484 @SuppressWarnings("unchecked")
485 <T> T getInstance(Class<T> type, String name, InternalContext context) {
486 ExternalContext<?> previous = context.getExternalContext();
487 Key<T> key = Key.newInstance(type, name);
488 context.setExternalContext(ExternalContext.newInstance(null, key, this));
489 try {
490 InternalFactory o = getFactory(key);
491 if (o != null) {
492 return getFactory(key).create(context);
493 } else {
494 return null;
495 }
496 } finally {
497 context.setExternalContext(previous);
498 }
499 }
500
501 <T> T getInstance(Class<T> type, InternalContext context) {
502 return getInstance(type, DEFAULT_NAME, context);
503 }
504
505 public void inject(final Object o) {
506 callInContext(new ContextualCallable<Void>() {
507 public Void call(InternalContext context) {
508 inject(o, context);
509 return null;
510 }
511 });
512 }
513
514 public <T> T inject(final Class<T> implementation) {
515 return callInContext(new ContextualCallable<T>() {
516 public T call(InternalContext context) {
517 return inject(implementation, context);
518 }
519 });
520 }
521
522 public <T> T getInstance(final Class<T> type, final String name) {
523 return callInContext(new ContextualCallable<T>() {
524 public T call(InternalContext context) {
525 return getInstance(type, name, context);
526 }
527 });
528 }
529
530 public <T> T getInstance(final Class<T> type) {
531 return callInContext(new ContextualCallable<T>() {
532 public T call(InternalContext context) {
533 return getInstance(type, context);
534 }
535 });
536 }
537
538 public Set<String> getInstanceNames(final Class<?> type) {
539 return factoryNamesByType.get(type);
540 }
541
542 ThreadLocal<Object[]> localContext =
543 new ThreadLocal<Object[]>() {
544 protected InternalContext[] initialValue() {
545 return new InternalContext[1];
546 }
547 };
548
549 /**
550 * Looks up thread local context. Creates (and removes) a new context if
551 * necessary.
552 */
553 <T> T callInContext(ContextualCallable<T> callable) {
554 InternalContext[] reference = (InternalContext[])localContext.get();
555 if (reference[0] == null) {
556 reference[0] = new InternalContext(this);
557 try {
558 return callable.call(reference[0]);
559 } finally {
560 // Only remove the context if this call created it.
561 reference[0] = null;
562 }
563 } else {
564 // Someone else will clean up this context.
565 return callable.call(reference[0]);
566 }
567 }
568
569 interface ContextualCallable<T> {
570 T call(InternalContext context);
571 }
572
573 /**
574 * Gets a constructor function for a given implementation class.
575 */
576 @SuppressWarnings("unchecked")
577 <T> ConstructorInjector<T> getConstructor(Class<T> implementation) {
578 return constructors.get(implementation);
579 }
580
581 final ThreadLocal<Object> localScopeStrategy =
582 new ThreadLocal<Object>();
583
584 public void setScopeStrategy(Scope.Strategy scopeStrategy) {
585 this.localScopeStrategy.set(scopeStrategy);
586 }
587
588 public void removeScopeStrategy() {
589 this.localScopeStrategy.remove();
590 }
591
592 /**
593 * Injects a field or method in a given object.
594 */
595 interface Injector extends Serializable {
596 void inject(InternalContext context, Object o);
597 }
598
599 static class MissingDependencyException extends Exception {
600
601 MissingDependencyException(String message) {
602 super(message);
603 }
604 }
605 }