/** * 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 com.google.inject.throwingproviders; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.Scopes; import com.google.inject.TypeLiteral; import com.google.inject.binder.ScopedBindingBuilder; import com.google.inject.internal.UniqueAnnotations; import com.google.inject.spi.Dependency; import com.google.inject.spi.ProviderWithDependencies; import com.google.inject.util.Types; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.List; import java.util.Set; /** * <p>Builds a binding for a {@link CheckedProvider}. * * <p>You can use a fluent API and custom providers: * <pre><code>ThrowingProviderBinder.create(binder()) * .bind(RemoteProvider.class, Customer.class) * .to(RemoteCustomerProvider.class) * .in(RequestScope.class); * </code></pre> * or, you can use throwing provider methods: * <pre><code>class MyModule extends AbstractModule { * configure() { * ThrowingProviderBinder.install(this, binder()); * } * * {@literal @}CheckedProvides(RemoteProvider.class) * {@literal @}RequestScope * Customer provideCustomer(FlakyCustomerCreator creator) throws RemoteException { * return creator.getCustomerOrThrow(); * } * } * </code></pre> * You also can declare that a CheckedProvider construct * a particular class whose constructor throws an exception: * <pre><code>ThrowingProviderBinder.create(binder()) * .bind(RemoteProvider.class, Customer.class) * .providing(CustomerImpl.class) * .in(RequestScope.class); * </code></pre> * * @author jmourits@google.com (Jerome Mourits) * @author jessewilson@google.com (Jesse Wilson) * @author sameb@google.com (Sam Berlin) */ public class ThrowingProviderBinder { private final Binder binder; private ThrowingProviderBinder(Binder binder) { this.binder = binder; } public static ThrowingProviderBinder create(Binder binder) { return new ThrowingProviderBinder(binder.skipSources( ThrowingProviderBinder.class, ThrowingProviderBinder.SecondaryBinder.class)); } /** * Returns a module that installs {@literal @}{@link CheckedProvides} methods. * * @since 3.0 */ public static Module forModule(Module module) { return CheckedProviderMethodsModule.forModule(module); } /** * @deprecated Use {@link #bind(Class, Class)} or {@link #bind(Class, TypeLiteral)} instead. */ @Deprecated public <P extends CheckedProvider> SecondaryBinder<P, ?> bind(Class<P> interfaceType, Type clazz) { return new SecondaryBinder<P, Object>(interfaceType, clazz); } public <P extends CheckedProvider, T> SecondaryBinder<P, T> bind(Class<P> interfaceType, Class<T> clazz) { return new SecondaryBinder<P, T>(interfaceType, clazz); } public <P extends CheckedProvider, T> SecondaryBinder<P, T> bind(Class<P> interfaceType, TypeLiteral<T> typeLiteral) { return new SecondaryBinder<P, T>(interfaceType, typeLiteral.getType()); } public class SecondaryBinder<P extends CheckedProvider, T> { private final Class<P> interfaceType; private final Type valueType; private final List<Class<? extends Throwable>> exceptionTypes; private final boolean valid; private Class<? extends Annotation> annotationType; private Annotation annotation; private Key<P> interfaceKey; public SecondaryBinder(Class<P> interfaceType, Type valueType) { this.interfaceType = checkNotNull(interfaceType, "interfaceType"); this.valueType = checkNotNull(valueType, "valueType"); if(checkInterface()) { this.exceptionTypes = getExceptionType(interfaceType); valid = true; } else { valid = false; this.exceptionTypes = ImmutableList.of(); } } List<Class<? extends Throwable>> getExceptionTypes() { return exceptionTypes; } Key<P> getKey() { return interfaceKey; } public SecondaryBinder<P, T> annotatedWith(Class<? extends Annotation> annotationType) { if (!(this.annotationType == null && this.annotation == null)) { throw new IllegalStateException("Cannot set annotation twice"); } this.annotationType = annotationType; return this; } public SecondaryBinder<P, T> annotatedWith(Annotation annotation) { if (!(this.annotationType == null && this.annotation == null)) { throw new IllegalStateException("Cannot set annotation twice"); } this.annotation = annotation; return this; } public ScopedBindingBuilder to(P target) { Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(target); return to(targetKey); } public ScopedBindingBuilder to(Class<? extends P> targetType) { return to(Key.get(targetType)); } public ScopedBindingBuilder providing(Class<? extends T> cxtorClass) { return providing(TypeLiteral.get(cxtorClass)); } @SuppressWarnings("unchecked") // safe because this is the cxtor of the literal public ScopedBindingBuilder providing(TypeLiteral<? extends T> cxtorLiteral) { // Find a constructor that has @ThrowingInject. Constructor<? extends T> cxtor = CheckedProvideUtils.findThrowingConstructor(cxtorLiteral, binder); final Provider<T> typeProvider; final Key<? extends T> typeKey; // If we found an injection point, then bind the cxtor to a unique key if (cxtor != null) { // Validate the exceptions are consistent with the CheckedProvider interface. CheckedProvideUtils.validateExceptions( binder, cxtorLiteral.getExceptionTypes(cxtor), exceptionTypes, interfaceType); typeKey = Key.get(cxtorLiteral, UniqueAnnotations.create()); binder.bind(typeKey).toConstructor((Constructor) cxtor).in(Scopes.NO_SCOPE); typeProvider = binder.getProvider((Key<T>) typeKey); } else { // never used, but need it assigned. typeProvider = null; typeKey = null; } // Create a CheckedProvider that calls our cxtor CheckedProvider<T> checkedProvider = new CheckedProviderWithDependencies<T>() { @Override public T get() throws Exception { try { return typeProvider.get(); } catch (ProvisionException pe) { // Rethrow the provision cause as the actual exception if (pe.getCause() instanceof Exception) { throw (Exception) pe.getCause(); } else if (pe.getCause() instanceof Error) { throw (Error) pe.getCause(); } else { // If this failed because of multiple reasons (ie, more than // one dependency failed due to scoping errors), then // the ProvisionException won't have a cause, so we need // to rethrow it as-is. throw pe; } } } @Override public Set<Dependency<?>> getDependencies() { return ImmutableSet.<Dependency<?>>of(Dependency.get(typeKey)); } }; Key<CheckedProvider> targetKey = Key.get(CheckedProvider.class, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(checkedProvider); return toInternal(targetKey); } ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) { Key<CheckedProviderMethod> targetKey = Key.get(CheckedProviderMethod.class, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(target); return toInternal(targetKey); } public ScopedBindingBuilder to(Key<? extends P> targetKey) { checkNotNull(targetKey, "targetKey"); return toInternal(targetKey); } private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider> targetKey) { final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create()); final Provider<Result> resultProvider = binder.getProvider(resultKey); final Provider<? extends CheckedProvider> targetProvider = binder.getProvider(targetKey); interfaceKey = createKey(); // don't bother binding the proxy type if this is in an invalid state. if(valid) { binder.bind(interfaceKey).toProvider(new ProviderWithDependencies<P>() { private final P instance = interfaceType.cast(Proxy.newProxyInstance( interfaceType.getClassLoader(), new Class<?>[] { interfaceType }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Allow methods like .equals(..), .hashcode(..), .toString(..) to work. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } return resultProvider.get().getOrThrow(); } })); public P get() { return instance; } public Set<Dependency<?>> getDependencies() { return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey)); } }); } return binder.bind(resultKey).toProvider(new ProviderWithDependencies<Result>() { public Result get() { try { return Result.forValue(targetProvider.get().get()); } catch (Exception e) { for(Class<? extends Throwable> exceptionType : exceptionTypes) { if (exceptionType.isInstance(e)) { return Result.forException(e); } } if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { // this should never happen throw new RuntimeException(e); } } } public Set<Dependency<?>> getDependencies() { return ImmutableSet.<Dependency<?>>of(Dependency.get(targetKey)); } }); } /** * Returns the exception type declared to be thrown by the get method of * {@code interfaceType}. */ private List<Class<? extends Throwable>> getExceptionType(Class<P> interfaceType) { try { Method getMethod = interfaceType.getMethod("get"); List<TypeLiteral<?>> exceptionLiterals = TypeLiteral.get(interfaceType).getExceptionTypes(getMethod); List<Class<? extends Throwable>> results = Lists.newArrayList(); for (TypeLiteral<?> exLiteral : exceptionLiterals) { results.add(exLiteral.getRawType().asSubclass(Throwable.class)); } return results; } catch (SecurityException e) { throw new IllegalStateException("Not allowed to inspect exception types", e); } catch (NoSuchMethodException e) { throw new IllegalStateException("No 'get'method available", e); } } private boolean checkInterface() { if(!checkArgument(interfaceType.isInterface(), "%s must be an interface", interfaceType.getName())) { return false; } if(!checkArgument(interfaceType.getGenericInterfaces().length == 1, "%s must extend CheckedProvider (and only CheckedProvider)", interfaceType)) { return false; } boolean tpMode = interfaceType.getInterfaces()[0] == ThrowingProvider.class; if(!tpMode) { if(!checkArgument(interfaceType.getInterfaces()[0] == CheckedProvider.class, "%s must extend CheckedProvider (and only CheckedProvider)", interfaceType)) { return false; } } // Ensure that T is parameterized and unconstrained. ParameterizedType genericThrowingProvider = (ParameterizedType) interfaceType.getGenericInterfaces()[0]; if (interfaceType.getTypeParameters().length == 1) { String returnTypeName = interfaceType.getTypeParameters()[0].getName(); Type returnType = genericThrowingProvider.getActualTypeArguments()[0]; if(!checkArgument(returnType instanceof TypeVariable, "%s does not properly extend CheckedProvider, the first type parameter of CheckedProvider (%s) is not a generic type", interfaceType, returnType)) { return false; } if(!checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()), "The generic type (%s) of %s does not match the generic type of CheckedProvider (%s)", returnTypeName, interfaceType, ((TypeVariable)returnType).getName())) { return false; } } else { if(!checkArgument(interfaceType.getTypeParameters().length == 0, "%s has more than one generic type parameter: %s", interfaceType, Arrays.asList(interfaceType.getTypeParameters()))) { return false; } if(!checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType), "%s expects the value type to be %s, but it was %s", interfaceType, genericThrowingProvider.getActualTypeArguments()[0], valueType)) { return false; } } if(tpMode) { // only validate exception in ThrowingProvider mode. Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1]; if(!checkArgument(exceptionType instanceof Class, "%s has the wrong Exception generic type (%s) when extending CheckedProvider", interfaceType, exceptionType)) { return false; } } // Skip synthetic/bridge methods because java8 generates // a default method on the interface w/ the superinterface type that // just delegates directly to the overridden method. List<Method> declaredMethods = FluentIterable .from(Arrays.asList(interfaceType.getDeclaredMethods())) .filter(NotSyntheticOrBridgePredicate.INSTANCE) .toList(); if (declaredMethods.size() == 1) { Method method = declaredMethods.get(0); if(!checkArgument(method.getName().equals("get"), "%s may not declare any new methods, but declared %s", interfaceType, method)) { return false; } if(!checkArgument(method.getParameterTypes().length == 0, "%s may not declare any new methods, but declared %s", interfaceType, method.toGenericString())) { return false; } } else { if(!checkArgument(declaredMethods.isEmpty(), "%s may not declare any new methods, but declared %s", interfaceType, Arrays.asList(interfaceType.getDeclaredMethods()))) { return false; } } return true; } private boolean checkArgument(boolean condition, String messageFormat, Object... args) { if (!condition) { binder.addError(messageFormat, args); return false; } else { return true; } } @SuppressWarnings({"unchecked"}) private Key<P> createKey() { TypeLiteral<P> typeLiteral; if (interfaceType.getTypeParameters().length == 1) { ParameterizedType type = Types.newParameterizedTypeWithOwner( interfaceType.getEnclosingClass(), interfaceType, valueType); typeLiteral = (TypeLiteral<P>) TypeLiteral.get(type); } else { typeLiteral = TypeLiteral.get(interfaceType); } if (annotation != null) { return Key.get(typeLiteral, annotation); } else if (annotationType != null) { return Key.get(typeLiteral, annotationType); } else { return Key.get(typeLiteral); } } } /** * Represents the returned value from a call to {@link * CheckedProvider#get()}. This is the value that will be scoped by Guice. */ static class Result implements Serializable { private final Object value; private final Exception exception; private Result(Object value, Exception exception) { this.value = value; this.exception = exception; } public static Result forValue(Object value) { return new Result(value, null); } public static Result forException(Exception e) { return new Result(null, e); } public Object getOrThrow() throws Exception { if (exception != null) { throw exception; } else { return value; } } private static final long serialVersionUID = 0L; } private static class NotSyntheticOrBridgePredicate implements Predicate<Method> { static NotSyntheticOrBridgePredicate INSTANCE = new NotSyntheticOrBridgePredicate(); @Override public boolean apply(Method input) { return !input.isBridge() && !input.isSynthetic(); } } }