All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.common.inject.assistedinject.FactoryProvider Maven / Gradle / Ivy

There is a newer version: 8.14.1
Show newest version
/*
 * Copyright (C) 2007 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.ImmutableSet;
import com.google.common.collect.Maps;
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.Provider;
import org.elasticsearch.common.inject.TypeLiteral;
import org.elasticsearch.common.inject.internal.Errors;
import org.elasticsearch.common.inject.spi.Dependency;
import org.elasticsearch.common.inject.spi.HasDependencies;
import org.elasticsearch.common.inject.spi.Message;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Provides a factory that combines the caller's arguments with injector-supplied values to
 * construct objects.
 * 

Defining a factory

* Create an interface whose methods return the constructed type, or any of its supertypes. The * method's parameters are the arguments required to build the constructed type. *
public interface PaymentFactory {
 *   Payment create(Date startDate, Money amount);
 * }
* You can name your factory methods whatever you like, such as create, createPayment * or newPayment. *

Creating a type that accepts factory parameters

* {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated * constructor. In addition to injector-supplied parameters, the constructor should have * parameters that match each of the factory method's parameters. Each factory-supplied parameter * requires an {@literal @}{@link Assisted} annotation. This serves to document that the parameter * is not bound by your application's modules. *
public class RealPayment implements Payment {
 *   {@literal @}Inject
 *   public RealPayment(
 *      CreditService creditService,
 *      AuthService authService,
 *      {@literal @}Assisted Date startDate,
 *      {@literal @}Assisted Money amount) {
 *     ...
 *   }
 * }
* Any parameter that permits a null value should also be annotated {@code @Nullable}. *

Configuring factories

* In your {@link org.elasticsearch.common.inject.Module module}, bind the factory interface to the returned * factory: *
bind(PaymentFactory.class).toProvider(
 *     FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));
* As a side-effect of this binding, Guice will inject the factory to initialize it for use. The * factory cannot be used until the injector has been initialized. *

Using the factory

* Inject your factory into your application classes. When you use the factory, your arguments * will be combined with values from the injector to construct an instance. *
public class PaymentAction {
 *   {@literal @}Inject private PaymentFactory paymentFactory;
 *   public void doPayment(Money amount) {
 *     Payment payment = paymentFactory.create(new Date(), amount);
 *     payment.apply();
 *   }
 * }
*

Making parameter types distinct

* The types of the factory method's parameters must be distinct. To use multiple parameters of * the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the * parameters. The names must be applied to the factory method's parameters: *
public interface PaymentFactory {
 *   Payment create(
 *       {@literal @}Assisted("startDate") Date startDate,
 *       {@literal @}Assisted("dueDate") Date dueDate,
 *       Money amount);
 * } 
* ...and to the concrete type's constructor parameters: *
public class RealPayment implements Payment {
 *   {@literal @}Inject
 *   public RealPayment(
 *      CreditService creditService,
 *      AuthService authService,
 *      {@literal @}Assisted("startDate") Date startDate,
 *      {@literal @}Assisted("dueDate") Date dueDate,
 *      {@literal @}Assisted Money amount) {
 *     ...
 *   }
 * }
*

Values are created by Guice

* Returned factories use child injectors to create values. The values are eligible for method * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are * returned. *

Backwards compatibility using {@literal @}AssistedInject

* Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatibility mode. *

Instead of matching factory method arguments to constructor parameters using their names, the * parameters are matched by their order. The first factory method argument is * used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no * effect. *

Returned values are not created by Guice. These types are not eligible for * method interception. They do receive post-construction member injection. * * @param The factory interface * @author [email protected] (Jerome Mourits) * @author [email protected] (Jesse Wilson) * @author [email protected] (Daniel Martin) */ public class FactoryProvider implements Provider, HasDependencies { /* * This class implements the old @AssistedInject implementation that manually matches constructors * to factory methods. The new child injector implementation lives in FactoryProvider2. */ private Injector injector; private final TypeLiteral factoryType; private final Map> factoryMethodToConstructor; public static Provider newFactory( Class factoryType, Class implementationType) { return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); } public static Provider newFactory( TypeLiteral factoryType, TypeLiteral implementationType) { Map> factoryMethodToConstructor = createMethodMapping(factoryType, implementationType); if (!factoryMethodToConstructor.isEmpty()) { return new FactoryProvider<>(factoryType, factoryMethodToConstructor); } else { return new FactoryProvider2<>(factoryType, Key.get(implementationType)); } } private FactoryProvider(TypeLiteral factoryType, Map> factoryMethodToConstructor) { this.factoryType = factoryType; this.factoryMethodToConstructor = factoryMethodToConstructor; checkDeclaredExceptionsMatch(); } @Inject void setInjectorAndCheckUnboundParametersAreInjectable(Injector injector) { this.injector = injector; for (AssistedConstructor c : factoryMethodToConstructor.values()) { for (Parameter p : c.getAllParameters()) { if (!p.isProvidedByFactory() && !paramCanBeInjected(p, injector)) { // this is lame - we're not using the proper mechanism to add an // error to the injector. Throughout this class we throw exceptions // to add errors, which isn't really the best way in Guice throw newConfigurationException("Parameter of type '%s' is not injectable or annotated " + "with @Assisted for Constructor '%s'", p, c); } } } } private void checkDeclaredExceptionsMatch() { for (Map.Entry> entry : factoryMethodToConstructor.entrySet()) { for (Class constructorException : entry.getValue().getDeclaredExceptions()) { if (!isConstructorExceptionCompatibleWithFactoryExeception( constructorException, entry.getKey().getExceptionTypes())) { throw newConfigurationException("Constructor %s declares an exception, but no compatible " + "exception is thrown by the factory method %s", entry.getValue(), entry.getKey()); } } } } private boolean isConstructorExceptionCompatibleWithFactoryExeception( Class constructorException, Class[] factoryExceptions) { for (Class factoryException : factoryExceptions) { if (factoryException.isAssignableFrom(constructorException)) { return true; } } return false; } private boolean paramCanBeInjected(Parameter parameter, Injector injector) { return parameter.isBound(injector); } private static Map> createMethodMapping( TypeLiteral factoryType, TypeLiteral implementationType) { List> constructors = new ArrayList<>(); for (Constructor constructor : implementationType.getRawType().getDeclaredConstructors()) { if (constructor.getAnnotation(AssistedInject.class) != null) { @SuppressWarnings("unchecked") // the constructor type and implementation type agree AssistedConstructor assistedConstructor = new AssistedConstructor( constructor, implementationType.getParameterTypes(constructor)); constructors.add(assistedConstructor); } } if (constructors.isEmpty()) { return ImmutableMap.of(); } Method[] factoryMethods = factoryType.getRawType().getMethods(); if (constructors.size() != factoryMethods.length) { throw newConfigurationException("Constructor mismatch: %s has %s @AssistedInject " + "constructors, factory %s has %s creation methods", implementationType, constructors.size(), factoryType, factoryMethods.length); } Map paramsToConstructor = Maps.newHashMap(); for (AssistedConstructor c : constructors) { if (paramsToConstructor.containsKey(c.getAssistedParameters())) { throw new RuntimeException("Duplicate constructor, " + c); } paramsToConstructor.put(c.getAssistedParameters(), c); } Map> result = Maps.newHashMap(); for (Method method : factoryMethods) { if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) { throw newConfigurationException("Return type of method %s is not assignable from %s", method, implementationType); } List parameterTypes = new ArrayList<>(); for (TypeLiteral parameterType : factoryType.getParameterTypes(method)) { parameterTypes.add(parameterType.getType()); } ParameterListKey methodParams = new ParameterListKey(parameterTypes); if (!paramsToConstructor.containsKey(methodParams)) { throw newConfigurationException("%s has no @AssistInject constructor that takes the " + "@Assisted parameters %s in that order. @AssistInject constructors are %s", implementationType, methodParams, paramsToConstructor.values()); } method.getParameterAnnotations(); for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { for (Annotation parameterAnnotation : parameterAnnotations) { if (parameterAnnotation.annotationType() == Assisted.class) { throw newConfigurationException("Factory method %s has an @Assisted parameter, which " + "is incompatible with the deprecated @AssistedInject annotation. Please replace " + "@AssistedInject with @Inject on the %s constructor.", method, implementationType); } } } AssistedConstructor matchingConstructor = paramsToConstructor.remove(methodParams); result.put(method, matchingConstructor); } return result; } @Override public Set> getDependencies() { List> dependencies = new ArrayList<>(); for (AssistedConstructor constructor : factoryMethodToConstructor.values()) { for (Parameter parameter : constructor.getAllParameters()) { if (!parameter.isProvidedByFactory()) { dependencies.add(Dependency.get(parameter.getPrimaryBindingKey())); } } } return ImmutableSet.copyOf(dependencies); } @Override public F get() { InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] creationArgs) throws Throwable { // pass methods from Object.class to the proxy if (method.getDeclaringClass().equals(Object.class)) { return method.invoke(this, creationArgs); } AssistedConstructor constructor = factoryMethodToConstructor.get(method); Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs); Object objectToReturn = constructor.newInstance(constructorArgs); injector.injectMembers(objectToReturn); return objectToReturn; } public Object[] gatherArgsForConstructor( AssistedConstructor constructor, Object[] factoryArgs) { int numParams = constructor.getAllParameters().size(); int argPosition = 0; Object[] result = new Object[numParams]; for (int i = 0; i < numParams; i++) { Parameter parameter = constructor.getAllParameters().get(i); if (parameter.isProvidedByFactory()) { result[i] = factoryArgs[argPosition]; argPosition++; } else { result[i] = parameter.getValue(injector); } } return result; } }; @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class Class factoryRawType = (Class) factoryType.getRawType(); return factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(), new Class[]{factoryRawType}, invocationHandler)); } private static ConfigurationException newConfigurationException(String format, Object... args) { return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy