Integrate RequestFactory Receiver exception handling fixes into 2.2 branch. git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.2@9545 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java index cf713be..b215b4f 100644 --- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java +++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
@@ -47,18 +47,20 @@ } public void send(String payload, TransportReceiver receiver) { + String result; try { if (DUMP_PAYLOAD) { System.out.println(">>> " + payload); } - String result = processor.process(payload); + result = processor.process(payload); if (DUMP_PAYLOAD) { System.out.println("<<< " + result); } - receiver.onTransportSuccess(result); } catch (RuntimeException e) { e.printStackTrace(); receiver.onTransportFailure(new ServerFailure(e.getMessage())); + return; } + receiver.onTransportSuccess(result); } }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java index 3027693..4f7e01d 100644 --- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java +++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -24,6 +24,7 @@ import com.google.gwt.autobean.shared.AutoBeanVisitor; import com.google.gwt.autobean.shared.Splittable; import com.google.gwt.autobean.shared.ValueCodex; +import com.google.gwt.event.shared.UmbrellaException; import com.google.gwt.requestfactory.shared.BaseProxy; import com.google.gwt.requestfactory.shared.EntityProxy; import com.google.gwt.requestfactory.shared.EntityProxyChange; @@ -628,14 +629,7 @@ errors.add(new MyViolation(message)); } - reuse(); - for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>( - invocations)) { - request.onViolation(errors); - } - if (receiver != null) { - receiver.onViolation(errors); - } + violation(receiver, errors); return; } @@ -643,37 +637,115 @@ processReturnOperations(response); // Send return values + Set<Throwable> causes = null; for (int i = 0, j = invocations.size(); i < j; i++) { - if (response.getStatusCodes().get(i)) { - invocations.get(i).onSuccess(response.getInvocationResults().get(i)); - } else { - ServerFailureMessage failure = AutoBeanCodex.decode( - MessageFactoryHolder.FACTORY, ServerFailureMessage.class, - response.getInvocationResults().get(i)).as(); - invocations.get(i).onFail( - new ServerFailure(failure.getMessage(), - failure.getExceptionType(), failure.getStackTrace(), - failure.isFatal())); + try { + if (response.getStatusCodes().get(i)) { + invocations.get(i).onSuccess( + response.getInvocationResults().get(i)); + } else { + ServerFailureMessage failure = AutoBeanCodex.decode( + MessageFactoryHolder.FACTORY, ServerFailureMessage.class, + response.getInvocationResults().get(i)).as(); + invocations.get(i).onFail( + new ServerFailure(failure.getMessage(), + failure.getExceptionType(), failure.getStackTrace(), + failure.isFatal())); + } + } catch (Throwable t) { + if (causes == null) { + causes = new HashSet<Throwable>(); + } + causes.add(t); } } if (receiver != null) { - receiver.onSuccess(null); + try { + receiver.onSuccess(null); + } catch (Throwable t) { + if (causes == null) { + causes = new HashSet<Throwable>(); + } + causes.add(t); + } } // After success, shut down the context editedProxies.clear(); invocations.clear(); returnedProxies.clear(); + + if (causes != null) { + throw new UmbrellaException(causes); + } } + /** + * Invoke the appropriate {@code onFailure} callbacks, possibly throwing + * an {@link UmbrellaException} if one or more callbacks fails. + */ private void fail(Receiver<Void> receiver, ServerFailure failure) { reuse(); + Set<Throwable> causes = null; for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>( invocations)) { - request.onFail(failure); + try { + request.onFail(failure); + } catch (Throwable t) { + if (causes == null) { + causes = new HashSet<Throwable>(); + } + causes.add(t); + } } if (receiver != null) { - receiver.onFailure(failure); + try { + receiver.onFailure(failure); + } catch (Throwable t) { + if (causes == null) { + causes = new HashSet<Throwable>(); + } + causes.add(t); + } + } + + if (causes != null) { + throw new UmbrellaException(causes); + } + } + + /** + * Invoke the appropriate {@code onViolation} callbacks, possibly throwing + * an {@link UmbrellaException} if one or more callbacks fails. + */ + private void violation(final Receiver<Void> receiver, + Set<Violation> errors) { + reuse(); + Set<Throwable> causes = null; + for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>( + invocations)) { + try { + request.onViolation(errors); + } catch (Throwable t) { + if (causes == null) { + causes = new HashSet<Throwable>(); + } + causes.add(t); + } + } + if (receiver != null) { + try { + receiver.onViolation(errors); + } catch (Throwable t) { + if (causes == null) { + causes = new HashSet<Throwable>(); + } + causes.add(t); + } + } + + if (causes != null) { + throw new UmbrellaException(causes); } } });
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java index 85d7956..b203b56 100644 --- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java +++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -20,6 +20,7 @@ import com.google.gwt.requestfactory.server.ComplexKeysJreTest; import com.google.gwt.requestfactory.server.FindServiceJreTest; import com.google.gwt.requestfactory.server.LocatorJreTest; +import com.google.gwt.requestfactory.server.RequestFactoryExceptionPropagationJreTest; import com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest; import com.google.gwt.requestfactory.server.RequestFactoryJreTest; import com.google.gwt.requestfactory.server.RequestFactoryUnicodeEscapingJreTest; @@ -39,11 +40,12 @@ suite.addTestSuite(ComplexKeysJreTest.class); suite.addTestSuite(FindServiceJreTest.class); suite.addTestSuite(LocatorJreTest.class); - suite.addTestSuite(RequestFactoryJreTest.class); - suite.addTestSuite(SimpleEntityProxyIdTest.class); + suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class); suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class); + suite.addTestSuite(RequestFactoryJreTest.class); suite.addTestSuite(RequestFactoryModelTest.class); suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class); + suite.addTestSuite(SimpleEntityProxyIdTest.class); return suite; } }
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java index 72cad85..6e8311b 100644 --- a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java +++ b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
@@ -18,6 +18,7 @@ import com.google.gwt.junit.tools.GWTTestSuite; import com.google.gwt.requestfactory.client.FindServiceTest; import com.google.gwt.requestfactory.client.RequestFactoryExceptionHandlerTest; +import com.google.gwt.requestfactory.client.RequestFactoryExceptionPropagationTest; import com.google.gwt.requestfactory.client.RequestFactoryPolymorphicTest; import com.google.gwt.requestfactory.client.RequestFactoryTest; import com.google.gwt.requestfactory.client.RequestFactoryUnicodeEscapingTest; @@ -42,6 +43,7 @@ suite.addTestSuite(LocatorTest.class); suite.addTestSuite(RequestFactoryTest.class); suite.addTestSuite(RequestFactoryExceptionHandlerTest.class); + suite.addTestSuite(RequestFactoryExceptionPropagationTest.class); suite.addTestSuite(RequestFactoryPolymorphicTest.class); suite.addTestSuite(RequestFactoryUnicodeEscapingTest.class); return suite;
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryExceptionPropagationTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryExceptionPropagationTest.java new file mode 100644 index 0000000..dbeb250 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryExceptionPropagationTest.java
@@ -0,0 +1,308 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.shared.UmbrellaException; +import com.google.gwt.requestfactory.shared.Receiver; +import com.google.gwt.requestfactory.shared.RequestContext; +import com.google.gwt.requestfactory.shared.ServerFailure; +import com.google.gwt.requestfactory.shared.SimpleFooProxy; +import com.google.gwt.requestfactory.shared.SimpleFooRequest; +import com.google.gwt.requestfactory.shared.Violation; + +import java.util.Set; + +/** + * Tests that an exception thrown by a {@link Receiver} does not prevent other + * {@link Receiver}s from being called. + */ +public class RequestFactoryExceptionPropagationTest extends + RequestFactoryTestBase { + /* + * DO NOT USE finishTest(). Instead, call finishTestAndReset(); + */ + + private class CountingReceiver extends Receiver<Object> { + int failureCallCount; + int successCallCount; + int violationCallCount; + + public void assertCounts(int expectedFailureCallCount, + int expectedSuccessCallCount, int expectedViolationCallCount) { + assertEquals(expectedFailureCallCount, failureCallCount); + assertEquals(expectedSuccessCallCount, successCallCount); + assertEquals(expectedViolationCallCount, violationCallCount); + } + + @Override + public void onFailure(ServerFailure error) { + failureCallCount++; + } + + @Override + public void onSuccess(Object response) { + successCallCount++; + } + + @Override + public void onViolation(Set<Violation> errors) { + violationCallCount++; + } + } + + private class ThrowingReceiver<T> extends Receiver<T> { + private final RuntimeException e; + + public ThrowingReceiver(RuntimeException e) { + this.e = e; + } + + @Override + public void onFailure(ServerFailure error) { + throw e; + } + + @Override + public void onSuccess(T response) { + throw e; + } + + @Override + public void onViolation(Set<Violation> errors) { + throw e; + } + } + + private static final int DELAY_TEST_FINISH = 10 * 1000; + + GWT.UncaughtExceptionHandler defaultUncaughtExceptionHandler; + + @Override + public String getModuleName() { + return "com.google.gwt.requestfactory.RequestFactorySuite"; + } + + @Override + public void gwtSetUp() { + super.gwtSetUp(); + + defaultUncaughtExceptionHandler = GWT.getUncaughtExceptionHandler(); + } + + /** + * Test mixed invocation failure and success. One receiver will throw from + * onSuccess and another from onFailure. Other receivers onSuccess and + * onFailure will corectly be called. + */ + public void testMixedSuccessAndFailureThrow() { + delayTestFinish(DELAY_TEST_FINISH); + + final RuntimeException exception1 = new RuntimeException("first exception"); + final RuntimeException exception2 = new RuntimeException("second exception"); + final CountingReceiver count = new CountingReceiver(); + + SimpleFooRequest context = req.simpleFooRequest(); + // 42 is the crash causing magic number for a runtime exception + context.pleaseCrash(42).to(count); + context.returnNullString().to(new ThrowingReceiver<String>(exception1)); + context.returnNullString().to(count); + + fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), + new GWT.UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { + if (e instanceof UmbrellaException) { + count.assertCounts(1, 1, 0); + + Set<Throwable> causes = ((UmbrellaException) e).getCauses(); + assertEquals(2, causes.size()); + assertTrue(causes.contains(exception1)); + assertTrue(causes.contains(exception2)); + + finishTestAndReset(); + } else { + defaultUncaughtExceptionHandler.onUncaughtException(e); + } + } + }); + } + + /** + * Test invocation failure. Other invocations' onSuccess should correctly be + * called. + */ + public void testOnFailureThrow() { + delayTestFinish(DELAY_TEST_FINISH); + + final RuntimeException exception1 = new RuntimeException("first exception"); + final RuntimeException exception2 = new RuntimeException("second exception"); + final CountingReceiver count = new CountingReceiver(); + + SimpleFooRequest context = req.simpleFooRequest(); + context.returnNullString().to(count); + // 42 is the crash causing magic number for a runtime exception + context.pleaseCrash(42).to(new ThrowingReceiver<Void>(exception1)); + context.returnNullString().to(count); + + fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), + new GWT.UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { + if (e instanceof UmbrellaException) { + count.assertCounts(0, 2, 0); + + Set<Throwable> causes = ((UmbrellaException) e).getCauses(); + assertEquals(2, causes.size()); + assertTrue(causes.contains(exception1)); + assertTrue(causes.contains(exception2)); + + finishTestAndReset(); + } else { + defaultUncaughtExceptionHandler.onUncaughtException(e); + } + } + }); + } + + /** + * Test global failure. All receivers will have their onFailure called, and + * some of them will throw. + */ + public void testOnGlobalFailureThrow() { + delayTestFinish(DELAY_TEST_FINISH); + + final RuntimeException exception1 = new RuntimeException("first exception"); + final RuntimeException exception2 = new RuntimeException("second exception"); + final CountingReceiver count = new CountingReceiver(); + + SimpleFooRequest context = req.simpleFooRequest(); + SimpleFooProxy newFoo = context.create(SimpleFooProxy.class); + + context.returnNullString().to(count); + context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1)); + context.returnNullString().to(count); + + final SimpleFooProxy mutableFoo = context.edit(newFoo); + // 42 is the crash causing magic number for a runtime exception + mutableFoo.setPleaseCrash(42); + + fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), + new GWT.UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { + if (e instanceof UmbrellaException) { + count.assertCounts(2, 0, 0); + + Set<Throwable> causes = ((UmbrellaException) e).getCauses(); + assertEquals(2, causes.size()); + assertTrue(causes.contains(exception1)); + assertTrue(causes.contains(exception2)); + + finishTestAndReset(); + } else { + defaultUncaughtExceptionHandler.onUncaughtException(e); + } + } + }); + } + + /** + * All receivers will have their onSuccess called, and some of them will + * throw. + */ + public void testOnSuccessThrow() { + delayTestFinish(DELAY_TEST_FINISH); + + final RuntimeException exception1 = new RuntimeException("first exception"); + final RuntimeException exception2 = new RuntimeException("second exception"); + final CountingReceiver count = new CountingReceiver(); + + SimpleFooRequest context = req.simpleFooRequest(); + context.returnNullString().to(count); + context.returnNullString().to(new ThrowingReceiver<String>(exception1)); + context.returnNullString().to(count); + + fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), + new GWT.UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { + if (e instanceof UmbrellaException) { + count.assertCounts(0, 2, 0); + + Set<Throwable> causes = ((UmbrellaException) e).getCauses(); + assertEquals(2, causes.size()); + assertTrue(causes.contains(exception1)); + assertTrue(causes.contains(exception2)); + + finishTestAndReset(); + } else { + defaultUncaughtExceptionHandler.onUncaughtException(e); + } + } + }); + } + + /** + * Test violations. All receivers will have their onViolation called, and some + * of them will throw. + */ + public void testOnViolationThrow() { + delayTestFinish(DELAY_TEST_FINISH); + + final RuntimeException exception1 = new RuntimeException("first exception"); + final RuntimeException exception2 = new RuntimeException("second exception"); + final CountingReceiver count = new CountingReceiver(); + + SimpleFooRequest context = req.simpleFooRequest(); + SimpleFooProxy newFoo = context.create(SimpleFooProxy.class); + newFoo.setUserName("a"); // too short + + context.returnNullString().to(count); + context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1)); + context.returnNullString().to(count); + + fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), + new GWT.UncaughtExceptionHandler() { + public void onUncaughtException(Throwable e) { + if (e instanceof UmbrellaException) { + count.assertCounts(0, 0, 2); + + Set<Throwable> causes = ((UmbrellaException) e).getCauses(); + assertEquals(2, causes.size()); + assertTrue(causes.contains(exception1)); + assertTrue(causes.contains(exception2)); + + finishTestAndReset(); + } else { + defaultUncaughtExceptionHandler.onUncaughtException(e); + } + } + }); + } + + protected void fireContextAndCatch(RequestContext context, + Receiver<Void> receiver, GWT.UncaughtExceptionHandler exceptionHandler) { + GWT.setUncaughtExceptionHandler(exceptionHandler); + + if (receiver == null) { + context.fire(); + } else { + context.fire(receiver); + } + } + + @Override + protected void gwtTearDown() throws Exception { + GWT.setUncaughtExceptionHandler(defaultUncaughtExceptionHandler); + } +}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java new file mode 100644 index 0000000..49b5652 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java
@@ -0,0 +1,52 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.server; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.requestfactory.client.RequestFactoryExceptionPropagationTest; +import com.google.gwt.requestfactory.shared.Receiver; +import com.google.gwt.requestfactory.shared.RequestContext; +import com.google.gwt.requestfactory.shared.SimpleRequestFactory; + +/** + * JRE version of {@link RequestFactoryExceptionPropagationTest}. + */ +public class RequestFactoryExceptionPropagationJreTest extends + RequestFactoryExceptionPropagationTest { + @Override + public String getModuleName() { + return null; + } + + @Override + protected SimpleRequestFactory createFactory() { + return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class); + } + + @Override + protected void fireContextAndCatch(RequestContext context, + Receiver<Void> receiver, GWT.UncaughtExceptionHandler exceptionHandler) { + try { + if (receiver == null) { + context.fire(); + } else { + context.fire(receiver); + } + } catch (Throwable e) { + exceptionHandler.onUncaughtException(e); + } + } +}
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java index be2632b..dad490c 100644 --- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java +++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -170,6 +170,16 @@ return foo; } + public static void pleaseCrash(Integer crashIf42or43) throws Exception { + if (crashIf42or43 == 42) { + throw new UnsupportedOperationException( + "THIS EXCEPTION IS EXPECTED BY A TEST"); + } + if (crashIf42or43 == 43) { + throw new Exception("THIS EXCEPTION IS EXPECTED BY A TEST"); + } + } + /** * Check client-side upcasting to BigDecimal and return a list of BigDecimals * that should be upcast. @@ -754,13 +764,7 @@ } public void setPleaseCrash(Integer crashIf42or43) throws Exception { - if (crashIf42or43 == 42) { - throw new UnsupportedOperationException( - "THIS EXCEPTION IS EXPECTED BY A TEST"); - } - if (crashIf42or43 == 43) { - throw new Exception("THIS EXCEPTION IS EXPECTED BY A TEST"); - } + pleaseCrash(crashIf42or43); pleaseCrash = crashIf42or43; }
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java index a27ad31..8fcc205 100644 --- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java +++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -50,7 +50,7 @@ Request<Set<Integer>> getNumberSet(); Request<SimpleFooProxy> getSimpleFooWithNullVersion(); - + Request<SimpleFooProxy> getSimpleFooWithSubPropertyCollection(); Request<SimpleFooProxy> getTripletReference(); @@ -65,10 +65,12 @@ InstanceRequest<SimpleFooProxy, SimpleFooProxy> persistCascadingAndReturnSelf(); - Request<List<BigInteger>> processBigIntegerList(List<BigInteger> values); + Request<Void> pleaseCrash(Integer crashIf42or43); Request<List<BigDecimal>> processBigDecimalList(List<BigDecimal> values); + Request<List<BigInteger>> processBigIntegerList(List<BigInteger> values); + Request<Boolean> processBooleanList(List<Boolean> values); Request<List<Date>> processDateList(List<Date> values);