com.opensymphony.xwork2.inject.ContainerBuilder Maven / Gradle / Ivy
/**
* 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 java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Builds a dependency injection {@link Container}. The combination of
* dependency type and name uniquely identifies a dependency mapping; you can
* use the same name for two different types. Not safe for concurrent use.
*
* Adds the following factories by default:
*
*
* - Injects the current {@link Container}.
*
- Injects the {@link Logger} for the injected member's declaring class.
*
*
* @author [email protected] (Bob Lee)
*/
public final class ContainerBuilder {
final Map, InternalFactory> factories = new HashMap<>();
final List> singletonFactories = new ArrayList<>();
final List> earlyInitializableFactories = new ArrayList<>();
final List> staticInjections = new ArrayList<>();
boolean created;
boolean allowDuplicates = false;
private static final InternalFactory CONTAINER_FACTORY =
new InternalFactory() {
public Container create(InternalContext context) {
return context.getContainer();
}
@Override
public Class type() {
return Container.class;
}
};
private static final InternalFactory LOGGER_FACTORY =
new InternalFactory() {
public Logger create(InternalContext context) {
Member member = context.getExternalContext().getMember();
return member == null ? Logger.getAnonymousLogger()
: Logger.getLogger(member.getDeclaringClass().getName());
}
@Override
public Class type() {
return Logger.class;
}
};
/**
* Constructs a new builder.
*/
public ContainerBuilder() {
// In the current container as the default Container implementation.
factories.put(Key.newInstance(Container.class, Container.DEFAULT_NAME), CONTAINER_FACTORY);
// Inject the logger for the injected member's declaring class.
factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME), LOGGER_FACTORY);
}
/**
* Maps a dependency. All methods in this class ultimately funnel through
* here.
*/
private ContainerBuilder factory(final Key key,
InternalFactory factory, Scope scope) {
ensureNotCreated();
checkKey(key);
final InternalFactory scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);
factories.put(key, scopedFactory);
InternalFactory callableFactory = createCallableFactory(key, scopedFactory);
if (EarlyInitializable.class.isAssignableFrom(factory.type())) {
earlyInitializableFactories.add(callableFactory);
} else if (scope == Scope.SINGLETON) {
singletonFactories.add(callableFactory);
}
return this;
}
private InternalFactory createCallableFactory(final Key key, final InternalFactory scopedFactory) {
return new InternalFactory() {
public T create(InternalContext context) {
try {
context.setExternalContext(ExternalContext.newInstance(null, key, context.getContainerImpl()));
return scopedFactory.create(context);
} finally {
context.setExternalContext(null);
}
}
@Override
public Class type() {
return scopedFactory.type();
}
};
}
/**
* Ensures a key isn't already mapped.
*
* @param key the key to check
*/
private void checkKey(Key key) {
if (factories.containsKey(key) && !allowDuplicates) {
throw new DependencyException("Dependency mapping for " + key + " already exists.");
}
}
/**
* Maps a factory to a given dependency type and name.
*
* @param type
* @param type of dependency
* @param name of dependency
* @param factory creates objects to inject
* @param scope scope of injected instances
* @return this builder
*/
public ContainerBuilder factory(final Class type, final String name,
final Factory factory, Scope scope) {
InternalFactory internalFactory = new InternalFactory() {
public T create(InternalContext context) {
try {
Context externalContext = context.getExternalContext();
return factory.create(externalContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Class type() {
return factory.type();
}
@Override
public String toString() {
return new LinkedHashMap() {{
put("type", type);
put("name", name);
put("factory", factory);
}}.toString();
}
};
return factory(Key.newInstance(type, name), internalFactory, scope);
}
/**
* Convenience method. Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, factory, scope)}.
*
* @param type
* @param type of dependency
* @param factory of dependency
* @param scope scope of injected instances
* @return a container builder
* @see #factory(Class, String, Factory, Scope)
*/
public ContainerBuilder factory(Class type, Factory factory, Scope scope) {
return factory(type, Container.DEFAULT_NAME, factory, scope);
}
/**
* Convenience method. Equivalent to {@code factory(type, name, factory,
* Scope.PROTOTYPE)}.
*
* @param type
* @param type of dependency
* @param name of dependency
* @param factory of dependency
* @return a container builder
* @see #factory(Class, String, Factory, Scope)
*/
public ContainerBuilder factory(Class type, String name, Factory factory) {
return factory(type, name, factory, Scope.PROTOTYPE);
}
/**
* Convenience method. Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, factory, Scope.PROTOTYPE)}.
*
* @param type
* @param type of dependency
* @param factory of dependency
* @return a container builder
* @see #factory(Class, String, Factory, Scope)
*/
public ContainerBuilder factory(Class type, Factory factory) {
return factory(type, Container.DEFAULT_NAME, factory, Scope.PROTOTYPE);
}
/**
* Maps an implementation class to a given dependency type and name. Creates
* instances using the container, recursively injecting dependencies.
*
* @param type
* @param type of dependency
* @param name of dependency
* @param implementation class
* @param scope scope of injected instances
* @return this builder
*/
public ContainerBuilder factory(final Class type, final String name,
final Class implementation, final Scope scope) {
// This factory creates new instances of the given implementation.
// We have to lazy load the constructor because the Container
// hasn't been created yet.
InternalFactory factory = new InternalFactory() {
volatile ContainerImpl.ConstructorInjector constructor;
@SuppressWarnings("unchecked")
public T create(InternalContext context) {
if (constructor == null) {
this.constructor =
context.getContainerImpl().getConstructor(implementation);
}
return (T) constructor.construct(context, type);
}
@Override
public Class type() {
return implementation;
}
@Override
public String toString() {
return new LinkedHashMap() {{
put("type", type);
put("name", name);
put("implementation", implementation);
put("scope", scope);
}}.toString();
}
};
return factory(Key.newInstance(type, name), factory, scope);
}
/**
*
* Maps an implementation class to a given dependency type and name. Creates
* instances using the container, recursively injecting dependencies.
*
*
* Sets scope to value from {@link Scoped} annotation on the
* implementation class. Defaults to {@link Scope#PROTOTYPE} if no annotation
* is found.
*
*
* @param type
* @param type of dependency
* @param name of dependency
* @param implementation class
* @return this builder
*/
public ContainerBuilder factory(final Class type, String name,
final Class implementation) {
Scoped scoped = implementation.getAnnotation(Scoped.class);
Scope scope = scoped == null ? Scope.PROTOTYPE : scoped.value();
return factory(type, name, implementation, scope);
}
/**
* Convenience method. Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, implementation)}.
*
* @param type
* @param type of dependency
* @param implementation class
* @return this builder
* @see #factory(Class, String, Class)
*/
public ContainerBuilder factory(Class type, Class implementation) {
return factory(type, Container.DEFAULT_NAME, implementation);
}
/**
* Convenience method. Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, type)}.
*
* @param type
* @param type of dependency
* @return this builder
* @see #factory(Class, String, Class)
*/
public ContainerBuilder factory(Class type) {
return factory(type, Container.DEFAULT_NAME, type);
}
/**
* Convenience method. Equivalent to {@code factory(type, name, type)}.
*
* @param type
* @param type of dependency
* @param name of dependency
* @return this builder
* @see #factory(Class, String, Class)
*/
public ContainerBuilder factory(Class type, String name) {
return factory(type, name, type);
}
/**
* Convenience method. Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, implementation, scope)}.
*
* @param type
* @param type of dependency
* @param implementation class
* @param scope the scope
* @return this builder
* @see #factory(Class, String, Class, Scope)
*/
public ContainerBuilder factory(Class type, Class implementation, Scope scope) {
return factory(type, Container.DEFAULT_NAME, implementation, scope);
}
/**
* Convenience method. Equivalent to {@code factory(type,
* Container.DEFAULT_NAME, type, scope)}.
*
* @param type
* @param type of dependency
* @param scope the scope
* @return this builder
* @see #factory(Class, String, Class, Scope)
*/
public ContainerBuilder factory(Class type, Scope scope) {
return factory(type, Container.DEFAULT_NAME, type, scope);
}
/**
* Convenience method. Equivalent to {@code factory(type, name, type,
* scope)}.
*
* @param type
* @param type of dependency
* @param name of dependency
* @param scope the scope
* @return this builder
* @see #factory(Class, String, Class, Scope)
*/
public ContainerBuilder factory(Class type, String name, Scope scope) {
return factory(type, name, type, scope);
}
/**
* Convenience method. Equivalent to {@code alias(type, Container.DEFAULT_NAME,
* type)}.
*
* @param type
* @param type of dependency
* @param alias of dependency
* @return this builder
* @see #alias(Class, String, String)
*/
public ContainerBuilder alias(Class type, String alias) {
return alias(type, Container.DEFAULT_NAME, alias);
}
/**
* Maps an existing factory to a new name.
*
* @param type
* @param type of dependency
* @param name of dependency
* @param alias of to the dependency
* @return this builder
*/
public ContainerBuilder alias(Class type, String name, String alias) {
return alias(Key.newInstance(type, name), Key.newInstance(type, alias));
}
/**
* Maps an existing dependency. All methods in this class ultimately funnel through
* here.
* @param type of key and alias
* @param key name of key
* @param aliasKey name of alias key
* @return this builder
*/
private ContainerBuilder alias(final Key key,
final Key aliasKey) {
ensureNotCreated();
checkKey(aliasKey);
final InternalFactory scopedFactory = (InternalFactory) factories.get(key);
if (scopedFactory == null) {
throw new DependencyException("Dependency mapping for " + key + " doesn't exists.");
}
factories.put(aliasKey, scopedFactory);
return this;
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, String value) {
return constant(String.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, int value) {
return constant(int.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, long value) {
return constant(long.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, boolean value) {
return constant(boolean.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value constant value
* @return this builder
*/
public ContainerBuilder constant(String name, double value) {
return constant(double.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, float value) {
return constant(float.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, short value) {
return constant(short.class, name, value);
}
/**
* Maps a constant value to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, char value) {
return constant(char.class, name, value);
}
/**
* Maps a class to the given name.
* @param name name
* @param value value
* @return this builder
*/
public ContainerBuilder constant(String name, Class value) {
return constant(Class.class, name, value);
}
/**
* Maps an enum to the given name.
* @param name name
* @param value type
* @param value value
* @return this builder
*/
public > ContainerBuilder constant(String name, E value) {
return constant(value.getDeclaringClass(), name, value);
}
/**
* Maps a constant value to the given type and name.
* @param type type of class
* @param name name
* @param value value
* @return this builder
*/
private ContainerBuilder constant(final Class type, final String name, final T value) {
InternalFactory factory = new InternalFactory() {
public T create(InternalContext ignored) {
return value;
}
@Override
public Class type() {
return (Class) value.getClass();
}
@Override
public String toString() {
return new LinkedHashMap() {
{
put("type", type);
put("name", name);
put("value", value);
}
}.toString();
}
};
return factory(Key.newInstance(type, name), factory, Scope.PROTOTYPE);
}
/**
* Upon creation, the {@link Container} will inject static fields and methods
* into the given classes.
*
* @param types for which static members will be injected
* @return this builder
*/
public ContainerBuilder injectStatics(Class... types) {
staticInjections.addAll(Arrays.asList(types));
return this;
}
/**
* @param type type of class
* @param name name of class
* @return true if this builder contains a mapping for the given type and
* name.
*/
public boolean contains(Class type, String name) {
return factories.containsKey(Key.newInstance(type, name));
}
/**
* Convenience method. Equivalent to {@code contains(type,
* Container.DEFAULT_NAME)}.
* @param type type of class
* @return true if this builder contains a mapping for the given type.
*/
public boolean contains(Class type) {
return contains(type, Container.DEFAULT_NAME);
}
/**
* Creates a {@link Container} instance. Injects static members for classes
* which were registered using {@link #injectStatics(Class...)}.
*
* @param loadSingletons If true, the container will load all singletons
* now. If false, the container will lazily load singletons. Eager loading
* is appropriate for production use while lazy loading can speed
* development.
* @return this builder
* @throws IllegalStateException if called more than once
*/
public Container create(boolean loadSingletons) {
ensureNotCreated();
created = true;
final ContainerImpl container = new ContainerImpl(new HashMap<>(factories));
if (loadSingletons) {
container.callInContext(new ContainerImpl.ContextualCallable() {
public Void call(InternalContext context) {
for (InternalFactory factory : singletonFactories) {
factory.create(context);
}
return null;
}
});
}
container.callInContext(new ContainerImpl.ContextualCallable() {
public Void call(InternalContext context) {
for (InternalFactory factory : earlyInitializableFactories) {
factory.create(context);
}
return null;
}
});
container.injectStatics(staticInjections);
return container;
}
/**
* Currently we only support creating one Container instance per builder.
* If we want to support creating more than one container per builder,
* we should move to a "factory factory" model where we create a factory
* instance per Container. Right now, one factory instance would be
* shared across all the containers, singletons synchronize on the
* container when lazy loading, etc.
*/
private void ensureNotCreated() {
if (created) {
throw new IllegalStateException("Container already created.");
}
}
public void setAllowDuplicates(boolean val) {
allowDuplicates = val;
}
/**
* Implemented by classes which participate in building a container.
*/
public interface Command {
/**
* Contributes factories to the given builder.
*
* @param builder the container builder
*/
void build(ContainerBuilder builder);
}
}