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