org.elasticsearch.common.inject.InjectorImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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 org.elasticsearch.common.inject;
import org.elasticsearch.common.Classes;
import org.elasticsearch.common.inject.internal.Annotations;
import org.elasticsearch.common.inject.internal.BindingImpl;
import org.elasticsearch.common.inject.internal.Errors;
import org.elasticsearch.common.inject.internal.ErrorsException;
import org.elasticsearch.common.inject.internal.InstanceBindingImpl;
import org.elasticsearch.common.inject.internal.InternalContext;
import org.elasticsearch.common.inject.internal.InternalFactory;
import org.elasticsearch.common.inject.internal.LinkedBindingImpl;
import org.elasticsearch.common.inject.internal.LinkedProviderBindingImpl;
import org.elasticsearch.common.inject.internal.MatcherAndConverter;
import org.elasticsearch.common.inject.internal.Scoping;
import org.elasticsearch.common.inject.internal.SourceProvider;
import org.elasticsearch.common.inject.internal.ToStringBuilder;
import org.elasticsearch.common.inject.spi.BindingTargetVisitor;
import org.elasticsearch.common.inject.spi.ConvertedConstantBinding;
import org.elasticsearch.common.inject.spi.Dependency;
import org.elasticsearch.common.inject.spi.ProviderBinding;
import org.elasticsearch.common.inject.spi.ProviderKeyBinding;
import org.elasticsearch.common.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptySet;
import static org.elasticsearch.common.inject.internal.Annotations.findScopeAnnotation;
/**
* Default {@link Injector} implementation.
*
* @author [email protected] (Bob Lee)
* @see InjectorBuilder
*/
class InjectorImpl implements Injector, Lookups {
final State state;
BindingsMultimap bindingsMultimap = new BindingsMultimap();
/**
* Just-in-time binding cache. Guarded by state.lock()
*/
Map, BindingImpl>> jitBindings = new HashMap<>();
Lookups lookups = new DeferredLookups(this);
InjectorImpl(State state) {
this.state = state;
localContext = new ThreadLocal<>();
}
/**
* Indexes bindings by type.
*/
void index() {
for (Binding> binding : state.getExplicitBindingsThisLevel().values()) {
index(binding);
}
}
void index(Binding binding) {
bindingsMultimap.put(binding.getKey().getTypeLiteral(), binding);
}
@Override
public List> findBindingsByType(TypeLiteral type) {
return bindingsMultimap.getAll(type);
}
/**
* Gets a binding implementation. First, it check to see if the parent has a binding. If the
* parent has a binding and the binding is scoped, it will use that binding. Otherwise, this
* checks for an explicit binding. If no explicit binding is found, it looks for a just-in-time
* binding.
*/
public BindingImpl getBindingOrThrow(Key key, Errors errors) throws ErrorsException {
// Check explicit bindings, i.e. bindings created by modules.
BindingImpl binding = state.getExplicitBinding(key);
if (binding != null) {
return binding;
}
// Look for an on-demand binding.
return getJustInTimeBinding(key, errors);
}
/**
* Returns a just-in-time binding for {@code key}, creating it if necessary.
*
* @throws ErrorsException if the binding could not be created.
*/
private BindingImpl getJustInTimeBinding(Key key, Errors errors) throws ErrorsException {
synchronized (state.lock()) {
// first try to find a JIT binding that we've already created
@SuppressWarnings("unchecked") // we only store bindings that match their key
BindingImpl binding = (BindingImpl) jitBindings.get(key);
if (binding != null) {
return binding;
}
return createJustInTimeBindingRecursive(key, errors);
}
}
/**
* Returns true if the key type is Provider (but not a subclass of Provider).
*/
static boolean isProvider(Key> key) {
return key.getTypeLiteral().getRawType().equals(Provider.class);
}
/**
* Returns true if the key type is MembersInjector (but not a subclass of MembersInjector).
*/
static boolean isMembersInjector(Key> key) {
return key.getTypeLiteral().getRawType().equals(MembersInjector.class) && key.hasAnnotationType() == false;
}
private BindingImpl> createMembersInjectorBinding(Key> key, Errors errors)
throws ErrorsException {
Type membersInjectorType = key.getTypeLiteral().getType();
if ((membersInjectorType instanceof ParameterizedType) == false) {
throw errors.cannotInjectRawMembersInjector().toException();
}
@SuppressWarnings("unchecked") // safe because T came from Key>
TypeLiteral instanceType = (TypeLiteral) TypeLiteral.get(
((ParameterizedType) membersInjectorType).getActualTypeArguments()[0]
);
MembersInjector membersInjector = membersInjectorStore.get(instanceType, errors);
InternalFactory> factory = new ConstantFactory<>(Initializables.of(membersInjector));
return new InstanceBindingImpl<>(this, key, SourceProvider.UNKNOWN_SOURCE, factory, emptySet(), membersInjector);
}
/**
* Creates a synthetic binding to {@code Provider}, i.e. a binding to the provider from
* {@code Binding}.
*/
private BindingImpl> createProviderBinding(Key> key, Errors errors) throws ErrorsException {
Type providerType = key.getTypeLiteral().getType();
// If the Provider has no type parameter (raw Provider)...
if ((providerType instanceof ParameterizedType) == false) {
throw errors.cannotInjectRawProvider().toException();
}
Type entryType = ((ParameterizedType) providerType).getActualTypeArguments()[0];
@SuppressWarnings("unchecked") // safe because T came from Key>
Key providedKey = (Key) key.ofType(entryType);
BindingImpl delegate = getBindingOrThrow(providedKey, errors);
return new ProviderBindingImpl<>(this, key, delegate);
}
static class ProviderBindingImpl extends BindingImpl> implements ProviderBinding> {
final BindingImpl providedBinding;
ProviderBindingImpl(InjectorImpl injector, Key> key, Binding providedBinding) {
super(injector, key, providedBinding.getSource(), createInternalFactory(providedBinding), Scoping.UNSCOPED);
this.providedBinding = (BindingImpl) providedBinding;
}
static InternalFactory> createInternalFactory(Binding providedBinding) {
final Provider provider = providedBinding.getProvider();
return (errors, context, dependency) -> provider;
}
@Override
public V acceptTargetVisitor(BindingTargetVisitor super Provider, V> visitor) {
return visitor.visit();
}
@Override
public String toString() {
return new ToStringBuilder(ProviderKeyBinding.class).add("key", getKey())
.add("providedKey", providedBinding.getKey())
.toString();
}
}
/**
* Converts a constant string binding to the required type.
*
* @return the binding if it could be resolved, or null if the binding doesn't exist
* @throws org.elasticsearch.common.inject.internal.ErrorsException
* if there was an error resolving the binding
*/
private BindingImpl convertConstantStringBinding(Key key, Errors errors) throws ErrorsException {
// Find a constant string binding.
BindingImpl stringBinding = state.getExplicitBinding(key.ofStringType());
if (stringBinding == null || stringBinding.isConstant() == false) {
return null;
}
String stringValue = stringBinding.getProvider().get();
Object source = stringBinding.getSource();
// Find a matching type converter.
TypeLiteral type = key.getTypeLiteral();
MatcherAndConverter matchingConverter = state.getConverter(stringValue, type, errors, source);
if (matchingConverter == null) {
// No converter can handle the given type.
return null;
}
// Try to convert the string. A failed conversion results in an error.
try {
@SuppressWarnings("unchecked") // This cast is safe because we double check below.
T converted = (T) matchingConverter.getTypeConverter().convert(stringValue, type);
if (converted == null) {
throw errors.converterReturnedNull(stringValue, source, type, matchingConverter).toException();
}
if (type.getRawType().isInstance(converted) == false) {
throw errors.conversionTypeError(stringValue, source, type, matchingConverter, converted).toException();
}
return new ConvertedConstantBindingImpl<>(this, key, converted, stringBinding);
} catch (RuntimeException e) {
throw errors.conversionError(stringValue, source, type, matchingConverter, e).toException();
}
}
private static class ConvertedConstantBindingImpl extends BindingImpl implements ConvertedConstantBinding {
final T value;
final Provider provider;
final Binding originalBinding;
ConvertedConstantBindingImpl(Injector injector, Key key, T value, Binding originalBinding) {
super(injector, key, originalBinding.getSource(), new ConstantFactory<>(Initializables.of(value)), Scoping.UNSCOPED);
this.value = value;
provider = Providers.of(value);
this.originalBinding = originalBinding;
}
@Override
public Provider getProvider() {
return provider;
}
@Override
public V acceptTargetVisitor(BindingTargetVisitor super T, V> visitor) {
return visitor.visit();
}
@Override
public String toString() {
return new ToStringBuilder(ConvertedConstantBinding.class).add("key", getKey())
.add("sourceKey", originalBinding.getKey())
.add("value", value)
.toString();
}
}
void initializeBinding(BindingImpl binding, Errors errors) throws ErrorsException {
// Put the partially constructed binding in the map a little early. This enables us to handle
// circular dependencies. Example: FooImpl -> BarImpl -> FooImpl.
// Note: We don't need to synchronize on state.lock() during injector creation.
// TODO: for the above example, remove the binding for BarImpl if the binding for FooImpl fails
if (binding instanceof ConstructorBindingImpl>) {
Key key = binding.getKey();
jitBindings.put(key, binding);
boolean successful = false;
try {
((ConstructorBindingImpl>) binding).initialize(this, errors);
successful = true;
} finally {
if (successful == false) {
jitBindings.remove(key);
}
}
}
}
/**
* Creates a binding for an injectable type with the given scope. Looks for a scope on the type if
* none is specified.
*/
BindingImpl createUnitializedBinding(Key key, Scoping scoping, Object source, Errors errors) throws ErrorsException {
Class> rawType = key.getTypeLiteral().getRawType();
// Don't try to inject arrays, or enums.
if (rawType.isArray() || rawType.isEnum()) {
throw errors.missingImplementation(key).toException();
}
// Handle TypeLiteral by binding the inner type
if (rawType == TypeLiteral.class) {
@SuppressWarnings("unchecked") // we have to fudge the inner type as Object
BindingImpl binding = (BindingImpl) createTypeLiteralBinding((Key>) key, errors);
return binding;
}
// Handle @ImplementedBy
ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class);
if (implementedBy != null) {
Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors);
return createImplementedByBinding(key, scoping, implementedBy, errors);
}
// Handle @ProvidedBy.
ProvidedBy providedBy = rawType.getAnnotation(ProvidedBy.class);
if (providedBy != null) {
Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors);
return createProvidedByBinding(key, scoping, providedBy, errors);
}
// We can't inject abstract classes.
// TODO: Method interceptors could actually enable us to implement
// abstract types. Should we remove this restriction?
if (Modifier.isAbstract(rawType.getModifiers())) {
throw errors.missingImplementation(key).toException();
}
// Error: Inner class.
if (Classes.isInnerClass(rawType)) {
throw errors.cannotInjectInnerClass(rawType).toException();
}
if (scoping.isExplicitlyScoped() == false) {
Class extends Annotation> scopeAnnotation = findScopeAnnotation(errors, rawType);
if (scopeAnnotation != null) {
scoping = Scopes.makeInjectable(Scoping.forAnnotation(scopeAnnotation), this, errors.withSource(rawType));
}
}
return ConstructorBindingImpl.create(this, key, source, scoping);
}
/**
* Converts a binding for a {@code Key>} to the value {@code TypeLiteral}. It's
* a bit awkward because we have to pull out the inner type in the type literal.
*/
private BindingImpl> createTypeLiteralBinding(Key> key, Errors errors) throws ErrorsException {
Type typeLiteralType = key.getTypeLiteral().getType();
if ((typeLiteralType instanceof ParameterizedType) == false) {
throw errors.cannotInjectRawTypeLiteral().toException();
}
ParameterizedType parameterizedType = (ParameterizedType) typeLiteralType;
Type innerType = parameterizedType.getActualTypeArguments()[0];
// this is unfortunate. We don't support building TypeLiterals for type variable like 'T'. If
// this proves problematic, we can probably fix TypeLiteral to support type variables
if ((innerType instanceof Class) == false
&& (innerType instanceof GenericArrayType) == false
&& (innerType instanceof ParameterizedType) == false) {
throw errors.cannotInjectTypeLiteralOf(innerType).toException();
}
@SuppressWarnings("unchecked") // by definition, innerType == T, so this is safe
TypeLiteral value = (TypeLiteral) TypeLiteral.get(innerType);
InternalFactory> factory = new ConstantFactory<>(Initializables.of(value));
return new InstanceBindingImpl<>(this, key, SourceProvider.UNKNOWN_SOURCE, factory, emptySet(), value);
}
/**
* Creates a binding for a type annotated with @ProvidedBy.
*/
BindingImpl createProvidedByBinding(Key key, Scoping scoping, ProvidedBy providedBy, Errors errors) throws ErrorsException {
final Class> rawType = key.getTypeLiteral().getRawType();
final Class extends Provider>> providerType = providedBy.value();
// Make sure it's not the same type. TODO: Can we check for deeper loops?
if (providerType == rawType) {
throw errors.recursiveProviderType().toException();
}
// Assume the provider provides an appropriate type. We double check at runtime.
@SuppressWarnings("unchecked")
final Key extends Provider> providerKey = (Key extends Provider>) Key.get(providerType);
final BindingImpl extends Provider>> providerBinding = getBindingOrThrow(providerKey, errors);
InternalFactory internalFactory = (errors1, context, dependency) -> {
errors1 = errors1.withSource(providerKey);
Provider> provider = providerBinding.getInternalFactory().get(errors1, context, dependency);
try {
Object o = provider.get();
if (o != null && rawType.isInstance(o) == false) {
throw errors1.subtypeNotProvided(providerType, rawType).toException();
}
@SuppressWarnings("unchecked") // protected by isInstance() check above
T t = (T) o;
return t;
} catch (RuntimeException e) {
throw errors1.errorInProvider(e).toException();
}
};
return new LinkedProviderBindingImpl<>(
this,
key,
rawType /* source */,
Scopes.scope(this, internalFactory, scoping),
scoping,
providerKey
);
}
/**
* Creates a binding for a type annotated with @ImplementedBy.
*/
BindingImpl createImplementedByBinding(Key key, Scoping scoping, ImplementedBy implementedBy, Errors errors)
throws ErrorsException {
Class> rawType = key.getTypeLiteral().getRawType();
Class> implementationType = implementedBy.value();
// Make sure it's not the same type. TODO: Can we check for deeper cycles?
if (implementationType == rawType) {
throw errors.recursiveImplementationType().toException();
}
// Make sure implementationType extends type.
if (rawType.isAssignableFrom(implementationType) == false) {
throw errors.notASubtype(implementationType, rawType).toException();
}
@SuppressWarnings("unchecked") // After the preceding check, this cast is safe.
Class extends T> subclass = (Class extends T>) implementationType;
// Look up the target binding.
final Key extends T> targetKey = Key.get(subclass);
final BindingImpl extends T> targetBinding = getBindingOrThrow(targetKey, errors);
InternalFactory internalFactory = (errors1, context, dependency) -> targetBinding.getInternalFactory()
.get(errors1.withSource(targetKey), context, dependency);
return new LinkedBindingImpl<>(this, key, rawType /* source */, Scopes.scope(this, internalFactory, scoping), scoping, targetKey);
}
/**
* Attempts to create a just-in-time binding for {@code key} in the root injector, falling back to
* other ancestor injectors until this injector is tried.
*/
private BindingImpl createJustInTimeBindingRecursive(Key key, Errors errors) throws ErrorsException {
if (state.isBlacklisted(key)) {
throw errors.childBindingAlreadySet(key).toException();
}
BindingImpl binding = createJustInTimeBinding(key, errors);
state.parent().blacklist(key);
jitBindings.put(key, binding);
return binding;
}
/**
* Returns a new just-in-time binding created by resolving {@code key}. The strategies used to
* create just-in-time bindings are:
*
* - Internalizing Providers. If the requested binding is for {@code Provider
}, we delegate
* to the binding for {@code T}.
* - Converting constants.
*
- ImplementedBy and ProvidedBy annotations. Only for unannotated keys.
*
- The constructor of the raw type. Only for unannotated keys.
*
*
* @throws org.elasticsearch.common.inject.internal.ErrorsException
* if the binding cannot be created.
*/
BindingImpl createJustInTimeBinding(Key key, Errors errors) throws ErrorsException {
if (state.isBlacklisted(key)) {
throw errors.childBindingAlreadySet(key).toException();
}
// Handle cases where T is a Provider>.
if (isProvider(key)) {
// These casts are safe. We know T extends Provider and that given Key>,
// createProviderBinding() will return BindingImpl>.
@SuppressWarnings("unchecked")
BindingImpl binding = (BindingImpl) createProviderBinding((Key>) key, errors);
return binding;
}
// Handle cases where T is a MembersInjector>
if (isMembersInjector(key)) {
// These casts are safe. T extends MembersInjector and that given Key>,
// createMembersInjectorBinding() will return BindingImpl>.
@SuppressWarnings("unchecked")
BindingImpl binding = (BindingImpl) createMembersInjectorBinding((Key>) key, errors);
return binding;
}
// Try to convert a constant string binding to the requested type.
BindingImpl convertedBinding = convertConstantStringBinding(key, errors);
if (convertedBinding != null) {
return convertedBinding;
}
// If the key has an annotation...
if (key.hasAnnotationType()) {
// Look for a binding without annotation attributes or return null.
if (key.hasAttributes()) {
try {
Errors ignored = new Errors();
return getBindingOrThrow(key.withoutAttributes(), ignored);
} catch (ErrorsException ignored) {
// throw with a more appropriate message below
}
}
throw errors.missingImplementation(key).toException();
}
Object source = key.getTypeLiteral().getRawType();
BindingImpl binding = createUnitializedBinding(key, Scoping.UNSCOPED, source, errors);
initializeBinding(binding, errors);
return binding;
}
InternalFactory extends T> getInternalFactory(Key key, Errors errors) throws ErrorsException {
return getBindingOrThrow(key, errors).getInternalFactory();
}
private static class BindingsMultimap {
final Map, List>> multimap = new HashMap<>();
void put(TypeLiteral type, Binding binding) {
multimap.computeIfAbsent(type, k -> new ArrayList<>()).add(binding);
}
@SuppressWarnings("unchecked")
// safe because we only put matching entries into the map
List> getAll(TypeLiteral type) {
List> bindings = multimap.get(type);
return bindings != null ? Collections.>unmodifiableList((List) multimap.get(type)) : Collections.emptyList();
}
}
/**
* Returns parameter injectors, or {@code null} if there are no parameters.
*/
SingleParameterInjector>[] getParametersInjectors(List> parameters, Errors errors) throws ErrorsException {
if (parameters.isEmpty()) {
return null;
}
int numErrorsBefore = errors.size();
SingleParameterInjector>[] result = new SingleParameterInjector>[parameters.size()];
int i = 0;
for (Dependency> parameter : parameters) {
try {
result[i++] = createParameterInjector(parameter, errors.withSource(parameter));
} catch (ErrorsException rethrownBelow) {
// rethrown below
}
}
errors.throwIfNewErrors(numErrorsBefore);
return result;
}
SingleParameterInjector createParameterInjector(final Dependency dependency, final Errors errors) throws ErrorsException {
InternalFactory extends T> factory = getInternalFactory(dependency.getKey(), errors);
return new SingleParameterInjector<>(dependency, factory);
}
/**
* Invokes a method.
*/
interface MethodInvoker {
Object invoke(Object target, Object... parameters) throws IllegalAccessException, InvocationTargetException;
}
/**
* Cached constructor injectors for each type
*/
ConstructorInjectorStore constructors = new ConstructorInjectorStore(this);
/**
* Cached field and method injectors for each type.
*/
MembersInjectorStore membersInjectorStore;
private Provider getProvider(Class type) {
return getProvider(Key.get(type));
}
Provider getProviderOrThrow(final Key key, Errors errors) throws ErrorsException {
final InternalFactory extends T> factory = getInternalFactory(key, errors);
// ES: optimize for a common case of read only instance getting from the parent...
if (factory instanceof InternalFactory.Instance) {
return () -> {
try {
return factory.get(null, null, null);
} catch (ErrorsException e) {
// ignore
}
// should never happen...
assert false;
return null;
};
}
final Dependency dependency = Dependency.get(key);
return new Provider<>() {
@Override
public T get() {
final Errors errors = new Errors(dependency);
try {
T t = callInContext((ContextualCallable) context -> {
context.setDependency(dependency);
try {
return factory.get(errors, context, dependency);
} finally {
context.setDependency(null);
}
});
errors.throwIfNewErrors(0);
return t;
} catch (ErrorsException e) {
throw new ProvisionException(errors.merge(e.getErrors()).getMessages());
}
}
@Override
public String toString() {
return factory.toString();
}
};
}
@Override
public Provider getProvider(final Key key) {
Errors errors = new Errors(key);
try {
Provider result = getProviderOrThrow(key, errors);
errors.throwIfNewErrors(0);
return result;
} catch (ErrorsException e) {
throw new ConfigurationException(errors.merge(e.getErrors()).getMessages());
}
}
@Override
public T getInstance(Key key) {
return getProvider(key).get();
}
@Override
public T getInstance(Class type) {
return getProvider(type).get();
}
private final ThreadLocal