• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@google.com (Jerome Mourits)
83  * @author jessewilson@google.com (Jesse Wilson)
84  * @author sameb@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 
ThrowingProviderBinder(Binder binder)96   private ThrowingProviderBinder(Binder binder) {
97     this.binder = binder;
98   }
99 
create(Binder binder)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    */
forModule(Module module)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, ?>
bind(Class<P> interfaceType, Type clazz)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>
bind(Class<P> interfaceType, Class<T> clazz)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>
bind(Class<P> interfaceType, TypeLiteral<T> typeLiteral)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 
SecondaryBinder(Class<P> interfaceType, Type valueType)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 
getExceptionTypes()163     List<Class<? extends Throwable>> getExceptionTypes() {
164       return exceptionTypes;
165     }
166 
getKey()167     Key<P> getKey() {
168       return interfaceKey;
169     }
170 
annotatedWith(Class<? extends Annotation> annotationType)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 
annotatedWith(Annotation annotation)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      */
scopeExceptions(boolean scopeExceptions)193     public SecondaryBinder<P, T> scopeExceptions(boolean scopeExceptions) {
194       this.scopeExceptions = scopeExceptions;
195       return this;
196     }
197 
to(P target)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 
to(Class<? extends P> targetType)204     public ScopedBindingBuilder to(Class<? extends P> targetType) {
205       return to(Key.get(targetType));
206     }
207 
208     /** @since 4.0 */
providing(Class<? extends T> cxtorClass)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
providing(TypeLiteral<? extends T> cxtorLiteral)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 
toProviderMethod(CheckedProviderMethod<?> target)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
to(Key<? extends P> targetKey)280     public ScopedBindingBuilder to(Key<? extends P> targetKey) {
281       checkNotNull(targetKey, "targetKey");
282       return toInternal((Key<? extends CheckedProvider<?>>)targetKey);
283     }
284 
toInternal(final Key<? extends CheckedProvider<?>> targetKey)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 
createResultProvider( final Key<? extends CheckedProvider<?>> targetKey, final Provider<? extends CheckedProvider<?>> targetProvider)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