Home | History | Annotate | Download | only in throwingproviders
      1 /**
      2  * Copyright (C) 2007 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject.throwingproviders;
     18 
     19 import static com.google.common.base.Preconditions.checkNotNull;
     20 
     21 import com.google.common.base.Predicate;
     22 import com.google.common.collect.FluentIterable;
     23 import com.google.common.collect.ImmutableList;
     24 import com.google.common.collect.ImmutableSet;
     25 import com.google.common.collect.Lists;
     26 import com.google.inject.Binder;
     27 import com.google.inject.Key;
     28 import com.google.inject.Module;
     29 import com.google.inject.Provider;
     30 import com.google.inject.ProvisionException;
     31 import com.google.inject.Scopes;
     32 import com.google.inject.TypeLiteral;
     33 import com.google.inject.binder.ScopedBindingBuilder;
     34 import com.google.inject.internal.UniqueAnnotations;
     35 import com.google.inject.spi.Dependency;
     36 import com.google.inject.spi.ProviderWithDependencies;
     37 import com.google.inject.util.Types;
     38 
     39 import java.io.Serializable;
     40 import java.lang.annotation.Annotation;
     41 import java.lang.reflect.Constructor;
     42 import java.lang.reflect.InvocationHandler;
     43 import java.lang.reflect.Method;
     44 import java.lang.reflect.ParameterizedType;
     45 import java.lang.reflect.Proxy;
     46 import java.lang.reflect.Type;
     47 import java.lang.reflect.TypeVariable;
     48 import java.util.Arrays;
     49 import java.util.List;
     50 import java.util.Set;
     51 
     52 /**
     53  * <p>Builds a binding for a {@link CheckedProvider}.
     54  *
     55  * <p>You can use a fluent API and custom providers:
     56  * <pre><code>ThrowingProviderBinder.create(binder())
     57  *    .bind(RemoteProvider.class, Customer.class)
     58  *    .to(RemoteCustomerProvider.class)
     59  *    .in(RequestScope.class);
     60  * </code></pre>
     61  * or, you can use throwing provider methods:
     62  * <pre><code>class MyModule extends AbstractModule {
     63  *   configure() {
     64  *     ThrowingProviderBinder.install(this, binder());
     65  *   }
     66  *
     67  *   {@literal @}CheckedProvides(RemoteProvider.class)
     68  *   {@literal @}RequestScope
     69  *   Customer provideCustomer(FlakyCustomerCreator creator) throws RemoteException {
     70  *     return creator.getCustomerOrThrow();
     71  *   }
     72  * }
     73  * </code></pre>
     74  * You also can declare that a CheckedProvider construct
     75  * a particular class whose constructor throws an exception:
     76  * <pre><code>ThrowingProviderBinder.create(binder())
     77  *    .bind(RemoteProvider.class, Customer.class)
     78  *    .providing(CustomerImpl.class)
     79  *    .in(RequestScope.class);
     80  * </code></pre>
     81  *
     82  * @author jmourits (at) google.com (Jerome Mourits)
     83  * @author jessewilson (at) google.com (Jesse Wilson)
     84  * @author sameb (at) google.com (Sam Berlin)
     85  */
     86 public class ThrowingProviderBinder {
     87 
     88   private static final TypeLiteral<CheckedProvider<?>> CHECKED_PROVIDER_TYPE
     89       = new TypeLiteral<CheckedProvider<?>>() { };
     90 
     91   private static final TypeLiteral<CheckedProviderMethod<?>> CHECKED_PROVIDER_METHOD_TYPE
     92       = new TypeLiteral<CheckedProviderMethod<?>>() { };
     93 
     94   private final Binder binder;
     95 
     96   private ThrowingProviderBinder(Binder binder) {
     97     this.binder = binder;
     98   }
     99 
    100   public static ThrowingProviderBinder create(Binder binder) {
    101     return new ThrowingProviderBinder(binder.skipSources(
    102         ThrowingProviderBinder.class,
    103         ThrowingProviderBinder.SecondaryBinder.class));
    104   }
    105 
    106   /**
    107    * Returns a module that installs {@literal @}{@link CheckedProvides} methods.
    108    *
    109    * @since 3.0
    110    */
    111   public static Module forModule(Module module) {
    112     return CheckedProviderMethodsModule.forModule(module);
    113   }
    114 
    115   /**
    116    * @deprecated Use {@link #bind(Class, Class)} or {@link #bind(Class, TypeLiteral)} instead.
    117    */
    118   @Deprecated
    119   public <P extends CheckedProvider> SecondaryBinder<P, ?>
    120       bind(Class<P> interfaceType, Type clazz) {
    121     return new SecondaryBinder<P, Object>(interfaceType, clazz);
    122   }
    123 
    124   /**
    125    * @since 4.0
    126    */
    127   public <P extends CheckedProvider, T> SecondaryBinder<P, T>
    128       bind(Class<P> interfaceType, Class<T> clazz) {
    129     return new SecondaryBinder<P, T>(interfaceType, clazz);
    130   }
    131 
    132   /**
    133    * @since 4.0
    134    */
    135   public <P extends CheckedProvider, T> SecondaryBinder<P, T>
    136       bind(Class<P> interfaceType, TypeLiteral<T> typeLiteral) {
    137     return new SecondaryBinder<P, T>(interfaceType, typeLiteral.getType());
    138   }
    139 
    140   public class SecondaryBinder<P extends CheckedProvider, T> {
    141     private final Class<P> interfaceType;
    142     private final Type valueType;
    143     private final List<Class<? extends Throwable>> exceptionTypes;
    144     private final boolean valid;
    145 
    146     private Class<? extends Annotation> annotationType;
    147     private Annotation annotation;
    148     private Key<P> interfaceKey;
    149     private boolean scopeExceptions = true;
    150 
    151     public SecondaryBinder(Class<P> interfaceType, Type valueType) {
    152       this.interfaceType = checkNotNull(interfaceType, "interfaceType");
    153       this.valueType = checkNotNull(valueType, "valueType");
    154       if(checkInterface()) {
    155         this.exceptionTypes = getExceptionType(interfaceType);
    156         valid = true;
    157       } else {
    158         valid = false;
    159         this.exceptionTypes = ImmutableList.of();
    160       }
    161     }
    162 
    163     List<Class<? extends Throwable>> getExceptionTypes() {
    164       return exceptionTypes;
    165     }
    166 
    167     Key<P> getKey() {
    168       return interfaceKey;
    169     }
    170 
    171     public SecondaryBinder<P, T> annotatedWith(Class<? extends Annotation> annotationType) {
    172       if (!(this.annotationType == null && this.annotation == null)) {
    173         throw new IllegalStateException("Cannot set annotation twice");
    174       }
    175       this.annotationType = annotationType;
    176       return this;
    177     }
    178 
    179     public SecondaryBinder<P, T> annotatedWith(Annotation annotation) {
    180       if (!(this.annotationType == null && this.annotation == null)) {
    181         throw new IllegalStateException("Cannot set annotation twice");
    182       }
    183       this.annotation = annotation;
    184       return this;
    185     }
    186 
    187     /**
    188      * Determines if exceptions should be scoped. By default exceptions are scoped.
    189      *
    190      * @param scopeExceptions whether exceptions should be scoped.
    191      * @since 4.0
    192      */
    193     public SecondaryBinder<P, T> scopeExceptions(boolean scopeExceptions) {
    194       this.scopeExceptions = scopeExceptions;
    195       return this;
    196     }
    197 
    198     public ScopedBindingBuilder to(P target) {
    199       Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create());
    200       binder.bind(targetKey).toInstance(target);
    201       return to(targetKey);
    202     }
    203 
    204     public ScopedBindingBuilder to(Class<? extends P> targetType) {
    205       return to(Key.get(targetType));
    206     }
    207 
    208     /** @since 4.0 */
    209     public ScopedBindingBuilder providing(Class<? extends T> cxtorClass) {
    210       return providing(TypeLiteral.get(cxtorClass));
    211     }
    212 
    213     /** @since 4.0 */
    214     @SuppressWarnings("unchecked") // safe because this is the cxtor of the literal
    215     public ScopedBindingBuilder providing(TypeLiteral<? extends T> cxtorLiteral) {
    216       // Find a constructor that has @ThrowingInject.
    217       Constructor<? extends T> cxtor =
    218           CheckedProvideUtils.findThrowingConstructor(cxtorLiteral, binder);
    219 
    220       final Provider<T> typeProvider;
    221       final Key<? extends T> typeKey;
    222       // If we found an injection point, then bind the cxtor to a unique key
    223       if (cxtor != null) {
    224         // Validate the exceptions are consistent with the CheckedProvider interface.
    225         CheckedProvideUtils.validateExceptions(
    226             binder, cxtorLiteral.getExceptionTypes(cxtor), exceptionTypes, interfaceType);
    227 
    228         typeKey = Key.get(cxtorLiteral, UniqueAnnotations.create());
    229         binder.bind(typeKey).toConstructor((Constructor) cxtor).in(Scopes.NO_SCOPE);
    230         typeProvider = binder.getProvider((Key<T>) typeKey);
    231       } else {
    232         // never used, but need it assigned.
    233         typeProvider = null;
    234         typeKey = null;
    235       }
    236 
    237       // Create a CheckedProvider that calls our cxtor
    238       CheckedProvider<T> checkedProvider = new CheckedProviderWithDependencies<T>() {
    239         @Override
    240         public T get() throws Exception {
    241           try {
    242             return typeProvider.get();
    243           } catch (ProvisionException pe) {
    244             // Rethrow the provision cause as the actual exception
    245             if (pe.getCause() instanceof Exception) {
    246               throw (Exception) pe.getCause();
    247             } else if (pe.getCause() instanceof Error) {
    248               throw (Error) pe.getCause();
    249             } else {
    250               // If this failed because of multiple reasons (ie, more than
    251               // one dependency failed due to scoping errors), then
    252               // the ProvisionException won't have a cause, so we need
    253               // to rethrow it as-is.
    254               throw pe;
    255             }
    256           }
    257         }
    258 
    259         @Override
    260         public Set<Dependency<?>> getDependencies() {
    261           return ImmutableSet.<Dependency<?>>of(Dependency.get(typeKey));
    262         }
    263       };
    264 
    265       Key<CheckedProvider<?>> targetKey = Key.get(CHECKED_PROVIDER_TYPE,
    266           UniqueAnnotations.create());
    267       binder.bind(targetKey).toInstance(checkedProvider);
    268       return toInternal(targetKey);
    269     }
    270 
    271     ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) {
    272       Key<CheckedProviderMethod<?>> targetKey =
    273           Key.get(CHECKED_PROVIDER_METHOD_TYPE, UniqueAnnotations.create());
    274       binder.bind(targetKey).toInstance(target);
    275 
    276       return toInternal(targetKey);
    277     }
    278 
    279     @SuppressWarnings("unchecked") // P only extends the raw type of CheckedProvider
    280     public ScopedBindingBuilder to(Key<? extends P> targetKey) {
    281       checkNotNull(targetKey, "targetKey");
    282       return toInternal((Key<? extends CheckedProvider<?>>)targetKey);
    283     }
    284 
    285     private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider<?>> targetKey) {
    286       final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create());
    287       // Note that this provider will behave like the final provider Guice creates.
    288       // It will especially do scoping if the user adds that.
    289       final Provider<Result> resultProvider = binder.getProvider(resultKey);
    290       final Provider<? extends CheckedProvider<?>> targetProvider = binder.getProvider(targetKey);
    291       interfaceKey = createKey();
    292 
    293       // don't bother binding the proxy type if this is in an invalid state.
    294       if(valid) {
    295         binder.bind(interfaceKey).toProvider(new ProviderWithDependencies<P>() {
    296           private final P instance = interfaceType.cast(Proxy.newProxyInstance(
    297               interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
    298               new InvocationHandler() {
    299                 public Object invoke(Object proxy, Method method, Object[] args)
    300                     throws Throwable {
    301                   // Allow methods like .equals(..), .hashcode(..), .toString(..) to work.
    302                   if (method.getDeclaringClass() == Object.class) {
    303                     return method.invoke(this, args);
    304                   }
    305 
    306                   if (scopeExceptions) {
    307                     return resultProvider.get().getOrThrow();
    308                   } else {
    309                     Result result;
    310                     try {
    311                       result = resultProvider.get();
    312                     } catch (ProvisionException pe) {
    313                       Throwable cause = pe.getCause();
    314                       if (cause instanceof ResultException) {
    315                         throw ((ResultException)cause).getCause();
    316                       } else {
    317                         throw pe;
    318                       }
    319                     }
    320                     return result.getOrThrow();
    321                   }
    322                 }
    323               }));
    324 
    325             @Override
    326             public P get() {
    327               return instance;
    328             }
    329 
    330             @Override
    331             public Set<Dependency<?>> getDependencies() {
    332               return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey));
    333             }
    334           });
    335       }
    336 
    337       // The provider is unscoped, but the user may apply a scope to it through the
    338       // ScopedBindingBuilder this returns.
    339       return binder.bind(resultKey).toProvider(
    340           createResultProvider(targetKey, targetProvider));
    341     }
    342 
    343     private ProviderWithDependencies<Result> createResultProvider(
    344         final Key<? extends CheckedProvider<?>> targetKey,
    345         final Provider<? extends CheckedProvider<?>> targetProvider) {
    346       return new ProviderWithDependencies<Result>() {
    347         @Override
    348         public Result get() {
    349           try {
    350             return Result.forValue(targetProvider.get().get());
    351           } catch (Exception e) {
    352             for (Class<? extends Throwable> exceptionType : exceptionTypes) {
    353               if (exceptionType.isInstance(e)) {
    354                 if (scopeExceptions) {
    355                   return Result.forException(e);
    356                 } else {
    357                   throw new ResultException(e);
    358                 }
    359               }
    360             }
    361 
    362             if (e instanceof RuntimeException) {
    363               throw (RuntimeException) e;
    364             } else {
    365               // this should never happen
    366               throw new RuntimeException(e);
    367             }
    368           }
    369         }
    370 
    371         @Override
    372         public Set<Dependency<?>> getDependencies() {
    373           return ImmutableSet.<Dependency<?>>of(Dependency.get(targetKey));
    374         }
    375       };
    376     }
    377 
    378     /**
    379      * Returns the exception type declared to be thrown by the get method of
    380      * {@code interfaceType}.
    381      */
    382     private List<Class<? extends Throwable>> getExceptionType(Class<P> interfaceType) {
    383       try {
    384         Method getMethod = interfaceType.getMethod("get");
    385         List<TypeLiteral<?>> exceptionLiterals =
    386             TypeLiteral.get(interfaceType).getExceptionTypes(getMethod);
    387         List<Class<? extends Throwable>> results = Lists.newArrayList();
    388         for (TypeLiteral<?> exLiteral : exceptionLiterals) {
    389           results.add(exLiteral.getRawType().asSubclass(Throwable.class));
    390         }
    391         return results;
    392       } catch (SecurityException e) {
    393         throw new IllegalStateException("Not allowed to inspect exception types", e);
    394       } catch (NoSuchMethodException e) {
    395         throw new IllegalStateException("No 'get'method available", e);
    396       }
    397     }
    398 
    399     private boolean checkInterface() {
    400       if(!checkArgument(interfaceType.isInterface(),
    401          "%s must be an interface", interfaceType.getName())) {
    402         return false;
    403       }
    404       if(!checkArgument(interfaceType.getGenericInterfaces().length == 1,
    405           "%s must extend CheckedProvider (and only CheckedProvider)",
    406           interfaceType)) {
    407         return false;
    408       }
    409 
    410       boolean tpMode = interfaceType.getInterfaces()[0] == ThrowingProvider.class;
    411       if(!tpMode) {
    412         if(!checkArgument(interfaceType.getInterfaces()[0] == CheckedProvider.class,
    413             "%s must extend CheckedProvider (and only CheckedProvider)",
    414             interfaceType)) {
    415           return false;
    416         }
    417       }
    418 
    419       // Ensure that T is parameterized and unconstrained.
    420       ParameterizedType genericThrowingProvider
    421           = (ParameterizedType) interfaceType.getGenericInterfaces()[0];
    422       if (interfaceType.getTypeParameters().length == 1) {
    423         String returnTypeName = interfaceType.getTypeParameters()[0].getName();
    424         Type returnType = genericThrowingProvider.getActualTypeArguments()[0];
    425         if(!checkArgument(returnType instanceof TypeVariable,
    426             "%s does not properly extend CheckedProvider, the first type parameter of CheckedProvider (%s) is not a generic type",
    427             interfaceType, returnType)) {
    428           return false;
    429         }
    430         if(!checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()),
    431             "The generic type (%s) of %s does not match the generic type of CheckedProvider (%s)",
    432             returnTypeName, interfaceType, ((TypeVariable)returnType).getName())) {
    433           return false;
    434         }
    435       } else {
    436         if(!checkArgument(interfaceType.getTypeParameters().length == 0,
    437             "%s has more than one generic type parameter: %s",
    438             interfaceType, Arrays.asList(interfaceType.getTypeParameters()))) {
    439           return false;
    440         }
    441         if(!checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType),
    442             "%s expects the value type to be %s, but it was %s",
    443             interfaceType, genericThrowingProvider.getActualTypeArguments()[0], valueType)) {
    444           return false;
    445         }
    446       }
    447 
    448       if(tpMode) { // only validate exception in ThrowingProvider mode.
    449         Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1];
    450         if(!checkArgument(exceptionType instanceof Class,
    451             "%s has the wrong Exception generic type (%s) when extending CheckedProvider",
    452             interfaceType, exceptionType)) {
    453           return false;
    454         }
    455       }
    456 
    457       // Skip synthetic/bridge methods because java8 generates
    458       // a default method on the interface w/ the superinterface type that
    459       // just delegates directly to the overridden method.
    460       List<Method> declaredMethods = FluentIterable
    461           .from(Arrays.asList(interfaceType.getDeclaredMethods()))
    462           .filter(NotSyntheticOrBridgePredicate.INSTANCE)
    463           .toList();
    464       if (declaredMethods.size() == 1) {
    465         Method method = declaredMethods.get(0);
    466         if(!checkArgument(method.getName().equals("get"),
    467             "%s may not declare any new methods, but declared %s",
    468             interfaceType, method)) {
    469           return false;
    470         }
    471         if(!checkArgument(method.getParameterTypes().length == 0,
    472             "%s may not declare any new methods, but declared %s",
    473             interfaceType, method.toGenericString())) {
    474           return false;
    475         }
    476       } else {
    477         if(!checkArgument(declaredMethods.isEmpty(),
    478             "%s may not declare any new methods, but declared %s",
    479             interfaceType, Arrays.asList(interfaceType.getDeclaredMethods()))) {
    480           return false;
    481         }
    482       }
    483 
    484       return true;
    485     }
    486 
    487     private boolean checkArgument(boolean condition,
    488         String messageFormat, Object... args) {
    489       if (!condition) {
    490         binder.addError(messageFormat, args);
    491         return false;
    492       } else {
    493         return true;
    494       }
    495     }
    496 
    497     @SuppressWarnings({"unchecked"})
    498     private Key<P> createKey() {
    499       TypeLiteral<P> typeLiteral;
    500       if (interfaceType.getTypeParameters().length == 1) {
    501         ParameterizedType type = Types.newParameterizedTypeWithOwner(
    502             interfaceType.getEnclosingClass(), interfaceType, valueType);
    503         typeLiteral = (TypeLiteral<P>) TypeLiteral.get(type);
    504       } else {
    505         typeLiteral = TypeLiteral.get(interfaceType);
    506       }
    507 
    508       if (annotation != null) {
    509         return Key.get(typeLiteral, annotation);
    510 
    511       } else if (annotationType != null) {
    512         return Key.get(typeLiteral, annotationType);
    513 
    514       } else {
    515         return Key.get(typeLiteral);
    516       }
    517     }
    518   }
    519 
    520   /**
    521    * Represents the returned value from a call to {@link CheckedProvider#get()}. This is the value
    522    * that will be scoped by Guice.
    523    */
    524   static class Result implements Serializable {
    525     private static final long serialVersionUID = 0L;
    526 
    527     private final Object value;
    528     private final Exception exception;
    529 
    530     private Result(Object value, Exception exception) {
    531       this.value = value;
    532       this.exception = exception;
    533     }
    534 
    535     public static Result forValue(Object value) {
    536       return new Result(value, null);
    537     }
    538 
    539     public static Result forException(Exception e) {
    540       return new Result(null, e);
    541     }
    542 
    543     public Object getOrThrow() throws Exception {
    544       if (exception != null) {
    545         throw exception;
    546       } else {
    547         return value;
    548       }
    549     }
    550   }
    551 
    552   /**
    553    * RuntimeException class to wrap exceptions from the checked provider.
    554    * The regular guice provider can throw it and the checked provider proxy extracts
    555    * the underlying exception and rethrows it.
    556    */
    557   private static class ResultException extends RuntimeException {
    558     ResultException(Exception cause) {
    559       super(cause);
    560     }
    561   }
    562 
    563   private static class NotSyntheticOrBridgePredicate implements Predicate<Method> {
    564     static NotSyntheticOrBridgePredicate INSTANCE = new NotSyntheticOrBridgePredicate();
    565     @Override public boolean apply(Method input) {
    566       return !input.isBridge() && !input.isSynthetic();
    567     }
    568   }
    569 }
    570