Revision: 7069 Author: b...@google.com Date: Fri Nov 20 10:37:09 2009 Log: Merge tr...@7049,7065 to better handle RuntimeExceptions throw by deRPC service methods. $ svn merge --ignore-ancestry -c7049,7065 https://google-web-toolkit.googlecode.com/svn/trunk .
http://code.google.com/p/google-web-toolkit/source/detail?r=7069 Modified: /releases/2.0/branch-info.txt /releases/2.0/user/src/com/google/gwt/rpc/server/RPC.java /releases/2.0/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java /releases/2.0/user/src/com/google/gwt/user/server/rpc/RPC.java /releases/2.0/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestService.java /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestServiceAsync.java /releases/2.0/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTestServiceImplBase.java ======================================= --- /releases/2.0/branch-info.txt Fri Nov 20 09:51:31 2009 +++ /releases/2.0/branch-info.txt Fri Nov 20 10:37:09 2009 @@ -870,3 +870,7 @@ Update IE installer to run without admin privileges svn merge --ignore-ancestry -c7066 https://google-web-toolkit.googlecode.com/svn/trunk . +tr...@7049,7065 were merged into this branch + Ensures correct handling of (unknown) RuntimeExceptions thrown from deRPC service methods. + svn merge --ignore-ancestry -c7049,7065 https://google-web-toolkit.googlecode.com/svn/trunk . + ======================================= --- /releases/2.0/user/src/com/google/gwt/rpc/server/RPC.java Mon Nov 16 10:54:36 2009 +++ /releases/2.0/user/src/com/google/gwt/rpc/server/RPC.java Fri Nov 20 10:37:09 2009 @@ -26,6 +26,8 @@ import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.server.rpc.RPCRequest; +import com.google.gwt.user.server.rpc.RPCServletUtils; +import com.google.gwt.user.server.rpc.UnexpectedException; import java.io.IOException; import java.io.OutputStream; @@ -210,8 +212,14 @@ throw securityException; } catch (InvocationTargetException e) { // Try to encode the caught exception - // Throwable cause = e.getCause(); + + // Don't allow random RuntimeExceptions to be thrown back to the client + if (!RPCServletUtils.isExpectedException(serviceMethod, cause)) { + throw new UnexpectedException("Service method '" + + getSourceRepresentation(serviceMethod) + + "' threw an unexpected exception: " + cause.toString(), cause); + } streamResponse(clientOracle, cause, sink, true); } ======================================= --- /releases/2.0/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java Wed Nov 4 07:16:01 2009 +++ /releases/2.0/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java Fri Nov 20 10:37:09 2009 @@ -94,6 +94,18 @@ * @param e the exception which was thrown */ protected void doUnexpectedFailure(Throwable e) { + try { + getThreadLocalResponse().reset(); + } catch (IllegalStateException ex) { + /* + * If we can't reset the request, the only way to signal that something + * has gone wrong is to throw an exception from here. It should be the + * case that we call the user's implementation code before emitting data + * into the response, so the only time that gets tripped is if the object + * serialization code blows up. + */ + throw new RuntimeException("Unable to report failure", e); + } ServletContext servletContext = getServletContext(); RPCServletUtils.writeResponseForUnexpectedFailure(servletContext, getThreadLocalResponse(), e); ======================================= --- /releases/2.0/user/src/com/google/gwt/user/server/rpc/RPC.java Mon Jun 15 14:00:16 2009 +++ /releases/2.0/user/src/com/google/gwt/user/server/rpc/RPC.java Fri Nov 20 10:37:09 2009 @@ -48,7 +48,9 @@ * <h3>Advanced Example</h3> The following example shows a more advanced way of * using this class to create an adapter between GWT RPC entities and POJOs. * - * {...@example com.google.gwt.examples.rpc.server.AdvancedExample#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)} + * {...@example + * com.google.gwt.examples.rpc.server.AdvancedExample#doPost(javax.servlet.http. + * HttpServletRequest, javax.servlet.http.HttpServletResponse)} */ public final class RPC { @@ -270,7 +272,8 @@ int paramCount = streamReader.readInt(); if (paramCount > streamReader.getNumberOfTokens()) { - throw new IncompatibleRemoteServiceException("Invalid number of parameters"); + throw new IncompatibleRemoteServiceException( + "Invalid number of parameters"); } Class<?>[] parameterTypes = new Class[paramCount]; @@ -372,7 +375,8 @@ throw new NullPointerException("serializationPolicy"); } - if (serviceMethod != null && !RPC.isExpectedException(serviceMethod, cause)) { + if (serviceMethod != null + && !RPCServletUtils.isExpectedException(serviceMethod, cause)) { throw new UnexpectedException("Service method '" + getSourceRepresentation(serviceMethod) + "' threw an unexpected exception: " + cause.toString(), cause); @@ -774,42 +778,6 @@ return true; } } - - return false; - } - - /** - * Returns true if the {...@link java.lang.reflect.Method Method} definition on - * the service is specified to throw the exception contained in the - * InvocationTargetException or false otherwise. NOTE we do not check that the - * type is serializable here. We assume that it must be otherwise the - * application would never have been allowed to run. - * - * @param serviceIntfMethod the method from the RPC request - * @param cause the exception that the method threw - * @return true if the exception's type is in the method's signature - */ - private static boolean isExpectedException(Method serviceIntfMethod, - Throwable cause) { - assert (serviceIntfMethod != null); - assert (cause != null); - - Class<?>[] exceptionsThrown = serviceIntfMethod.getExceptionTypes(); - if (exceptionsThrown.length <= 0) { - // The method is not specified to throw any exceptions - // - return false; - } - - Class<? extends Throwable> causeType = cause.getClass(); - - for (Class<?> exceptionThrown : exceptionsThrown) { - assert (exceptionThrown != null); - - if (exceptionThrown.isAssignableFrom(causeType)) { - return true; - } - } return false; } ======================================= --- /releases/2.0/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java Mon Jul 6 16:17:17 2009 +++ /releases/2.0/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java Fri Nov 20 10:37:09 2009 @@ -18,6 +18,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Method; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletContext; @@ -87,6 +88,42 @@ public static boolean exceedsUncompressedContentLengthLimit(String content) { return (content.length() * 2) > UNCOMPRESSED_BYTE_SIZE_LIMIT; } + + /** + * Returns true if the {...@link java.lang.reflect.Method Method} definition on + * the service is specified to throw the exception contained in the + * InvocationTargetException or false otherwise. NOTE we do not check that the + * type is serializable here. We assume that it must be otherwise the + * application would never have been allowed to run. + * + * @param serviceIntfMethod the method from the RPC request + * @param cause the exception that the method threw + * @return true if the exception's type is in the method's signature + */ + public static boolean isExpectedException(Method serviceIntfMethod, + Throwable cause) { + assert (serviceIntfMethod != null); + assert (cause != null); + + Class<?>[] exceptionsThrown = serviceIntfMethod.getExceptionTypes(); + if (exceptionsThrown.length <= 0) { + // The method is not specified to throw any exceptions + // + return false; + } + + Class<? extends Throwable> causeType = cause.getClass(); + + for (Class<?> exceptionThrown : exceptionsThrown) { + assert (exceptionThrown != null); + + if (exceptionThrown.isAssignableFrom(causeType)) { + return true; + } + } + + return false; + } /** * Returns the content of an {...@link HttpServletRequest} by decoding it using @@ -258,7 +295,12 @@ try { response.setContentType("text/plain"); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.getWriter().write(GENERIC_FAILURE_MSG); + try { + response.getOutputStream().write(GENERIC_FAILURE_MSG.getBytes("UTF-8")); + } catch (IllegalStateException e) { + // Handle the (unexpected) case where getWriter() was previously used + response.getWriter().write(GENERIC_FAILURE_MSG); + } } catch (IOException ex) { servletContext.log( "respondWithUnexpectedFailure failed while sending the previous failure to the client", ======================================= --- /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java Wed Nov 4 13:11:32 2009 +++ /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java Fri Nov 20 10:37:09 2009 @@ -206,4 +206,49 @@ }); assertTrue(req.isPending()); } -} + + /** + * Verify behavior when the RPC method throws a RuntimeException declared on + * the RemoteService interface. + */ + public void testDeclaredRuntimeException() { + RemoteServiceServletTestServiceAsync service = getAsyncService(); + + delayTestFinishForRpc(); + + service.throwDeclaredRuntimeException(new AsyncCallback<Void>() { + + public void onFailure(Throwable caught) { + assertTrue(caught instanceof NullPointerException); + assertEquals("expected", caught.getMessage()); + finishTest(); + } + + public void onSuccess(Void result) { + fail(); + } + }); + } + + /** + * Verify behavior when the RPC method throws an unknown RuntimeException + * (possibly one unknown to the client). + */ + public void testUnknownRuntimeException() { + RemoteServiceServletTestServiceAsync service = getAsyncService(); + + delayTestFinishForRpc(); + + service.throwUnknownRuntimeException(new AsyncCallback<Void>() { + + public void onFailure(Throwable caught) { + assertTrue(caught instanceof InvocationException); + finishTest(); + } + + public void onSuccess(Void result) { + fail(); + } + }); + } +} ======================================= --- /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestService.java Thu Mar 5 09:29:25 2009 +++ /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestService.java Fri Nov 20 10:37:09 2009 @@ -25,4 +25,8 @@ void testExpectCustomHeader(); void testExpectPermutationStrongName(String expectedStrongName); -} + + void throwDeclaredRuntimeException() throws NullPointerException; + + void throwUnknownRuntimeException(); +} ======================================= --- /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestServiceAsync.java Thu Mar 5 09:29:25 2009 +++ /releases/2.0/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTestServiceAsync.java Fri Nov 20 10:37:09 2009 @@ -28,4 +28,8 @@ void testExpectPermutationStrongName(String expectedStrongName, AsyncCallback<Void> callback); -} + + void throwDeclaredRuntimeException(AsyncCallback<Void> callback); + + void throwUnknownRuntimeException(AsyncCallback<Void> callback); +} ======================================= --- /releases/2.0/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTestServiceImplBase.java Mon Jul 6 16:17:17 2009 +++ /releases/2.0/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTestServiceImplBase.java Fri Nov 20 10:37:09 2009 @@ -25,6 +25,15 @@ */ public class RemoteServiceServletTestServiceImplBase extends HybridServiceServlet implements RemoteServiceServletTestService { + + /** + * A RuntimeException the client code shouldn't know anything about. + */ + public static class FooException extends RuntimeException { + public FooException() { + super("This is OK. Simulating random backend code exception."); + } + } public void test() { } @@ -46,4 +55,12 @@ + getPermutationStrongName()); } } -} + + public void throwDeclaredRuntimeException() { + throw new NullPointerException("expected"); + } + + public void throwUnknownRuntimeException() { + throw new FooException(); + } +} -- http://groups.google.com/group/Google-Web-Toolkit-Contributors