ThrowingProviderBinder.java revision bac730fa1b717351736182034aff62827a383090
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
17package com.google.inject.throwingproviders;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import com.google.common.base.Predicate;
22import com.google.common.collect.FluentIterable;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.ImmutableSet;
25import com.google.common.collect.Lists;
26import com.google.inject.Binder;
27import com.google.inject.Key;
28import com.google.inject.Module;
29import com.google.inject.Provider;
30import com.google.inject.ProvisionException;
31import com.google.inject.Scopes;
32import com.google.inject.TypeLiteral;
33import com.google.inject.binder.ScopedBindingBuilder;
34import com.google.inject.internal.UniqueAnnotations;
35import com.google.inject.spi.Dependency;
36import com.google.inject.spi.ProviderWithDependencies;
37import com.google.inject.util.Types;
38
39import java.io.Serializable;
40import java.lang.annotation.Annotation;
41import java.lang.reflect.Constructor;
42import java.lang.reflect.InvocationHandler;
43import java.lang.reflect.Method;
44import java.lang.reflect.ParameterizedType;
45import java.lang.reflect.Proxy;
46import java.lang.reflect.Type;
47import java.lang.reflect.TypeVariable;
48import java.util.Arrays;
49import java.util.List;
50import 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 */
86public 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     * @param scopeExceptions whether exceptions should be scoped.
190     * @since 4.0
191     */
192    public SecondaryBinder<P, T> scopeExceptions(boolean scopeExceptions) {
193      this.scopeExceptions = scopeExceptions;
194      return this;
195    }
196
197    public ScopedBindingBuilder to(P target) {
198      Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create());
199      binder.bind(targetKey).toInstance(target);
200      return to(targetKey);
201    }
202
203    public ScopedBindingBuilder to(Class<? extends P> targetType) {
204      return to(Key.get(targetType));
205    }
206
207    public ScopedBindingBuilder providing(Class<? extends T> cxtorClass) {
208      return providing(TypeLiteral.get(cxtorClass));
209    }
210
211    @SuppressWarnings("unchecked") // safe because this is the cxtor of the literal
212    public ScopedBindingBuilder providing(TypeLiteral<? extends T> cxtorLiteral) {
213      // Find a constructor that has @ThrowingInject.
214      Constructor<? extends T> cxtor =
215          CheckedProvideUtils.findThrowingConstructor(cxtorLiteral, binder);
216
217      final Provider<T> typeProvider;
218      final Key<? extends T> typeKey;
219      // If we found an injection point, then bind the cxtor to a unique key
220      if (cxtor != null) {
221        // Validate the exceptions are consistent with the CheckedProvider interface.
222        CheckedProvideUtils.validateExceptions(
223            binder, cxtorLiteral.getExceptionTypes(cxtor), exceptionTypes, interfaceType);
224
225        typeKey = Key.get(cxtorLiteral, UniqueAnnotations.create());
226        binder.bind(typeKey).toConstructor((Constructor) cxtor).in(Scopes.NO_SCOPE);
227        typeProvider = binder.getProvider((Key<T>) typeKey);
228      } else {
229        // never used, but need it assigned.
230        typeProvider = null;
231        typeKey = null;
232      }
233
234      // Create a CheckedProvider that calls our cxtor
235      CheckedProvider<T> checkedProvider = new CheckedProviderWithDependencies<T>() {
236        @Override
237        public T get() throws Exception {
238          try {
239            return typeProvider.get();
240          } catch (ProvisionException pe) {
241            // Rethrow the provision cause as the actual exception
242            if (pe.getCause() instanceof Exception) {
243              throw (Exception) pe.getCause();
244            } else if (pe.getCause() instanceof Error) {
245              throw (Error) pe.getCause();
246            } else {
247              // If this failed because of multiple reasons (ie, more than
248              // one dependency failed due to scoping errors), then
249              // the ProvisionException won't have a cause, so we need
250              // to rethrow it as-is.
251              throw pe;
252            }
253          }
254        }
255
256        @Override
257        public Set<Dependency<?>> getDependencies() {
258          return ImmutableSet.<Dependency<?>>of(Dependency.get(typeKey));
259        }
260      };
261
262      Key<CheckedProvider<?>> targetKey = Key.get(CHECKED_PROVIDER_TYPE,
263          UniqueAnnotations.create());
264      binder.bind(targetKey).toInstance(checkedProvider);
265      return toInternal(targetKey);
266    }
267
268    ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) {
269      Key<CheckedProviderMethod<?>> targetKey =
270          Key.get(CHECKED_PROVIDER_METHOD_TYPE, UniqueAnnotations.create());
271      binder.bind(targetKey).toInstance(target);
272
273      return toInternal(targetKey);
274    }
275
276    @SuppressWarnings("unchecked") // P only extends the raw type of CheckedProvider
277    public ScopedBindingBuilder to(Key<? extends P> targetKey) {
278      checkNotNull(targetKey, "targetKey");
279      return toInternal((Key<? extends CheckedProvider<?>>)targetKey);
280    }
281
282    private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider<?>> targetKey) {
283      final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create());
284      // Note that this provider will behave like the final provider Guice creates.
285      // It will especially do scoping if the user adds that.
286      final Provider<Result> resultProvider = binder.getProvider(resultKey);
287      final Provider<? extends CheckedProvider<?>> targetProvider = binder.getProvider(targetKey);
288      interfaceKey = createKey();
289
290      // don't bother binding the proxy type if this is in an invalid state.
291      if(valid) {
292        binder.bind(interfaceKey).toProvider(new ProviderWithDependencies<P>() {
293          private final P instance = interfaceType.cast(Proxy.newProxyInstance(
294              interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
295              new InvocationHandler() {
296                public Object invoke(Object proxy, Method method, Object[] args)
297                    throws Throwable {
298                  // Allow methods like .equals(..), .hashcode(..), .toString(..) to work.
299                  if (method.getDeclaringClass() == Object.class) {
300                    return method.invoke(this, args);
301                  }
302
303                  if (scopeExceptions) {
304                    return resultProvider.get().getOrThrow();
305                  } else {
306                    Result result;
307                    try {
308                      result = resultProvider.get();
309                    } catch (ProvisionException pe) {
310                      Throwable cause = pe.getCause();
311                      if (cause instanceof ResultException) {
312                        throw ((ResultException)cause).getCause();
313                      } else {
314                        throw pe;
315                      }
316                    }
317                    return result.getOrThrow();
318                  }
319                }
320              }));
321
322            @Override
323            public P get() {
324              return instance;
325            }
326
327            @Override
328            public Set<Dependency<?>> getDependencies() {
329              return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey));
330            }
331          });
332      }
333
334      // The provider is unscoped, but the user may apply a scope to it through the
335      // ScopedBindingBuilder this returns.
336      return binder.bind(resultKey).toProvider(
337          createResultProvider(targetKey, targetProvider));
338    }
339
340    private ProviderWithDependencies<Result> createResultProvider(
341        final Key<? extends CheckedProvider<?>> targetKey,
342        final Provider<? extends CheckedProvider<?>> targetProvider) {
343      return new ProviderWithDependencies<Result>() {
344        @Override
345        public Result get() {
346          try {
347            return Result.forValue(targetProvider.get().get());
348          } catch (Exception e) {
349            for (Class<? extends Throwable> exceptionType : exceptionTypes) {
350              if (exceptionType.isInstance(e)) {
351                if (scopeExceptions) {
352                  return Result.forException(e);
353                } else {
354                  throw new ResultException(e);
355                }
356              }
357            }
358
359            if (e instanceof RuntimeException) {
360              throw (RuntimeException) e;
361            } else {
362              // this should never happen
363              throw new RuntimeException(e);
364            }
365          }
366        }
367
368        @Override
369        public Set<Dependency<?>> getDependencies() {
370          return ImmutableSet.<Dependency<?>>of(Dependency.get(targetKey));
371        }
372      };
373    }
374
375    /**
376     * Returns the exception type declared to be thrown by the get method of
377     * {@code interfaceType}.
378     */
379    private List<Class<? extends Throwable>> getExceptionType(Class<P> interfaceType) {
380      try {
381        Method getMethod = interfaceType.getMethod("get");
382        List<TypeLiteral<?>> exceptionLiterals =
383            TypeLiteral.get(interfaceType).getExceptionTypes(getMethod);
384        List<Class<? extends Throwable>> results = Lists.newArrayList();
385        for (TypeLiteral<?> exLiteral : exceptionLiterals) {
386          results.add(exLiteral.getRawType().asSubclass(Throwable.class));
387        }
388        return results;
389      } catch (SecurityException e) {
390        throw new IllegalStateException("Not allowed to inspect exception types", e);
391      } catch (NoSuchMethodException e) {
392        throw new IllegalStateException("No 'get'method available", e);
393      }
394    }
395
396    private boolean checkInterface() {
397      if(!checkArgument(interfaceType.isInterface(),
398         "%s must be an interface", interfaceType.getName())) {
399        return false;
400      }
401      if(!checkArgument(interfaceType.getGenericInterfaces().length == 1,
402          "%s must extend CheckedProvider (and only CheckedProvider)",
403          interfaceType)) {
404        return false;
405      }
406
407      boolean tpMode = interfaceType.getInterfaces()[0] == ThrowingProvider.class;
408      if(!tpMode) {
409        if(!checkArgument(interfaceType.getInterfaces()[0] == CheckedProvider.class,
410            "%s must extend CheckedProvider (and only CheckedProvider)",
411            interfaceType)) {
412          return false;
413        }
414      }
415
416      // Ensure that T is parameterized and unconstrained.
417      ParameterizedType genericThrowingProvider
418          = (ParameterizedType) interfaceType.getGenericInterfaces()[0];
419      if (interfaceType.getTypeParameters().length == 1) {
420        String returnTypeName = interfaceType.getTypeParameters()[0].getName();
421        Type returnType = genericThrowingProvider.getActualTypeArguments()[0];
422        if(!checkArgument(returnType instanceof TypeVariable,
423            "%s does not properly extend CheckedProvider, the first type parameter of CheckedProvider (%s) is not a generic type",
424            interfaceType, returnType)) {
425          return false;
426        }
427        if(!checkArgument(returnTypeName.equals(((TypeVariable) returnType).getName()),
428            "The generic type (%s) of %s does not match the generic type of CheckedProvider (%s)",
429            returnTypeName, interfaceType, ((TypeVariable)returnType).getName())) {
430          return false;
431        }
432      } else {
433        if(!checkArgument(interfaceType.getTypeParameters().length == 0,
434            "%s has more than one generic type parameter: %s",
435            interfaceType, Arrays.asList(interfaceType.getTypeParameters()))) {
436          return false;
437        }
438        if(!checkArgument(genericThrowingProvider.getActualTypeArguments()[0].equals(valueType),
439            "%s expects the value type to be %s, but it was %s",
440            interfaceType, genericThrowingProvider.getActualTypeArguments()[0], valueType)) {
441          return false;
442        }
443      }
444
445      if(tpMode) { // only validate exception in ThrowingProvider mode.
446        Type exceptionType = genericThrowingProvider.getActualTypeArguments()[1];
447        if(!checkArgument(exceptionType instanceof Class,
448            "%s has the wrong Exception generic type (%s) when extending CheckedProvider",
449            interfaceType, exceptionType)) {
450          return false;
451        }
452      }
453
454      // Skip synthetic/bridge methods because java8 generates
455      // a default method on the interface w/ the superinterface type that
456      // just delegates directly to the overridden method.
457      List<Method> declaredMethods = FluentIterable
458          .from(Arrays.asList(interfaceType.getDeclaredMethods()))
459          .filter(NotSyntheticOrBridgePredicate.INSTANCE)
460          .toList();
461      if (declaredMethods.size() == 1) {
462        Method method = declaredMethods.get(0);
463        if(!checkArgument(method.getName().equals("get"),
464            "%s may not declare any new methods, but declared %s",
465            interfaceType, method)) {
466          return false;
467        }
468        if(!checkArgument(method.getParameterTypes().length == 0,
469            "%s may not declare any new methods, but declared %s",
470            interfaceType, method.toGenericString())) {
471          return false;
472        }
473      } else {
474        if(!checkArgument(declaredMethods.isEmpty(),
475            "%s may not declare any new methods, but declared %s",
476            interfaceType, Arrays.asList(interfaceType.getDeclaredMethods()))) {
477          return false;
478        }
479      }
480
481      return true;
482    }
483
484    private boolean checkArgument(boolean condition,
485        String messageFormat, Object... args) {
486      if (!condition) {
487        binder.addError(messageFormat, args);
488        return false;
489      } else {
490        return true;
491      }
492    }
493
494    @SuppressWarnings({"unchecked"})
495    private Key<P> createKey() {
496      TypeLiteral<P> typeLiteral;
497      if (interfaceType.getTypeParameters().length == 1) {
498        ParameterizedType type = Types.newParameterizedTypeWithOwner(
499            interfaceType.getEnclosingClass(), interfaceType, valueType);
500        typeLiteral = (TypeLiteral<P>) TypeLiteral.get(type);
501      } else {
502        typeLiteral = TypeLiteral.get(interfaceType);
503      }
504
505      if (annotation != null) {
506        return Key.get(typeLiteral, annotation);
507
508      } else if (annotationType != null) {
509        return Key.get(typeLiteral, annotationType);
510
511      } else {
512        return Key.get(typeLiteral);
513      }
514    }
515  }
516
517  /**
518   * Represents the returned value from a call to {@link CheckedProvider#get()}. This is the value
519   * that will be scoped by Guice.
520   */
521  static class Result implements Serializable {
522    private static final long serialVersionUID = 0L;
523
524    private final Object value;
525    private final Exception exception;
526
527    private Result(Object value, Exception exception) {
528      this.value = value;
529      this.exception = exception;
530    }
531
532    public static Result forValue(Object value) {
533      return new Result(value, null);
534    }
535
536    public static Result forException(Exception e) {
537      return new Result(null, e);
538    }
539
540    public Object getOrThrow() throws Exception {
541      if (exception != null) {
542        throw exception;
543      } else {
544        return value;
545      }
546    }
547  }
548
549  /**
550   * RuntimeException class to wrap exceptions from the checked provider.
551   * The regular guice provider can throw it and the checked provider proxy extracts
552   * the underlying exception and rethrows it.
553   */
554  private static class ResultException extends RuntimeException {
555    ResultException(Exception cause) {
556      super(cause);
557    }
558  }
559
560  private static class NotSyntheticOrBridgePredicate implements Predicate<Method> {
561    static NotSyntheticOrBridgePredicate INSTANCE = new NotSyntheticOrBridgePredicate();
562    @Override public boolean apply(Method input) {
563      return !input.isBridge() && !input.isSynthetic();
564    }
565  }
566}
567