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