diff options
author | cnester <cnester@google.com> | 2015-03-02 10:11:36 -0800 |
---|---|---|
committer | Sam Berlin <sameb@google.com> | 2015-03-23 18:27:17 -0400 |
commit | fb04caeeadfea0ba966bcfe633aa5ec3f8046cbf (patch) | |
tree | 658d7c02d131b5031140ff2ad1e886d5020840e5 | |
parent | 156c8cc762fab971efb727c7ab107fa243be2fc9 (diff) | |
download | guice-fb04caeeadfea0ba966bcfe633aa5ec3f8046cbf.tar.gz |
Add ability to not scope exception to RemoteProviderBinder
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=87524840
6 files changed, 256 insertions, 38 deletions
diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java index f65b6d2f..a76d64cb 100644 --- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java +++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethod.java @@ -50,6 +50,7 @@ class CheckedProviderMethod<T> implements CheckedProvider<T>, HasDependencies { private final boolean exposed; private final Class<? extends CheckedProvider> checkedProvider; private final List<TypeLiteral<?>> exceptionTypes; + private final boolean scopeExceptions; CheckedProviderMethod( Key<T> key, @@ -59,7 +60,8 @@ class CheckedProviderMethod<T> implements CheckedProvider<T>, HasDependencies { List<Provider<?>> parameterProviders, Class<? extends Annotation> scopeAnnotation, Class<? extends CheckedProvider> checkedProvider, - List<TypeLiteral<?>> exceptionTypes) { + List<TypeLiteral<?>> exceptionTypes, + boolean scopeExceptions) { this.key = key; this.scopeAnnotation = scopeAnnotation; this.instance = instance; @@ -69,6 +71,7 @@ class CheckedProviderMethod<T> implements CheckedProvider<T>, HasDependencies { this.exposed = method.isAnnotationPresent(Exposed.class); this.checkedProvider = checkedProvider; this.exceptionTypes = exceptionTypes; + this.scopeExceptions = scopeExceptions; method.setAccessible(true); } @@ -77,13 +80,14 @@ class CheckedProviderMethod<T> implements CheckedProvider<T>, HasDependencies { binder = binder.withSource(method); SecondaryBinder<?, ?> sbinder = - ThrowingProviderBinder.create(binder) - .bind(checkedProvider, key.getTypeLiteral()); + ThrowingProviderBinder.create(binder) + .bind(checkedProvider, key.getTypeLiteral()); if(key.getAnnotation() != null) { sbinder = sbinder.annotatedWith(key.getAnnotation()); } else if(key.getAnnotationType() != null) { sbinder = sbinder.annotatedWith(key.getAnnotationType()); - } + } + sbinder.scopeExceptions(scopeExceptions); ScopedBindingBuilder sbbuilder = sbinder.toProviderMethod(this); if(scopeAnnotation != null) { sbbuilder.in(scopeAnnotation); diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java index dce2bfa9..f88c5078 100644 --- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java +++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProviderMethodsModule.java @@ -79,7 +79,7 @@ final class CheckedProviderMethodsModule implements Module { for (Method method : c.getDeclaredMethods()) { CheckedProvides checkedProvides = method.getAnnotation(CheckedProvides.class); if(checkedProvides != null) { - result.add(createProviderMethod(binder, method, checkedProvides.value())); + result.add(createProviderMethod(binder, method, checkedProvides)); } } } @@ -87,7 +87,9 @@ final class CheckedProviderMethodsModule implements Module { } <T> CheckedProviderMethod<T> createProviderMethod(Binder binder, final Method method, - Class<? extends CheckedProvider> throwingProvider) { + CheckedProvides checkedProvides) { + @SuppressWarnings("rawtypes") + Class<? extends CheckedProvider> throwingProvider = checkedProvides.value(); binder = binder.withSource(method); Errors errors = new Errors(method); @@ -123,7 +125,8 @@ final class CheckedProviderMethodsModule implements Module { } return new CheckedProviderMethod<T>(key, method, delegate, ImmutableSet.copyOf(dependencies), - parameterProviders, scopeAnnotation, throwingProvider, exceptionTypes); + parameterProviders, scopeAnnotation, throwingProvider, exceptionTypes, + checkedProvides.scopeExceptions()); } <T> Key<T> getKey(Errors errors, TypeLiteral<T> type, Member member, Annotation[] annotations) { diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java index b702dcc8..c7bfaedc 100644 --- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java +++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/CheckedProvides.java @@ -42,5 +42,10 @@ public @interface CheckedProvides { * The interface that provides this value, a subinterface of {@link CheckedProvider}. */ Class<? extends CheckedProvider> value(); - + + /** + * Whether exceptions should be put into the Guice scope. + * Default behavior is that exceptions are scoped. + */ + boolean scopeExceptions() default true; } diff --git a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java index 637099ac..de33d2c7 100644 --- a/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java +++ b/extensions/throwingproviders/src/com/google/inject/throwingproviders/ThrowingProviderBinder.java @@ -85,6 +85,12 @@ import java.util.Set; */ public class ThrowingProviderBinder { + private static final TypeLiteral<CheckedProvider<?>> CHECKED_PROVIDER_TYPE + = new TypeLiteral<CheckedProvider<?>>() { }; + + private static final TypeLiteral<CheckedProviderMethod<?>> CHECKED_PROVIDER_METHOD_TYPE + = new TypeLiteral<CheckedProviderMethod<?>>() { }; + private final Binder binder; private ThrowingProviderBinder(Binder binder) { @@ -134,6 +140,7 @@ public class ThrowingProviderBinder { private Class<? extends Annotation> annotationType; private Annotation annotation; private Key<P> interfaceKey; + private boolean scopeExceptions = true; public SecondaryBinder(Class<P> interfaceType, Type valueType) { this.interfaceType = checkNotNull(interfaceType, "interfaceType"); @@ -171,6 +178,15 @@ public class ThrowingProviderBinder { return this; } + /** + * Determines if exceptions should be scoped. By default exceptions are scoped. + * @param scopeExceptions whether exceptions should be scoped. + */ + public SecondaryBinder<P, T> scopeExceptions(boolean scopeExceptions) { + this.scopeExceptions = scopeExceptions; + return this; + } + public ScopedBindingBuilder to(P target) { Key<P> targetKey = Key.get(interfaceType, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(target); @@ -236,28 +252,32 @@ public class ThrowingProviderBinder { } }; - Key<CheckedProvider> targetKey = Key.get(CheckedProvider.class, UniqueAnnotations.create()); + Key<CheckedProvider<?>> targetKey = Key.get(CHECKED_PROVIDER_TYPE, + UniqueAnnotations.create()); binder.bind(targetKey).toInstance(checkedProvider); return toInternal(targetKey); } ScopedBindingBuilder toProviderMethod(CheckedProviderMethod<?> target) { - Key<CheckedProviderMethod> targetKey = - Key.get(CheckedProviderMethod.class, UniqueAnnotations.create()); + Key<CheckedProviderMethod<?>> targetKey = + Key.get(CHECKED_PROVIDER_METHOD_TYPE, UniqueAnnotations.create()); binder.bind(targetKey).toInstance(target); return toInternal(targetKey); } + @SuppressWarnings("unchecked") // P only extends the raw type of CheckedProvider public ScopedBindingBuilder to(Key<? extends P> targetKey) { checkNotNull(targetKey, "targetKey"); - return toInternal(targetKey); + return toInternal((Key<? extends CheckedProvider<?>>)targetKey); } - private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider> targetKey) { + private ScopedBindingBuilder toInternal(final Key<? extends CheckedProvider<?>> targetKey) { final Key<Result> resultKey = Key.get(Result.class, UniqueAnnotations.create()); + // Note that this provider will behave like the final provider Guice creates. + // It will especially do scoping if the user adds that. final Provider<Result> resultProvider = binder.getProvider(resultKey); - final Provider<? extends CheckedProvider> targetProvider = binder.getProvider(targetKey); + final Provider<? extends CheckedProvider<?>> targetProvider = binder.getProvider(targetKey); interfaceKey = createKey(); // don't bother binding the proxy type if this is in an invalid state. @@ -272,31 +292,63 @@ public class ThrowingProviderBinder { if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } - return resultProvider.get().getOrThrow(); + + if (scopeExceptions) { + return resultProvider.get().getOrThrow(); + } else { + Result result; + try { + result = resultProvider.get(); + } catch (ProvisionException pe) { + Throwable cause = pe.getCause(); + if (cause instanceof ResultException) { + throw ((ResultException)cause).getCause(); + } else { + throw pe; + } + } + return result.getOrThrow(); + } } })); + @Override public P get() { return instance; } - + + @Override public Set<Dependency<?>> getDependencies() { return ImmutableSet.<Dependency<?>>of(Dependency.get(resultKey)); } }); } - return binder.bind(resultKey).toProvider(new ProviderWithDependencies<Result>() { + // The provider is unscoped, but the user may apply a scope to it through the + // ScopedBindingBuilder this returns. + return binder.bind(resultKey).toProvider( + createResultProvider(targetKey, targetProvider)); + } + + private ProviderWithDependencies<Result> createResultProvider( + final Key<? extends CheckedProvider<?>> targetKey, + final Provider<? extends CheckedProvider<?>> targetProvider) { + return new ProviderWithDependencies<Result>() { + @Override public Result get() { try { return Result.forValue(targetProvider.get().get()); } catch (Exception e) { - for(Class<? extends Throwable> exceptionType : exceptionTypes) { + for (Class<? extends Throwable> exceptionType : exceptionTypes) { if (exceptionType.isInstance(e)) { - return Result.forException(e); + if (scopeExceptions) { + return Result.forException(e); + } else { + throw new ResultException(e); + } } } - + if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { @@ -305,13 +357,14 @@ public class ThrowingProviderBinder { } } } - + + @Override public Set<Dependency<?>> getDependencies() { return ImmutableSet.<Dependency<?>>of(Dependency.get(targetKey)); } - }); + }; } - + /** * Returns the exception type declared to be thrown by the get method of * {@code interfaceType}. @@ -455,10 +508,12 @@ public class ThrowingProviderBinder { } /** - * Represents the returned value from a call to {@link - * CheckedProvider#get()}. This is the value that will be scoped by Guice. + * Represents the returned value from a call to {@link CheckedProvider#get()}. This is the value + * that will be scoped by Guice. */ static class Result implements Serializable { + private static final long serialVersionUID = 0L; + private final Object value; private final Exception exception; @@ -474,7 +529,7 @@ public class ThrowingProviderBinder { public static Result forException(Exception e) { return new Result(null, e); } - + public Object getOrThrow() throws Exception { if (exception != null) { throw exception; @@ -482,8 +537,17 @@ public class ThrowingProviderBinder { return value; } } - - private static final long serialVersionUID = 0L; + } + + /** + * RuntimeException class to wrap exceptions from the checked provider. + * The regular guice provider can throw it and the checked provider proxy extracts + * the underlying exception and rethrows it. + */ + private static class ResultException extends RuntimeException { + ResultException(Exception cause) { + super(cause); + } } private static class NotSyntheticOrBridgePredicate implements Predicate<Method> { diff --git a/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java b/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java index 7a640bea..1f4f9775 100644 --- a/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java +++ b/extensions/throwingproviders/test/com/google/inject/throwingproviders/CheckedProviderTest.java @@ -16,6 +16,9 @@ package com.google.inject.throwingproviders; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -23,6 +26,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.inject.AbstractModule; import com.google.inject.Asserts; +import com.google.inject.BindingAnnotation; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Inject; @@ -45,6 +49,7 @@ import com.google.inject.throwingproviders.ThrowingProviderBinder.Result; import junit.framework.TestCase; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,6 +69,8 @@ import java.util.TooManyListenersException; * @author sameb@google.com (Sam Berlin) */ public class CheckedProviderTest extends TestCase { + @Target(METHOD) @Retention(RUNTIME) @BindingAnnotation + @interface NotExceptionScoping { }; private static final Function<Dependency<?>, Key<?>> DEPENDENCY_TO_KEY = new Function<Dependency<?>, Key<?>>() { @@ -95,6 +102,14 @@ public class CheckedProviderTest extends TestCase { .bind(RemoteProvider.class, Foo.class) .to(mockRemoteProvider) .in(testScope); + + ThrowingProviderBinder.create(binder()) + .bind(RemoteProvider.class, Foo.class) + .annotatedWith(NotExceptionScoping.class) + .scopeExceptions(false) + .to(mockRemoteProvider) + .in(testScope); + } }); @@ -111,6 +126,15 @@ public class CheckedProviderTest extends TestCase { Foo throwOrGet() throws RemoteException, BindException { return mockRemoteProvider.get(); } + + @SuppressWarnings("unused") + @CheckedProvides(value = RemoteProvider.class, scopeExceptions = false) + @NotExceptionScoping + @TestScope.Scoped + Foo notExceptionScopingThrowOrGet() throws RemoteException, BindException { + return mockRemoteProvider.get(); + } + }); cxtorInjector = Guice.createInjector(new AbstractModule() { @@ -120,6 +144,14 @@ public class CheckedProviderTest extends TestCase { .bind(RemoteProvider.class, Foo.class) .providing(MockFoo.class) .in(testScope); + + ThrowingProviderBinder.create(binder()) + .bind(RemoteProvider.class, Foo.class) + .annotatedWith(NotExceptionScoping.class) + .scopeExceptions(false) + .providing(MockFoo.class) + .in(testScope); + } }); } @@ -151,17 +183,28 @@ public class CheckedProviderTest extends TestCase { } public void testValuesScoped_Bind() throws Exception { - tValuesScoped(bindInjector); + tValuesScoped(bindInjector, null); } public void testValuesScoped_Provides() throws Exception { - tValuesScoped(providesInjector); + tValuesScoped(providesInjector, null); } - private void tValuesScoped(Injector injector) throws Exception { - RemoteProvider<Foo> remoteProvider = - injector.getInstance(Key.get(remoteProviderOfFoo)); + public void testValuesScopedWhenNotExceptionScoping_Bind() throws Exception { + tValuesScoped(bindInjector, NotExceptionScoping.class); + } + + public void testValuesScopedWhenNotExceptionScoping_Provides() throws Exception { + tValuesScoped(providesInjector, NotExceptionScoping.class); + } + private void tValuesScoped(Injector injector, + Class<? extends Annotation> annotation) throws Exception { + Key<RemoteProvider<Foo>> key = annotation != null ? + Key.get(remoteProviderOfFoo, annotation) : + Key.get(remoteProviderOfFoo); + RemoteProvider<Foo> remoteProvider = injector.getInstance(key); + mockRemoteProvider.setNextToReturn(new SimpleFoo("A")); assertEquals("A", remoteProvider.get().s()); @@ -218,6 +261,41 @@ public class CheckedProviderTest extends TestCase { } } + public void testExceptionsNotScopedWhenNotExceptionScoping_Bind() throws Exception { + tExceptionsNotScopedWhenNotExceptionScoping(bindInjector); + } + + public void testExceptionsNotScopedWhenNotExceptionScoping_Provides() throws Exception { + tExceptionsNotScopedWhenNotExceptionScoping(providesInjector); + } + + public void testExceptionNotScopedWhenNotExceptionScoping_Cxtor() throws Exception { + tExceptionsNotScopedWhenNotExceptionScoping(cxtorInjector); + } + + private void tExceptionsNotScopedWhenNotExceptionScoping(Injector injector) throws Exception { + RemoteProvider<Foo> remoteProvider = + injector.getInstance(Key.get(remoteProviderOfFoo, NotExceptionScoping.class)); + + mockRemoteProvider.throwOnNextGet(new RemoteException("A")); + MockFoo.nextToThrow = new RemoteException("A"); + try { + remoteProvider.get(); + fail(); + } catch (RemoteException expected) { + assertEquals("A", expected.getMessage()); + } + + mockRemoteProvider.throwOnNextGet(new RemoteException("B")); + MockFoo.nextToThrow = new RemoteException("B"); + try { + remoteProvider.get(); + fail(); + } catch (RemoteException expected) { + assertEquals("B", expected.getMessage()); + } + } + public void testAnnotations_Bind() throws Exception { final MockRemoteProvider<Foo> mockRemoteProviderA = new MockRemoteProvider<Foo>(); final MockRemoteProvider<Foo> mockRemoteProviderB = new MockRemoteProvider<Foo>(); diff --git a/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java b/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java index d56cb7dc..373c68d2 100644 --- a/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java +++ b/extensions/throwingproviders/test/com/google/inject/throwingproviders/ThrowingProviderTest.java @@ -16,11 +16,15 @@ package com.google.inject.throwingproviders; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; +import com.google.inject.BindingAnnotation; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Inject; @@ -37,6 +41,9 @@ import com.google.inject.spi.Message; import junit.framework.TestCase; import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; import java.rmi.AccessException; import java.rmi.RemoteException; import java.util.Arrays; @@ -50,6 +57,8 @@ import java.util.TooManyListenersException; */ @SuppressWarnings("deprecation") public class ThrowingProviderTest extends TestCase { + @Target(METHOD) @Retention(RUNTIME) @BindingAnnotation + @interface NotExceptionScoping { }; private final TypeLiteral<RemoteProvider<String>> remoteProviderOfString = new TypeLiteral<RemoteProvider<String>>() { }; @@ -61,6 +70,13 @@ public class ThrowingProviderTest extends TestCase { .bind(RemoteProvider.class, String.class) .to(mockRemoteProvider) .in(testScope); + + ThrowingProviderBinder.create(binder()) + .bind(RemoteProvider.class, String.class) + .annotatedWith(NotExceptionScoping.class) + .scopeExceptions(false) + .to(mockRemoteProvider) + .in(testScope); } }); private Injector providesInjector = Guice.createInjector(new AbstractModule() { @@ -75,6 +91,14 @@ public class ThrowingProviderTest extends TestCase { String throwOrGet() throws RemoteException { return mockRemoteProvider.get(); } + + @SuppressWarnings("unused") + @CheckedProvides(value = RemoteProvider.class, scopeExceptions = false) + @NotExceptionScoping + @TestScope.Scoped + String notExceptionScopingThrowOrGet() throws RemoteException { + return mockRemoteProvider.get(); + } }); public void testExceptionsThrown_Bind() { @@ -99,16 +123,27 @@ public class ThrowingProviderTest extends TestCase { } public void testValuesScoped_Bind() throws RemoteException { - tValuesScoped(bindInjector); + tValuesScoped(bindInjector, null); } public void testValuesScoped_Provides() throws RemoteException { - tValuesScoped(providesInjector); + tValuesScoped(providesInjector, null); } - private void tValuesScoped(Injector injector) throws RemoteException { - RemoteProvider<String> remoteProvider = - injector.getInstance(Key.get(remoteProviderOfString)); + public void testValuesScopedWhenNotExceptionScoping_Bind() throws RemoteException { + tValuesScoped(bindInjector, NotExceptionScoping.class); + } + + public void testValuesScopedWhenNotExceptionScoping_Provides() throws RemoteException { + tValuesScoped(providesInjector, NotExceptionScoping.class); + } + + private void tValuesScoped(Injector injector, Class<? extends Annotation> annotation) + throws RemoteException { + Key<RemoteProvider<String>> key = annotation != null ? + Key.get(remoteProviderOfString, annotation) : + Key.get(remoteProviderOfString); + RemoteProvider<String> remoteProvider = injector.getInstance(key); mockRemoteProvider.setNextToReturn("A"); assertEquals("A", remoteProvider.get()); @@ -148,6 +183,35 @@ public class ThrowingProviderTest extends TestCase { assertEquals("A", expected.getMessage()); } } + + public void testExceptionsNotScopedWhenNotExceptionScoping_Bind() { + tExceptionsNotScopedWhenNotExceptionScoping(bindInjector); + } + + public void testExceptionsNotScopedWhenNotExceptionScoping_Provides() { + tExceptionsNotScopedWhenNotExceptionScoping(providesInjector); + } + + private void tExceptionsNotScopedWhenNotExceptionScoping(Injector injector) { + RemoteProvider<String> remoteProvider = + injector.getInstance(Key.get(remoteProviderOfString, NotExceptionScoping.class)); + + mockRemoteProvider.throwOnNextGet("A"); + try { + remoteProvider.get(); + fail(); + } catch (RemoteException expected) { + assertEquals("A", expected.getMessage()); + } + + mockRemoteProvider.throwOnNextGet("B"); + try { + remoteProvider.get(); + fail(); + } catch (RemoteException expected) { + assertEquals("B", expected.getMessage()); + } + } public void testAnnotations_Bind() throws RemoteException { final MockRemoteProvider<String> mockRemoteProviderA = new MockRemoteProvider<String>(); |