org.elasticsearch.common.inject.assistedinject.FactoryProvider2 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) 2008 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.assistedinject;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Binder;
import org.elasticsearch.common.inject.Binding;
import org.elasticsearch.common.inject.ConfigurationException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Injector;
import org.elasticsearch.common.inject.Key;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.inject.Provider;
import org.elasticsearch.common.inject.ProvisionException;
import org.elasticsearch.common.inject.TypeLiteral;
import org.elasticsearch.common.inject.internal.Errors;
import org.elasticsearch.common.inject.internal.ErrorsException;
import org.elasticsearch.common.inject.spi.Message;
import org.elasticsearch.common.inject.util.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkState;
import static org.elasticsearch.common.inject.internal.Annotations.getKey;
/**
* The newer implementation of factory provider. This implementation uses a child injector to
* create values.
*
* @author [email protected] (Jesse Wilson)
* @author [email protected] (Daniel Martin)
*/
public final class FactoryProvider2 implements InvocationHandler, Provider {
/**
* if a factory method parameter isn't annotated, it gets this annotation.
*/
static final Assisted DEFAULT_ANNOTATION = new Assisted() {
@Override
public String value() {
return "";
}
@Override
public Class annotationType() {
return Assisted.class;
}
@Override
public boolean equals(Object o) {
return o instanceof Assisted
&& ((Assisted) o).value().equals("");
}
@Override
public int hashCode() {
return 127 * "value".hashCode() ^ "".hashCode();
}
@Override
public String toString() {
return "@" + Assisted.class.getName() + "(value=)";
}
};
/**
* the produced type, or null if all methods return concrete types
*/
private final Key producedType;
private final ImmutableMap> returnTypesByMethod;
private final ImmutableMap>> paramTypes;
/**
* the hosting injector, or null if we haven't been initialized yet
*/
private Injector injector;
/**
* the factory interface, implemented and provided
*/
private final F factory;
/**
* @param factoryType a Java interface that defines one or more create methods.
* @param producedType a concrete type that is assignable to the return types of all factory
* methods.
*/
FactoryProvider2(TypeLiteral factoryType, Key producedType) {
this.producedType = producedType;
Errors errors = new Errors();
@SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class
Class factoryRawType = (Class) factoryType.getRawType();
try {
ImmutableMap.Builder> returnTypesBuilder = ImmutableMap.builder();
ImmutableMap.Builder>> paramTypesBuilder
= ImmutableMap.builder();
// TODO: also grab methods from superinterfaces
for (Method method : factoryRawType.getMethods()) {
Key returnType = getKey(
factoryType.getReturnType(method), method, method.getAnnotations(), errors);
returnTypesBuilder.put(method, returnType);
List> params = factoryType.getParameterTypes(method);
Annotation[][] paramAnnotations = method.getParameterAnnotations();
int p = 0;
List> keys = new ArrayList<>();
for (TypeLiteral param : params) {
Key paramKey = getKey(param, method, paramAnnotations[p++], errors);
keys.add(assistKey(method, paramKey, errors));
}
paramTypesBuilder.put(method, Collections.unmodifiableList(keys));
}
returnTypesByMethod = returnTypesBuilder.build();
paramTypes = paramTypesBuilder.build();
} catch (ErrorsException e) {
throw new ConfigurationException(e.getErrors().getMessages());
}
factory = factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(),
new Class[]{factoryRawType}, this));
}
@Override
public F get() {
return factory;
}
/**
* Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation.
* This fails if another binding annotation is clobbered in the process. If the key already has
* the {@literal @}Assisted annotation, it is returned as-is to preserve any String value.
*/
private Key assistKey(Method method, Key key, Errors errors) throws ErrorsException {
if (key.getAnnotationType() == null) {
return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION);
} else if (key.getAnnotationType() == Assisted.class) {
return key;
} else {
errors.withSource(method).addMessage(
"Only @Assisted is allowed for factory parameters, but found @%s",
key.getAnnotationType());
throw errors.toException();
}
}
/**
* At injector-creation time, we initialize the invocation handler. At this time we make sure
* all factory methods will be able to build the target types.
*/
@Inject
public void initialize(Injector injector) {
if (this.injector != null) {
throw new ConfigurationException(Collections.singletonList(new Message(FactoryProvider2.class,
"Factories.create() factories may only be used in one Injector!")));
}
this.injector = injector;
for (Method method : returnTypesByMethod.keySet()) {
Object[] args = new Object[method.getParameterTypes().length];
Arrays.fill(args, "dummy object for validating Factories");
getBindingFromNewInjector(method, args); // throws if the binding isn't properly configured
}
}
/**
* Creates a child injector that binds the args, and returns the binding for the method's result.
*/
public Binding getBindingFromNewInjector(final Method method, final Object[] args) {
checkState(injector != null,
"Factories.create() factories cannot be used until they're initialized by Guice.");
final Key returnType = returnTypesByMethod.get(method);
Module assistedModule = new AbstractModule() {
@Override
@SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value
protected void configure() {
Binder binder = binder().withSource(method);
int p = 0;
for (Key paramKey : paramTypes.get(method)) {
// Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter
binder.bind((Key) paramKey).toProvider(Providers.of(args[p++]));
}
if (producedType != null && !returnType.equals(producedType)) {
binder.bind(returnType).to((Key) producedType);
} else {
binder.bind(returnType);
}
}
};
Injector forCreate = injector.createChildInjector(assistedModule);
return forCreate.getBinding(returnType);
}
/**
* When a factory method is invoked, we create a child injector that binds all parameters, then
* use that to get an instance of the return type.
*/
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
Provider provider = getBindingFromNewInjector(method, args).getProvider();
try {
return provider.get();
} catch (ProvisionException e) {
// if this is an exception declared by the factory method, throw it as-is
if (e.getErrorMessages().size() == 1) {
Message onlyError = Iterables.getOnlyElement(e.getErrorMessages());
Throwable cause = onlyError.getCause();
if (cause != null && canRethrow(method, cause)) {
throw cause;
}
}
throw e;
}
}
@Override
public String toString() {
return factory.getClass().getInterfaces()[0].getName()
+ " for " + producedType.getTypeLiteral();
}
@Override
public boolean equals(Object o) {
return o == this || o == factory;
}
/**
* Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping.
*/
static boolean canRethrow(Method invoked, Throwable thrown) {
if (thrown instanceof Error || thrown instanceof RuntimeException) {
return true;
}
for (Class declared : invoked.getExceptionTypes()) {
if (declared.isInstance(thrown)) {
return true;
}
}
return false;
}
}