/* * 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.Optional; 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.util.List; import java.util.Set; /** * 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 static final TypeLiteral<CheckedProvider<?>> CHECKED_PROVIDER_TYPE = new TypeLiteral<CheckedProvider<?>>() {}; private static final TypeLiteral<CheckedProviderMethod<?>> CHECKED_PROVIDER_METHOD_TYPE = new TypeLiteral<CheckedProviderMethod<?>>() {}; 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); } /** @since 4.0 */ public <P extends CheckedProvider, T> SecondaryBinder<P, T> bind( Class<P> interfaceType, Class<T> clazz) { return new SecondaryBinder<P, T>(interfaceType, clazz); } /** @since 4.0 */ 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; private boolean scopeExceptions = true; 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; } /** * Determines if exceptions should be scoped. By default exceptions are scoped. * * @param scopeExceptions whether exceptions should be scoped. * @since 4.0 */ public SecondaryBinder<P, T> scopeExceptions(boolean scopeExceptions) { this.scopeExceptions = scopeExceptions; 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)); } /** @since 4.0 */ public ScopedBindingBuilder providing(Class<? extends T> cxtorClass) { return providing(TypeLiteral.get(cxtorClass)); } /** @since 4.0 */ @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(CHECKED_PROVIDER_TYPE, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(checkedProvider); return toInternal(targetKey); } ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) { Key<CheckedProviderMethod<?>> targetKey = Key.get(CHECKED_PROVIDER_METHOD_TYPE, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(target); return toInternal(targetKey); } @SuppressWarnings("unchecked") // P only extends the raw type of CheckedProvider public ScopedBindingBuilder to(Key<? extends P> targetKey) { checkNotNull(targetKey, "targetKey"); return toInternal((Key<? extends CheckedProvider<?>>) targetKey); } private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider<?>> targetKey) { final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create()); // Note that this provider will behave like the final provider Guice creates. // It will especially do scoping if the user adds that. 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() { @Override 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); } if (scopeExceptions) { return resultProvider.get().getOrThrow(); } else { Result result; try { result = resultProvider.get(); } catch (ProvisionException pe) { Throwable cause = pe.getCause(); if (cause instanceof ResultException) { throw ((ResultException) cause).getCause(); } else { throw pe; } } return result.getOrThrow(); } } })); @Override public P get() { return instance; } @Override public Set<Dependency<?>> getDependencies() { return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey)); } }); } // The provider is unscoped, but the user may apply a scope to it through the // ScopedBindingBuilder this returns. return binder.bind(resultKey).toProvider(createResultProvider(targetKey, targetProvider)); } private ProviderWithDependencies<Result> createResultProvider( final Key<? extends CheckedProvider<?>> targetKey, final Provider<? extends CheckedProvider<?>> targetProvider) { return new ProviderWithDependencies<Result>() { @Override public Result get() { try { return Result.forValue(targetProvider.get().get()); } catch (Exception e) { for (Class<? extends Throwable> exceptionType : exceptionTypes) { if (exceptionType.isInstance(e)) { if (scopeExceptions) { return Result.forException(e); } else { throw new ResultException(e); } } } if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { // this should never happen throw new RuntimeException(e); } } } @Override 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() { try { ProviderChecker.checkInterface(interfaceType, Optional.of(valueType)); return true; } catch (IllegalArgumentException e) { binder.addError(e.getMessage()); return false; } } @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 static final long serialVersionUID = 0L; 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; } } } /** * RuntimeException class to wrap exceptions from the checked provider. The regular guice provider * can throw it and the checked provider proxy extracts the underlying exception and rethrows it. */ private static class ResultException extends RuntimeException { ResultException(Exception cause) { super(cause); } } }