com.opensymphony.xwork2.inject.ContainerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwork Show documentation
Show all versions of xwork Show documentation
XWork is an command-pattern framework that is used to power WebWork
as well as other applications. XWork provides an Inversion of Control
container, a powerful expression language, data type conversion,
validation, and pluggable configuration.
/**
* Copyright (C) 2006 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.opensymphony.xwork2.inject;
import com.opensymphony.xwork2.inject.util.ReferenceCache;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.io.Serializable;
/**
* Default {@link Container} implementation.
*
* @see ContainerBuilder
* @author [email protected] (Bob Lee)
*/
class ContainerImpl implements Container {
final Map, InternalFactory>> factories;
final Map,Set> factoryNamesByType;
ContainerImpl(Map, InternalFactory>> factories) {
this.factories = factories;
Map,Set> map = new HashMap,Set>();
for (Key> key : factories.keySet()) {
Set names = map.get(key.getType());
if (names == null) {
names = new HashSet();
map.put(key.getType(), names);
}
names.add(key.getName());
}
for (Entry,Set> entry : map.entrySet()) {
entry.setValue(Collections.unmodifiableSet(entry.getValue()));
}
this.factoryNamesByType = Collections.unmodifiableMap(map);
}
@SuppressWarnings("unchecked")
InternalFactory extends T> getFactory(Key key) {
return (InternalFactory) factories.get(key);
}
/**
* Field and method injectors.
*/
final Map, List> injectors =
new ReferenceCache, List>() {
protected List create(Class> key) {
List injectors = new ArrayList();
addInjectors(key, injectors);
return injectors;
}
};
/**
* Recursively adds injectors for fields and methods from the given class to
* the given list. Injects parent classes before sub classes.
*/
void addInjectors(Class clazz, List injectors) {
if (clazz == Object.class) {
return;
}
// Add injectors for superclass first.
addInjectors(clazz.getSuperclass(), injectors);
// TODO (crazybob): Filter out overridden members.
addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
}
void injectStatics(List> staticInjections) {
final List injectors = new ArrayList();
for (Class> clazz : staticInjections) {
addInjectorsForFields(clazz.getDeclaredFields(), true, injectors);
addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors);
}
callInContext(new ContextualCallable() {
public Void call(InternalContext context) {
for (Injector injector : injectors) {
injector.inject(context, null);
}
return null;
}
});
}
void addInjectorsForMethods(Method[] methods, boolean statics,
List injectors) {
addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
new InjectorFactory() {
public Injector create(ContainerImpl container, Method method,
String name) throws MissingDependencyException {
return new MethodInjector(container, method, name);
}
});
}
void addInjectorsForFields(Field[] fields, boolean statics,
List injectors) {
addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
new InjectorFactory() {
public Injector create(ContainerImpl container, Field field,
String name) throws MissingDependencyException {
return new FieldInjector(container, field, name);
}
});
}
void addInjectorsForMembers(
List members, boolean statics, List injectors,
InjectorFactory injectorFactory) {
for (M member : members) {
if (isStatic(member) == statics) {
Inject inject = member.getAnnotation(Inject.class);
if (inject != null) {
try {
injectors.add(injectorFactory.create(this, member, inject.value()));
} catch (MissingDependencyException e) {
if (inject.required()) {
throw new DependencyException(e);
}
}
}
}
}
}
interface InjectorFactory {
Injector create(ContainerImpl container, M member, String name)
throws MissingDependencyException;
}
private boolean isStatic(Member member) {
return Modifier.isStatic(member.getModifiers());
}
static class FieldInjector implements Injector {
final Field field;
final InternalFactory> factory;
final ExternalContext> externalContext;
public FieldInjector(ContainerImpl container, Field field, String name)
throws MissingDependencyException {
this.field = field;
field.setAccessible(true);
Key> key = Key.newInstance(field.getType(), name);
factory = container.getFactory(key);
if (factory == null) {
throw new MissingDependencyException(
"No mapping found for dependency " + key + " in " + field + ".");
}
this.externalContext = ExternalContext.newInstance(field, key, container);
}
public void inject(InternalContext context, Object o) {
ExternalContext> previous = context.getExternalContext();
context.setExternalContext(externalContext);
try {
field.set(o, factory.create(context));
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} finally {
context.setExternalContext(previous);
}
}
}
/**
* Gets parameter injectors.
*
* @param member to which the parameters belong
* @param annotations on the parameters
* @param parameterTypes parameter types
* @return injections
*/
ParameterInjector>[]
getParametersInjectors(M member,
Annotation[][] annotations, Class[] parameterTypes, String defaultName)
throws MissingDependencyException {
List> parameterInjectors =
new ArrayList>();
Iterator annotationsIterator =
Arrays.asList(annotations).iterator();
for (Class> parameterType : parameterTypes) {
Inject annotation = findInject(annotationsIterator.next());
String name = annotation == null ? defaultName : annotation.value();
Key> key = Key.newInstance(parameterType, name);
parameterInjectors.add(createParameterInjector(key, member));
}
return toArray(parameterInjectors);
}
ParameterInjector createParameterInjector(
Key key, Member member) throws MissingDependencyException {
InternalFactory extends T> factory = getFactory(key);
if (factory == null) {
throw new MissingDependencyException(
"No mapping found for dependency " + key + " in " + member + ".");
}
ExternalContext externalContext =
ExternalContext.newInstance(member, key, this);
return new ParameterInjector(externalContext, factory);
}
@SuppressWarnings("unchecked")
private ParameterInjector>[] toArray(
List> parameterInjections) {
return parameterInjections.toArray(
new ParameterInjector[parameterInjections.size()]);
}
/**
* Finds the {@link Inject} annotation in an array of annotations.
*/
Inject findInject(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType() == Inject.class) {
return Inject.class.cast(annotation);
}
}
return null;
}
static class MethodInjector implements Injector {
final Method method;
final ParameterInjector>[] parameterInjectors;
public MethodInjector(ContainerImpl container, Method method, String name)
throws MissingDependencyException {
this.method = method;
method.setAccessible(true);
Class>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0) {
throw new DependencyException(
method + " has no parameters to inject.");
}
parameterInjectors = container.getParametersInjectors(
method, method.getParameterAnnotations(), parameterTypes, name);
}
public void inject(InternalContext context, Object o) {
try {
method.invoke(o, getParameters(method, context, parameterInjectors));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Map, ConstructorInjector> constructors =
new ReferenceCache, ConstructorInjector>() {
@SuppressWarnings("unchecked")
protected ConstructorInjector> create(Class> implementation) {
return new ConstructorInjector(ContainerImpl.this, implementation);
}
};
static class ConstructorInjector {
final Class implementation;
final List injectors;
final Constructor constructor;
final ParameterInjector>[] parameterInjectors;
ConstructorInjector(ContainerImpl container, Class implementation) {
this.implementation = implementation;
constructor = findConstructorIn(implementation);
constructor.setAccessible(true);
try {
Inject inject = constructor.getAnnotation(Inject.class);
parameterInjectors = inject == null
? null // default constructor.
: container.getParametersInjectors(
constructor,
constructor.getParameterAnnotations(),
constructor.getParameterTypes(),
inject.value()
);
} catch (MissingDependencyException e) {
throw new DependencyException(e);
}
injectors = container.injectors.get(implementation);
}
@SuppressWarnings("unchecked")
private Constructor findConstructorIn(Class implementation) {
Constructor found = null;
Constructor[] declaredConstructors = (Constructor[]) implementation
.getDeclaredConstructors();
for(Constructor constructor : declaredConstructors) {
if (constructor.getAnnotation(Inject.class) != null) {
if (found != null) {
throw new DependencyException("More than one constructor annotated"
+ " with @Inject found in " + implementation + ".");
}
found = constructor;
}
}
if (found != null) {
return found;
}
// If no annotated constructor is found, look for a no-arg constructor
// instead.
try {
return implementation.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new DependencyException("Could not find a suitable constructor"
+ " in " + implementation.getName() + ".");
}
}
/**
* Construct an instance. Returns {@code Object} instead of {@code T}
* because it may return a proxy.
*/
Object construct(InternalContext context, Class super T> expectedType) {
ConstructionContext constructionContext =
context.getConstructionContext(this);
// We have a circular reference between constructors. Return a proxy.
if (constructionContext.isConstructing()) {
// TODO (crazybob): if we can't proxy this object, can we proxy the
// other object?
return constructionContext.createProxy(expectedType);
}
// If we're re-entering this factory while injecting fields or methods,
// return the same instance. This prevents infinite loops.
T t = constructionContext.getCurrentReference();
if (t != null) {
return t;
}
try {
// First time through...
constructionContext.startConstruction();
try {
Object[] parameters =
getParameters(constructor, context, parameterInjectors);
t = constructor.newInstance(parameters);
constructionContext.setProxyDelegates(t);
} finally {
constructionContext.finishConstruction();
}
// Store reference. If an injector re-enters this factory, they'll
// get the same reference.
constructionContext.setCurrentReference(t);
// Inject fields and methods.
for (Injector injector : injectors) {
injector.inject(context, t);
}
return t;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} finally {
constructionContext.removeCurrentReference();
}
}
}
static class ParameterInjector {
final ExternalContext externalContext;
final InternalFactory extends T> factory;
public ParameterInjector(ExternalContext externalContext,
InternalFactory extends T> factory) {
this.externalContext = externalContext;
this.factory = factory;
}
T inject(Member member, InternalContext context) {
ExternalContext> previous = context.getExternalContext();
context.setExternalContext(externalContext);
try {
return factory.create(context);
} finally {
context.setExternalContext(previous);
}
}
}
private static Object[] getParameters(Member member, InternalContext context,
ParameterInjector[] parameterInjectors) {
if (parameterInjectors == null) {
return null;
}
Object[] parameters = new Object[parameterInjectors.length];
for (int i = 0; i < parameters.length; i++) {
parameters[i] = parameterInjectors[i].inject(member, context);
}
return parameters;
}
void inject(Object o, InternalContext context) {
List injectors = this.injectors.get(o.getClass());
for (Injector injector : injectors) {
injector.inject(context, o);
}
}
T inject(Class implementation, InternalContext context) {
try {
ConstructorInjector constructor = getConstructor(implementation);
return implementation.cast(
constructor.construct(context, implementation));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
T getInstance(Class type, String name, InternalContext context) {
ExternalContext> previous = context.getExternalContext();
Key key = Key.newInstance(type, name);
context.setExternalContext(ExternalContext.newInstance(null, key, this));
try {
InternalFactory o = getFactory(key);
if (o != null) {
return getFactory(key).create(context);
} else {
return null;
}
} finally {
context.setExternalContext(previous);
}
}
T getInstance(Class type, InternalContext context) {
return getInstance(type, DEFAULT_NAME, context);
}
public void inject(final Object o) {
callInContext(new ContextualCallable() {
public Void call(InternalContext context) {
inject(o, context);
return null;
}
});
}
public T inject(final Class implementation) {
return callInContext(new ContextualCallable() {
public T call(InternalContext context) {
return inject(implementation, context);
}
});
}
public T getInstance(final Class type, final String name) {
return callInContext(new ContextualCallable() {
public T call(InternalContext context) {
return getInstance(type, name, context);
}
});
}
public T getInstance(final Class type) {
return callInContext(new ContextualCallable() {
public T call(InternalContext context) {
return getInstance(type, context);
}
});
}
public Set getInstanceNames(final Class> type) {
return factoryNamesByType.get(type);
}
ThreadLocal localContext =
new ThreadLocal() {
protected InternalContext[] initialValue() {
return new InternalContext[1];
}
};
/**
* Looks up thread local context. Creates (and removes) a new context if
* necessary.
*/
T callInContext(ContextualCallable callable) {
InternalContext[] reference = localContext.get();
if (reference[0] == null) {
reference[0] = new InternalContext(this);
try {
return callable.call(reference[0]);
} finally {
// Only remove the context if this call created it.
reference[0] = null;
}
} else {
// Someone else will clean up this context.
return callable.call(reference[0]);
}
}
interface ContextualCallable {
T call(InternalContext context);
}
/**
* Gets a constructor function for a given implementation class.
*/
@SuppressWarnings("unchecked")
ConstructorInjector getConstructor(Class implementation) {
return constructors.get(implementation);
}
final ThreadLocal localScopeStrategy =
new ThreadLocal();
public void setScopeStrategy(Scope.Strategy scopeStrategy) {
this.localScopeStrategy.set(scopeStrategy);
}
public void removeScopeStrategy() {
this.localScopeStrategy.remove();
}
/**
* Injects a field or method in a given object.
*/
interface Injector extends Serializable {
void inject(InternalContext context, Object o);
}
static class MissingDependencyException extends Exception {
MissingDependencyException(String message) {
super(message);
}
}
}