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