Fixes issue #1297 by implementing a whitelist approach to types the server may serialize. The whitelist is generated by the compile process and must be included on the server. RemoteServiceServlet will attempt to load this file through ServletContext.getResource(). A failure to load the whilelist will result in 1.3.3 compatible behavior where java.io.Serializable is not considered a valid marker interface. This is to prevent a malicious client from causing a server to instantiable artibtrary types extending java.io.Serializable.
Patch by: mmendez, me
Review by: me, mmendez
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1302 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
index 4a56d0d..960c4ba 100644
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -36,6 +36,7 @@
import java.util.Iterator;
import java.util.Map;
+import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -82,7 +83,7 @@
private final Map loadedModulesByName = new HashMap();
- private final Map loadedServletsByClassName = new HashMap();
+ private final Map loadedServletsByModuleAndClassName = new HashMap();
private final Map mimeTypes = new HashMap();
@@ -171,6 +172,7 @@
}
String servletClassName = null;
+ ModuleDef moduleDef = null;
try {
// Attempt to split the URL into module/path, which we'll use to see
@@ -182,7 +184,7 @@
// we're only looking for servlet invocations, which can only happen
// when we have *already* loaded the destination module to serve up the
// client code in the first place.
- ModuleDef moduleDef = (ModuleDef) loadedModulesByName.get(parts.moduleName);
+ moduleDef = (ModuleDef) loadedModulesByName.get(parts.moduleName);
if (moduleDef != null) {
// Okay, we know this module. Do we know this servlet path?
// It is right to prepend the slash because (1) ModuleDefSchema requires
@@ -204,7 +206,7 @@
// Try to map a bare path that isn't preceded by the module name.
// This is no longer the recommended practice, so we warn.
String path = request.getPathInfo();
- ModuleDef moduleDef = (ModuleDef) modulesByServletPath.get(path);
+ moduleDef = (ModuleDef) modulesByServletPath.get(path);
if (moduleDef != null) {
// See if there is a servlet we can delegate to for the given url.
servletClassName = moduleDef.findServletForPath(path);
@@ -235,7 +237,8 @@
// Load/get the servlet if we found one.
if (servletClassName != null) {
- HttpServlet delegatee = tryGetOrLoadServlet(logger, servletClassName);
+ HttpServlet delegatee = tryGetOrLoadServlet(logger, moduleDef,
+ servletClassName);
if (delegatee == null) {
logger.log(TreeLogger.ERROR, "Unable to dispatch request", null);
sendErrorResponse(response,
@@ -853,9 +856,11 @@
}
}
- private HttpServlet tryGetOrLoadServlet(TreeLogger logger, String className) {
- synchronized (loadedServletsByClassName) {
- HttpServlet servlet = (HttpServlet) loadedServletsByClassName.get(className);
+ private HttpServlet tryGetOrLoadServlet(TreeLogger logger,
+ ModuleDef moduleDef, String className) {
+ synchronized (loadedServletsByModuleAndClassName) {
+ String moduleAndClassName = moduleDef.getName() + "/" + className;
+ HttpServlet servlet = (HttpServlet) loadedServletsByModuleAndClassName.get(moduleAndClassName);
if (servlet != null) {
// Found it.
//
@@ -879,9 +884,18 @@
//
servlet = (HttpServlet) newInstance;
- servlet.init(getServletConfig());
+ // We create proxies for ServletContext and ServletConfig to enable
+ // RemoteServiceServlets to load public and generated resources via
+ // ServeletContext.getResourceAsStream()
+ //
+ ServletContext context = new HostedModeServletContextProxy(
+ getServletContext(), moduleDef, getOutputDir());
+ ServletConfig config = new HostedModeServletConfigProxy(
+ getServletConfig(), context);
- loadedServletsByClassName.put(className, servlet);
+ servlet.init(config);
+
+ loadedServletsByModuleAndClassName.put(moduleAndClassName, servlet);
return servlet;
} catch (ClassNotFoundException e) {
caught = e;
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletConfigProxy.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletConfigProxy.java
new file mode 100644
index 0000000..d2a1d92
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletConfigProxy.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2007 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.dev.shell;
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * {@link ServletConfig} proxy which ensures that an un-proxied
+ * {@link ServletContext} is never returned to a servlet in hosted mode.
+ */
+class HostedModeServletConfigProxy implements ServletConfig {
+ private final ServletConfig config;
+ private final ServletContext context;
+
+ public HostedModeServletConfigProxy(ServletConfig config,
+ ServletContext context) {
+ this.config = config;
+ this.context = context;
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletConfig#getInitParameter(java.lang.String)
+ */
+ public String getInitParameter(String arg0) {
+ return config.getInitParameter(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletConfig#getInitParameterNames()
+ */
+ public Enumeration getInitParameterNames() {
+ return config.getInitParameterNames();
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletConfig#getServletContext()
+ */
+ public ServletContext getServletContext() {
+ return context;
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletConfig#getServletName()
+ */
+ public String getServletName() {
+ return config.getServletName();
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
new file mode 100644
index 0000000..10151a1
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2007 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.dev.shell;
+
+import com.google.gwt.dev.cfg.ModuleDef;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Set;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+/**
+ * ServletContext proxy that implements the getResource and getResourceAsStream
+ * members so that they can work with the {@link GWTShellServlet}.
+ */
+class HostedModeServletContextProxy implements ServletContext {
+ private final ServletContext context;
+ private final ModuleDef moduleDef;
+ private final File outputDir;
+
+ HostedModeServletContextProxy(ServletContext context, ModuleDef moduleDef,
+ File outputDir) {
+ this.context = context;
+ this.moduleDef = moduleDef;
+ this.outputDir = outputDir;
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String arg0) {
+ return context.getAttribute(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getAttributeNames()
+ */
+ public Enumeration getAttributeNames() {
+ return context.getAttributeNames();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getContext(java.lang.String)
+ */
+ public ServletContext getContext(String arg0) {
+ return context.getContext(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+ */
+ public String getInitParameter(String arg0) {
+ return context.getInitParameter(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getInitParameterNames()
+ */
+ public Enumeration getInitParameterNames() {
+ return context.getInitParameterNames();
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getMajorVersion()
+ */
+ public int getMajorVersion() {
+ return context.getMajorVersion();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+ */
+ public String getMimeType(String arg0) {
+ return context.getMimeType(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getMinorVersion()
+ */
+ public int getMinorVersion() {
+ return context.getMinorVersion();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getNamedDispatcher(String arg0) {
+ return context.getNamedDispatcher(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+ */
+ public String getRealPath(String arg0) {
+ return context.getRealPath(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getRequestDispatcher(String arg0) {
+ return context.getRequestDispatcher(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @throws MalformedURLException
+ * @see javax.servlet.ServletContext#getResource(java.lang.String)
+ */
+ public URL getResource(String path) throws MalformedURLException {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+
+ URL url = moduleDef.findPublicFile(path);
+ if (url == null) {
+ File requestedFile = new File(outputDir, path);
+ if (requestedFile.exists()) {
+ url = requestedFile.toURL();
+ }
+ }
+
+ return url;
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+ */
+ public InputStream getResourceAsStream(String arg0) {
+ URL url;
+ try {
+ url = getResource(arg0);
+ if (url != null) {
+ return url.openStream();
+ }
+ } catch (MalformedURLException e) {
+ // Ignore the exception; return null
+ } catch (IOException e) {
+ // Ignore the exception; return null
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * @param path
+ * @return
+ * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+ */
+ public Set getResourcePaths(String path) {
+ return context.getResourcePaths(path);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getServerInfo()
+ */
+ public String getServerInfo() {
+ return context.getServerInfo();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @throws ServletException
+ * @deprecated
+ * @see javax.servlet.ServletContext#getServlet(java.lang.String)
+ */
+ public Servlet getServlet(String arg0) throws ServletException {
+ return context.getServlet(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getServletContextName()
+ */
+ public String getServletContextName() {
+ return context.getServletContextName();
+ }
+
+ /**
+ * @return
+ * @deprecated
+ * @see javax.servlet.ServletContext#getServletNames()
+ */
+ public Enumeration getServletNames() {
+ return context.getServletNames();
+ }
+
+ /**
+ * @return
+ * @deprecated
+ * @see javax.servlet.ServletContext#getServlets()
+ */
+ public Enumeration getServlets() {
+ return context.getServlets();
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ * @deprecated
+ * @see javax.servlet.ServletContext#log(java.lang.Exception,
+ * java.lang.String)
+ */
+ public void log(Exception arg0, String arg1) {
+ context.log(arg0, arg1);
+ }
+
+ /**
+ * @param arg0
+ * @see javax.servlet.ServletContext#log(java.lang.String)
+ */
+ public void log(String arg0) {
+ context.log(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ * @see javax.servlet.ServletContext#log(java.lang.String,
+ * java.lang.Throwable)
+ */
+ public void log(String arg0, Throwable arg1) {
+ context.log(arg0, arg1);
+ }
+
+ /**
+ * @param arg0
+ * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String arg0) {
+ context.removeAttribute(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ * @see javax.servlet.ServletContext#setAttribute(java.lang.String,
+ * java.lang.Object)
+ */
+ public void setAttribute(String arg0, Object arg1) {
+ context.setAttribute(arg0, arg1);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 11a52cb..adf7218 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -768,6 +768,25 @@
}
/**
+ * Returns a string representation of the byte array as a series of
+ * hexadecimal characters.
+ *
+ * @param bytes byte array to convert
+ * @return a string representation of the byte array as a series of
+ * hexadecimal characters
+ */
+ public static String toHexString(byte[] bytes) {
+ char[] hexString = new char[2 * bytes.length];
+ int j = 0;
+ for (int i = 0; i < bytes.length; i++) {
+ hexString[j++] = Util.HEX_CHARS[(bytes[i] & 0xF0) >> 4];
+ hexString[j++] = Util.HEX_CHARS[bytes[i] & 0x0F];
+ }
+
+ return new String(hexString);
+ }
+
+ /**
* Creates a string array from the contents of a collection.
*/
public static String[] toStringArray(Collection coll) {
diff --git a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
index 84aa23a..ff364f5 100644
--- a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
@@ -135,7 +135,7 @@
throws UnableToCompleteException, IOException {
String path = createTempOutFilename();
OutputStream os = genCtx.tryCreateResource(mockLogger, path);
- os.write("going to call commit twice after this...".getBytes());
+ os.write("going to call commit twice after this...".getBytes(Util.DEFAULT_ENCODING));
genCtx.commitResource(mockLogger, os);
File createdFile = new File(tempOutDir, path);
assertTrue(createdFile.exists());
@@ -309,7 +309,8 @@
private String createTempOutFilename() {
File tempFile;
do {
- tempFile = new File(tempOutDir, System.currentTimeMillis() + "-" + (++tempFileCounter) + ".gwt.tmp");
+ tempFile = new File(tempOutDir, System.currentTimeMillis() + "-"
+ + (++tempFileCounter) + ".gwt.tmp");
} while (tempFile.exists());
return tempFile.getName();
}
diff --git a/eclipse/settings/english.dictionary b/eclipse/settings/english.dictionary
index 701b8af..0bd4cae 100644
--- a/eclipse/settings/english.dictionary
+++ b/eclipse/settings/english.dictionary
@@ -47264,3 +47264,5 @@
proxy
mozilla
fulfill
+deserialization
+deserialized
diff --git a/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java b/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java
index a274c8f..aec0916 100644
--- a/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java
+++ b/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java
@@ -19,11 +19,9 @@
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
-import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -39,33 +37,33 @@
* could be performed.
*/
public void doPost(HttpServletRequest httpRequest,
- HttpServletResponse httpResponse) throws ServletException, IOException {
+ HttpServletResponse httpResponse) {
String payload = readPayloadAsUtf8(httpRequest);
try {
try {
RPCRequest rpcRequest = RPC.decodeRequest(payload);
-
+
Object targetInstance = getInstanceToHandleRequest(httpRequest,
rpcRequest);
-
+
Method targetMethod = maybeMapRequestedMethod(targetInstance,
rpcRequest.getMethod());
-
+
Object[] targetParameters = maybeMapParameters(rpcRequest.getParameters());
-
+
try {
Object result = targetMethod.invoke(targetInstance, targetParameters);
-
+
result = maybeMapResult(rpcRequest.getMethod(), result);
-
+
/*
* Encode the object that will be given to the client code's
* AsyncCallback::onSuccess(Object) method.
*/
String encodedResult = RPC.encodeResponseForSuccess(
rpcRequest.getMethod(), result);
-
+
sendResponseForSuccess(httpResponse, encodedResult);
} catch (IllegalArgumentException e) {
SecurityException securityException = new SecurityException(
@@ -76,27 +74,29 @@
SecurityException securityException = new SecurityException(
"Blocked attempt to access inaccessible method "
+ targetMethod
- + (targetInstance != null ? " on target " + targetInstance : ""));
+ + (targetInstance != null ? " on target " + targetInstance
+ : ""));
securityException.initCause(e);
throw securityException;
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
-
+
Throwable mappedThrowable = maybeMapThrowable(cause,
rpcRequest.getMethod());
-
+
/*
- * Encode the exception that will be passed back to the client's client
- * code's AsyncCallback::onFailure(Throwable) method.
+ * Encode the exception that will be passed back to the client's
+ * client code's AsyncCallback::onFailure(Throwable) method.
*/
String failurePayload = RPC.encodeResponseForFailure(
rpcRequest.getMethod(), mappedThrowable);
-
+
sendResponseForFailure(httpResponse, failurePayload);
}
} catch (IncompatibleRemoteServiceException e) {
- sendResponseForFailure(httpResponse, RPC.encodeResponseForFailure(null, e));
- }
+ sendResponseForFailure(httpResponse, RPC.encodeResponseForFailure(null,
+ e));
+ }
} catch (Throwable e) {
/*
* Return a generic error which will be passed to the client code's
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
index 94ae2e3..f8473c8 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -17,17 +17,23 @@
/**
* Base class for the client and server serialization streams. This class
- * handles the basic serialization and desirialization formatting for primitive
+ * handles the basic serialization and deserialization formatting for primitive
* types since these are common between the client and the server.
*/
public abstract class AbstractSerializationStream {
public static final int SERIALIZATION_STREAM_FLAGS_NO_TYPE_VERSIONING = 1;
- public static final int SERIALIZATION_STREAM_VERSION = 2;
+ public static final int SERIALIZATION_STREAM_VERSION = 3;
- protected int flags = 0;
+ /**
+ * The last legacy stream version which does not use a
+ * {@link com.google.gwt.user.server.rpc.SerializationPolicy SerializationPolicy}.
+ */
+ public static final int SERIALIZATION_STREAM_VERSION_WITHOUT_SERIALIZATION_POLICY = 2;
- protected int version;
+ private int flags = 0;
+
+ private int version = SERIALIZATION_STREAM_VERSION;
public final void addFlags(int flags) {
this.flags |= flags;
@@ -49,6 +55,17 @@
return (flags & SERIALIZATION_STREAM_FLAGS_NO_TYPE_VERSIONING) == 0;
}
+ /**
+ * Returns <code>true</code> if this stream encodes information which can be
+ * used to lookup a {@link SerializationPolicy}.
+ *
+ * @return <code>true</code> if this stream encodes information which can be
+ * used to lookup a <code>SerializationPolicy</code>
+ */
+ protected boolean hasSerializationPolicyInfo() {
+ return getVersion() > SERIALIZATION_STREAM_VERSION_WITHOUT_SERIALIZATION_POLICY;
+ }
+
protected final void setVersion(int version) {
this.version = version;
}
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
index a7ae018..c201d75 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStreamWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -20,7 +20,7 @@
/**
* Base class for the client and server serialization streams. This class
- * handles the basic serialization and desirialization formatting for primitive
+ * handles the basic serialization and deserialization formatting for primitive
* types since these are common between the client and the server.
*/
public abstract class AbstractSerializationStreamWriter extends
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
index 89fb7d6..21a91d6 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -33,33 +33,59 @@
sb.append('\uffff');
}
- private static native JavaScriptObject createJavaScriptObject() /*-{
- return {};
- }-*/;
+ private StringBuffer encodeBuffer;
+
+ private final String moduleBaseURL;
+
+ private int objectCount;
/*
* Accessed from JSNI code, so ignore unused warning.
*/
private JavaScriptObject objectMap;
+ private final String serializationPolicyStrongName;
+
+ private final Serializer serializer;
+
/*
* Accesses need to be prefixed with ':' to prevent conflict with built-in
* JavaScript properties.
- *
+ *
* Accessed from JSNI code, so ignore unused warning.
*/
private JavaScriptObject stringMap;
- private StringBuffer encodeBuffer;
-
- private int objectCount;
-
- private Serializer serializer;
-
private ArrayList stringTable = new ArrayList();
+ /**
+ * Constructs a <code>ClientSerializationStreamWriter</code> that does not
+ * use a serialization policy file.
+ *
+ * @param serializer the {@link Serializer} to use
+ */
public ClientSerializationStreamWriter(Serializer serializer) {
this.serializer = serializer;
+ this.moduleBaseURL = null;
+ this.serializationPolicyStrongName = null;
+ // Override the default version if no policy info is given.
+ setVersion(SERIALIZATION_STREAM_VERSION_WITHOUT_SERIALIZATION_POLICY);
+ }
+
+ /**
+ * Constructs a <code>ClientSerializationStreamWriter</code> using the
+ * specified module base URL and the serialization policy.
+ *
+ * @param serializer the {@link Serializer} to use
+ * @param moduleBaseURL the location of the module
+ * @param serializationPolicyStrongName the strong name of serialization
+ * policy
+ */
+ public ClientSerializationStreamWriter(Serializer serializer,
+ String moduleBaseURL, String serializationPolicyStrongName) {
+ this.serializer = serializer;
+ this.moduleBaseURL = moduleBaseURL;
+ this.serializationPolicyStrongName = serializationPolicyStrongName;
}
/**
@@ -68,10 +94,15 @@
*/
public void prepareToWrite() {
objectCount = 0;
- objectMap = createJavaScriptObject();
- stringMap = createJavaScriptObject();
+ objectMap = JavaScriptObject.createObject();
+ stringMap = JavaScriptObject.createObject();
stringTable.clear();
encodeBuffer = new StringBuffer();
+
+ if (hasSerializationPolicyInfo()) {
+ writeString(moduleBaseURL);
+ writeString(serializationPolicyStrongName);
+ }
}
public String toString() {
@@ -148,7 +179,7 @@
}-*/;
private void writeHeader(StringBuffer buffer) {
- append(buffer, String.valueOf(SERIALIZATION_STREAM_VERSION));
+ append(buffer, String.valueOf(getVersion()));
append(buffer, String.valueOf(getFlags()));
}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
index 436d956..741ce07 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -29,6 +29,7 @@
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.NameFactory;
+import com.google.gwt.dev.util.Util;
import com.google.gwt.user.client.ResponseTextHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
@@ -40,8 +41,16 @@
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
/**
* Creates a client-side proxy for a
@@ -52,14 +61,6 @@
private static final String ENTRY_POINT_TAG = "gwt.defaultEntryPoint";
private static final String PROXY_SUFFIX = "_Proxy";
- private static final String SERIALIZATION_STREAM_READER_INSTANTIATION = ClientSerializationStreamReader.class.getName()
- + " streamReader = new "
- + ClientSerializationStreamReader.class.getName() + "(SERIALIZER);";
-
- private static final String SERIALIZATION_STREAM_WRITER_INSTANTIATION = ClientSerializationStreamWriter.class.getName()
- + " streamWriter = new "
- + ClientSerializationStreamWriter.class.getName() + "(SERIALIZER);";
-
/*
* This method returns the real type name. Currently, it only affects
* JParameterizedType since their names are not legal Java names.
@@ -79,7 +80,6 @@
public ProxyCreator(JClassType serviceIntf) {
assert (serviceIntf.isInterface() != null);
-
this.serviceIntf = serviceIntf;
}
@@ -96,7 +96,7 @@
}
TypeOracle typeOracle = context.getTypeOracle();
-
+
// Make sure that the async and synchronous versions of the RemoteService
// agree with one another
//
@@ -117,7 +117,10 @@
enforceTypeVersioning = Shared.shouldEnforceTypeVersioning(logger,
context.getPropertyOracle());
- generateProxyFields(srcWriter, sto);
+ String serializationPolicyStrongName = writeSerializationPolicyFile(logger,
+ context, sto);
+
+ generateProxyFields(srcWriter, sto, serializationPolicyStrongName);
generateServiceDefTargetImpl(srcWriter);
@@ -175,8 +178,14 @@
w.println((i > 0 ? ", final " : "final ") + AsyncCallback.class.getName()
+ " callback) {");
w.indent();
- w.println("final " + SERIALIZATION_STREAM_READER_INSTANTIATION);
- w.println("final " + SERIALIZATION_STREAM_WRITER_INSTANTIATION);
+ w.println("final "
+ + (ClientSerializationStreamReader.class.getName()
+ + " streamReader = new "
+ + ClientSerializationStreamReader.class.getName() + "(SERIALIZER);"));
+ w.println("final "
+ + (ClientSerializationStreamWriter.class.getName()
+ + " streamWriter = new "
+ + ClientSerializationStreamWriter.class.getName() + "(SERIALIZER, GWT.getModuleBaseURL(), SERIALIZATION_POLICY);"));
w.println("try {");
w.indent();
{
@@ -370,10 +379,16 @@
+ ClientSerializationStreamReader.class.getName()
+ ".SERIALIZATION_STREAM_FLAGS_NO_TYPE_VERSIONING);");
}
- w.println("streamWriter.writeString(\""
- + serializableTypeOracle.getSerializedTypeName(method.getEnclosingType())
- + "\");");
+
+ // Write the remote service interface's binary name
+ JClassType remoteService = method.getEnclosingType();
+ String remoteServiceBinaryName = serializableTypeOracle.getSerializedTypeName(remoteService);
+ w.println("streamWriter.writeString(\"" + remoteServiceBinaryName + "\");");
+
+ // Write the method name
w.println("streamWriter.writeString(\"" + methodName + "\");");
+
+ // Write the parameter count followed by the parameter values
w.println("streamWriter.writeInt(" + params.length + ");");
for (int i = 0; i < params.length; ++i) {
JParameter param = params[i];
@@ -397,10 +412,13 @@
* Generate any fields required by the proxy.
*/
private void generateProxyFields(SourceWriter srcWriter,
- SerializableTypeOracle serializableTypeOracle) {
+ SerializableTypeOracle serializableTypeOracle,
+ String serializationPolicyStrongName) {
String typeSerializerName = serializableTypeOracle.getTypeSerializerQualifiedName(serviceIntf);
srcWriter.println("private static final " + typeSerializerName
+ " SERIALIZER = new " + typeSerializerName + "();");
+ srcWriter.println("private static final String SERIALIZATION_POLICY =\""
+ + serializationPolicyStrongName + "\";");
}
private void generateProxyMethods(SourceWriter w,
@@ -506,4 +524,57 @@
private boolean shouldEnforceTypeVersioning() {
return enforceTypeVersioning;
}
+
+ private String writeSerializationPolicyFile(TreeLogger logger,
+ GeneratorContext ctx, SerializableTypeOracle sto)
+ throws UnableToCompleteException {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ OutputStreamWriter osw = new OutputStreamWriter(baos,
+ SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
+ PrintWriter pw = new PrintWriter(osw);
+
+ JType[] serializableTypes = sto.getSerializableTypes();
+ for (int i = 0; i < serializableTypes.length; ++i) {
+ JType serializableType = serializableTypes[i];
+ String binaryTypeName = sto.getSerializedTypeName(serializableType);
+ boolean maybeInstantiated = sto.maybeInstantiated(serializableType);
+ pw.println(binaryTypeName + ", " + Boolean.toString(maybeInstantiated));
+ }
+
+ // Closes the wrapped streams.
+ pw.close();
+
+ byte[] serializationPolicyFileContents = baos.toByteArray();
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(serializationPolicyFileContents);
+
+ String serializationPolicyName = Util.toHexString(md5.digest());
+ String serializationPolicyFileName = SerializationPolicyLoader.getSerializationPolicyFileName(serializationPolicyName);
+ OutputStream os = ctx.tryCreateResource(logger,
+ serializationPolicyFileName);
+ if (os != null) {
+ os.write(serializationPolicyFileContents);
+ ctx.commitResource(logger, os);
+ } else {
+ logger.log(TreeLogger.TRACE,
+ "SerializationPolicy file for RemoteService '"
+ + serviceIntf.getQualifiedSourceName()
+ + "' already exists; no need to rewrite it.", null);
+ }
+
+ return serializationPolicyName;
+ } catch (NoSuchAlgorithmException e) {
+ logger.log(TreeLogger.ERROR, "Error initializing MD5", e);
+ throw new UnableToCompleteException();
+ } catch (UnsupportedEncodingException e) {
+ logger.log(TreeLogger.ERROR,
+ SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
+ + " is not supported", e);
+ throw new UnableToCompleteException();
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, null, e);
+ throw new UnableToCompleteException();
+ }
+ }
}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracle.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracle.java
index 7673f95..1dde20d 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracle.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracle.java
@@ -60,8 +60,8 @@
String getSerializationSignature(JType instanceType);
/**
- * Returns the serialized name of a type. The serialized name of a type is
- * the name that would be returned by {@link Class#getName()}.
+ * Returns the serialized name of a type. The serialized name of a type is the
+ * name that would be returned by {@link Class#getName()}.
*
* @param type
* @return serialized name of a type
@@ -104,4 +104,13 @@
* @return true if the type is serializable
*/
boolean isSerializable(JType type);
+
+ /**
+ * Returns <code>true</code> if the type might be instantiated as part of
+ * deserialization or serialization.
+ *
+ * @param type the type to test
+ * @return <code>true</code> if the type might be instantiated
+ */
+ boolean maybeInstantiated(JType type);
}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index 00dbb42..7d9047e 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -190,6 +190,11 @@
private final JClassType manualSerializer;
/**
+ * <code>true</code> if this type might be instantiated.
+ */
+ private boolean maybeInstantiated;
+
+ /**
* <code>true</code> if the type is automatically or manually serializable
* and the corresponding checks succeed.
*/
@@ -298,6 +303,15 @@
}
/**
+ * Returns <code>true</code> if this type might be instantiated.
+ *
+ * @return <code>true</code> if this type might be instantiated
+ */
+ public boolean maybeInstantiated() {
+ return maybeInstantiated;
+ }
+
+ /**
* Returns <code>true</code> if this type needs to be rechecked. Only type
* roots types have their subtypes analyzed. However it is possible that
* this type was previously checked as the super type of a root type in
@@ -354,6 +368,10 @@
directlyImplementsMarker = null;
}
+ public void setMaybeInstantiated(boolean maybeInstantiated) {
+ this.maybeInstantiated = maybeInstantiated;
+ }
+
public void setSerializable(boolean serializable) {
this.serializable = serializable;
}
@@ -702,10 +720,12 @@
// String is always serializable
MetaTypeInfo stringMti = getMetaTypeInfo(stringClass);
stringMti.setSerializable(true);
+ stringMti.setMaybeInstantiated(true);
// IncompatibleRemoteServiceException is always serializable
MetaTypeInfo incompatibleRemoteServiceExceptionMti = getMetaTypeInfo(typeOracle.getType(IncompatibleRemoteServiceException.class.getName()));
incompatibleRemoteServiceExceptionMti.setSerializable(true);
+ incompatibleRemoteServiceExceptionMti.setMaybeInstantiated(true);
} catch (NotFoundException e) {
rootLogger.log(TreeLogger.ERROR, null, e);
throw new UnableToCompleteException();
@@ -744,6 +764,7 @@
throw new UnableToCompleteException();
}
+ Set possiblyInstantiatedTypes = new HashSet();
List serializableTypesList = new ArrayList();
Iterator iterTypes = typeToMetaTypeInfo.values().iterator();
while (iterTypes.hasNext()) {
@@ -751,6 +772,9 @@
JType type = mti.getType();
if (mti.isSerializable() && type.isInterface() == null) {
+ if (mti.maybeInstantiated()) {
+ possiblyInstantiatedTypes.add(type);
+ }
serializableTypesList.add(type);
}
}
@@ -768,7 +792,8 @@
logSerializableTypes(logger, serializableTypes);
- return new SerializableTypeOracleImpl(typeOracle, serializableTypes);
+ return new SerializableTypeOracleImpl(typeOracle, serializableTypes,
+ possiblyInstantiatedTypes);
}
/**
@@ -856,6 +881,8 @@
return;
}
+ // TODO: revisit this check; we probably want to field serialize a type
+ // that is not default constructable, for the sake of subclasses.
if (type.isClass() != null && !type.isAbstract()
&& !type.isDefaultInstantiable()) {
markAsUnserializableAndLog(
@@ -881,19 +908,27 @@
}
if (checkSubtypes) {
+ if (!type.isAbstract()
+ && (type.isDefaultInstantiable() || mti.qualifiesForManualSerialization())) {
+ mti.setMaybeInstantiated(true);
+ }
+
JClassType[] subtypes = type.getSubtypes();
if (subtypes.length > 0) {
TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
"Analyzing subclasses:", null);
for (int i = 0; i < subtypes.length; ++i) {
- JClassType subtype = subtypes[i];
- MetaTypeInfo smti = getMetaTypeInfo(subtype);
+ JClassType subType = subtypes[i];
+ MetaTypeInfo smti = getMetaTypeInfo(subType);
if (smti.qualifiesForSerialization()) {
- checkType(localLogger, subtype, false);
+ checkType(localLogger, subType, false);
+ if (!subType.isAbstract() && subType.isDefaultInstantiable()) {
+ smti.setMaybeInstantiated(true);
+ }
} else {
localLogger.branch(TreeLogger.DEBUG, "Not analyzing subclass '"
- + subtype.getParameterizedQualifiedSourceName()
+ + subType.getParameterizedQualifiedSourceName()
+ "' because it is not assignable to '"
+ IsSerializable.class.getName() + "' or '"
+ Serializable.class.getName()
@@ -1145,7 +1180,8 @@
JType leafType = isArray.getLeafType();
if (leafType.isPrimitive() != null) {
serializableTypes.add(isArray);
- getMetaTypeInfo(isArray).setSerializable(true);
+ mti.setSerializable(true);
+ mti.setMaybeInstantiated(true);
} else {
List leafTypes = getSerializableTypesAssignableTo(leafType);
List covariantLeafTypes = getAllTypesBetweenRootTypeAndSerializableLeaves(
@@ -1157,7 +1193,10 @@
JArrayType covariantArray = getArrayType(typeOracle,
isArray.getRank(), clazz);
serializableTypes.add(covariantArray);
- getMetaTypeInfo(covariantArray).setSerializable(true);
+
+ MetaTypeInfo cmti = getMetaTypeInfo(covariantArray);
+ cmti.setSerializable(true);
+ cmti.setMaybeInstantiated(true);
}
}
} else if (isParameterized != null) {
@@ -1258,14 +1297,6 @@
}
private void markAsUnserializableAndLog(TreeLogger logger,
- TreeLogger.Type logType, String logMessage, MetaTypeInfo mti) {
- mti.setState(SerializableTypeOracleBuilder.CHECK_FAILED);
- mti.setSerializable(false);
- mti.addSerializationIssue(logType, logMessage);
- logger.branch(logType, logMessage, null);
- }
-
- private void markAsUnserializableAndLog(TreeLogger logger,
TreeLogger.Type logType, List failures, MetaTypeInfo mti) {
Iterator iter = failures.iterator();
while (iter.hasNext()) {
@@ -1273,6 +1304,14 @@
}
}
+ private void markAsUnserializableAndLog(TreeLogger logger,
+ TreeLogger.Type logType, String logMessage, MetaTypeInfo mti) {
+ mti.setState(SerializableTypeOracleBuilder.CHECK_FAILED);
+ mti.setSerializable(false);
+ mti.addSerializationIssue(logType, logMessage);
+ logger.branch(logType, logMessage, null);
+ }
+
private void validateRemoteService(TreeLogger logger, JClassType remoteService) {
JMethod[] methods = remoteService.getOverridableMethods();
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
index 3401959..57b640f 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
@@ -22,7 +22,9 @@
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.util.Util;
+import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -30,11 +32,13 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.TreeSet;
import java.util.zip.CRC32;
final class SerializableTypeOracleImpl implements SerializableTypeOracle {
private static final String DEFAULT_BUILTIN_CUSTOM_SERIALIZER_PACKAGE_NAME = "com.google.gwt.user.client.rpc.core.java.lang";
+
private static final Comparator FIELD_COMPARATOR = new Comparator() {
public int compare(Object o1, Object o2) {
JField f1 = (JField) o1;
@@ -43,6 +47,16 @@
return f1.getName().compareTo(f2.getName());
}
};
+
+ private static final Comparator TYPE_COMPARATOR = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ JType t1 = (JType) o1;
+ JType t2 = (JType) o2;
+
+ return t1.getParameterizedQualifiedSourceName().compareTo(
+ t2.getParameterizedQualifiedSourceName());
+ }
+ };
private static final String GENERATED_FIELD_SERIALIZER_SUFFIX = "_FieldSerializer";
private static final String TYPE_SERIALIZER_SUFFIX = "_TypeSerializer";
private static final Set TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES = new HashSet();
@@ -62,17 +76,18 @@
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add("java.lang.Throwable");
}
- private final JType[] serializableTypes;
private final Set /* <JType> */serializableTypesSet;
private final TypeOracle typeOracle;
+ private final Set /* <JType> */possiblyInstantiatedTypes;
public SerializableTypeOracleImpl(TypeOracle typeOracle,
- JType[] serializableTypes) {
+ JType[] serializableTypes, Set possiblyInstantiatedTypes) {
- this.serializableTypes = serializableTypes;
- serializableTypesSet = new HashSet();
+ serializableTypesSet = new TreeSet(TYPE_COMPARATOR);
serializableTypesSet.addAll(Arrays.asList(serializableTypes));
this.typeOracle = typeOracle;
+
+ this.possiblyInstantiatedTypes = possiblyInstantiatedTypes;
}
public String getFieldSerializerName(JType type) {
@@ -125,13 +140,18 @@
*
*/
public JType[] getSerializableTypes() {
- return serializableTypes;
+ return (JType[]) serializableTypesSet.toArray(new JType[serializableTypesSet.size()]);
}
public String getSerializationSignature(JType type) {
CRC32 crc = new CRC32();
- generateSerializationSignature(type, crc);
+ try {
+ generateSerializationSignature(type, crc);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(
+ "Could not compute the serialization signature", e);
+ }
return Long.toString(crc.getValue());
}
@@ -232,6 +252,10 @@
return serializableTypesSet.contains(type);
}
+ public boolean maybeInstantiated(JType type) {
+ return possiblyInstantiatedTypes.contains(type);
+ }
+
private boolean excludeImplementationFromSerializationSignature(
JType instanceType) {
if (TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.contains(instanceType.getQualifiedSourceName())) {
@@ -241,7 +265,8 @@
return false;
}
- private void generateSerializationSignature(JType type, CRC32 crc) {
+ private void generateSerializationSignature(JType type, CRC32 crc)
+ throws UnsupportedEncodingException {
JParameterizedType parameterizedType = type.isParameterized();
if (parameterizedType != null) {
generateSerializationSignature(parameterizedType.getRawType(), crc);
@@ -250,7 +275,7 @@
}
String serializedTypeName = getSerializedTypeName(type);
- crc.update(serializedTypeName.getBytes());
+ crc.update(serializedTypeName.getBytes(Util.DEFAULT_ENCODING));
if (excludeImplementationFromSerializationSignature(type)) {
return;
@@ -269,8 +294,9 @@
JField field = fields[i];
assert (field != null);
- crc.update(field.getName().getBytes());
- crc.update(getSerializedTypeName(field.getType()).getBytes());
+ crc.update(field.getName().getBytes(Util.DEFAULT_ENCODING));
+ crc.update(getSerializedTypeName(field.getType()).getBytes(
+ Util.DEFAULT_ENCODING));
}
JClassType superClass = isClassOrInterface.getSuperclass();
diff --git a/user/src/com/google/gwt/user/server/rpc/RPC.java b/user/src/com/google/gwt/user/server/rpc/RPC.java
index f38d615..2e5baa6 100644
--- a/user/src/com/google/gwt/user/server/rpc/RPC.java
+++ b/user/src/com/google/gwt/user/server/rpc/RPC.java
@@ -18,8 +18,7 @@
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.SerializationException;
-import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracle;
-import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracleImpl;
+import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicy;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
@@ -57,18 +56,13 @@
private static final Map PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS = new HashMap();
/**
- * Oracle used in stream construction. Encapsulates a set of static,
- * synchronized caches.
- */
- private static ServerSerializableTypeOracle serializableTypeOracle;
-
- /**
* Static map of classes to sets of interfaces (e.g. classes). Optimizes
* lookup of interfaces for security.
*/
private static Map/* <Class, Set<String> > */serviceToImplementedInterfacesMap;
private static final HashMap TYPE_NAMES;
+
static {
PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Boolean.class, Boolean.TYPE);
PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Byte.class, Byte.TYPE);
@@ -90,9 +84,6 @@
TYPE_NAMES.put("J", long.class);
TYPE_NAMES.put("S", short.class);
- serializableTypeOracle = new ServerSerializableTypeOracleImpl(
- getPackagePaths());
-
serviceToImplementedInterfacesMap = new HashMap();
}
@@ -170,6 +161,63 @@
* </ul>
*/
public static RPCRequest decodeRequest(String encodedRequest, Class type) {
+ return decodeRequest(encodedRequest, type, null);
+ }
+
+ /**
+ * Returns an {@link RPCRequest} that is built by decoding the contents of an
+ * encoded RPC request and optionally validating that type can handle the
+ * request. If the type parameter is not <code>null</code>, the
+ * implementation checks that the type is assignable to the
+ * {@link RemoteService} interface requested in the encoded request string.
+ *
+ * <p>
+ * If the serializationPolicyProvider parameter is not <code>null</code>, it is
+ * asked for a {@link SerializationPolicy} to use to restrict the set of types
+ * that can be decoded from the request. If this parameter is
+ * <code>null</code>, then only subtypes of
+ * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
+ * types which have custom field serializers can be decoded.
+ * </p>
+ *
+ * <p>
+ * Invoking this method with <code>null</code> for the type parameter,
+ * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to
+ * calling <code>decodeRequest(encodedRequest)</code>.
+ * </p>
+ *
+ * @param encodedRequest a string that encodes the {@link RemoteService}
+ * interface, the service method, and the arguments to pass to the
+ * service method
+ * @param type if not <code>null</code>, the implementation checks that the
+ * type is assignable to the {@link RemoteService} interface encoded
+ * in the encoded request string.
+ * @param serializationPolicyProvider if not <code>null</code>, the
+ * implementation asks this provider for a
+ * {@link SerializationPolicy} which will be used to restrict the set
+ * of types that can be decoded from this request
+ * @return an {@link RPCRequest} instance
+ *
+ * @throws NullPointerException if the encodedRequest is <code>null</code>
+ * @throws IllegalArgumentException if the encodedRequest is an empty string
+ * @throws IncompatibleRemoteServiceException if any of the following
+ * conditions apply:
+ * <ul>
+ * <li>if the types in the encoded request cannot be deserialized</li>
+ * <li>if the {@link ClassLoader} acquired from
+ * <code>Thread.currentThread().getContextClassLoader()</code>
+ * cannot load the service interface or any of the types specified
+ * in the encodedRequest</li>
+ * <li>the requested interface is not assignable to
+ * {@link RemoteService}</li>
+ * <li>the service method requested in the encodedRequest is not a
+ * member of the requested service interface</li>
+ * <li>the type parameter is not <code>null</code> and is not
+ * assignable to the requested {@link RemoteService} interface
+ * </ul>
+ */
+ public static RPCRequest decodeRequest(String encodedRequest, Class type,
+ SerializationPolicyProvider serializationPolicyProvider) {
if (encodedRequest == null) {
throw new NullPointerException("encodedRequest cannot be null");
}
@@ -182,9 +230,10 @@
try {
ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
- serializableTypeOracle, classLoader);
+ classLoader, serializationPolicyProvider);
streamReader.prepareToRead(encodedRequest);
+ // Read the name of the RemoteService interface
String serviceIntfName = streamReader.readString();
if (type != null) {
@@ -197,6 +246,7 @@
}
}
+ SerializationPolicy serializationPolicy = streamReader.getSerializationPolicy();
Class serviceIntf;
try {
serviceIntf = getClassFromSerializedName(serviceIntfName, classLoader);
@@ -243,7 +293,7 @@
parameterValues[i] = streamReader.deserializeValue(parameterTypes[i]);
}
- return new RPCRequest(method, parameterValues);
+ return new RPCRequest(method, parameterValues, serializationPolicy);
} catch (SerializationException ex) {
throw new IncompatibleRemoteServiceException(ex.getMessage(), ex);
@@ -255,7 +305,7 @@
* <code>null</code>, it is an error if the exception is not in the
* method's list of checked exceptions.
*
- * @param serviceMethod the method that threw the exception, maybe
+ * @param serviceMethod the method that threw the exception, may be
* <code>null</code>
* @param cause the {@link Throwable} that was thrown
* @return a string that encodes the exception
@@ -267,17 +317,53 @@
*/
public static String encodeResponseForFailure(Method serviceMethod,
Throwable cause) throws SerializationException {
+ return encodeResponseForFailure(serviceMethod, cause,
+ getDefaultSerializationPolicy());
+ }
+
+ /**
+ * Returns a string that encodes an exception. If method is not
+ * <code>null</code>, it is an error if the exception is not in the
+ * method's list of checked exceptions.
+ *
+ * <p>
+ * If the serializationPolicy parameter is not <code>null</code>, it is used to
+ * determine what types can be encoded as part of this response. If this
+ * parameter is <code>null</code>, then only subtypes of
+ * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
+ * types which have custom field serializers may be encoded.
+ * </p>
+ *
+ * @param serviceMethod the method that threw the exception, may be
+ * <code>null</code>
+ * @param cause the {@link Throwable} that was thrown
+ * @param serializationPolicy determines the serialization policy to be used
+ * @return a string that encodes the exception
+ *
+ * @throws NullPointerException if the the cause or the serializationPolicy
+ * are <code>null</code>
+ * @throws SerializationException if the result cannot be serialized
+ * @throws UnexpectedException if the result was an unexpected exception (a
+ * checked exception not declared in the serviceMethod's signature)
+ */
+ public static String encodeResponseForFailure(Method serviceMethod,
+ Throwable cause, SerializationPolicy serializationPolicy)
+ throws SerializationException {
if (cause == null) {
throw new NullPointerException("cause cannot be null");
}
+ if (serializationPolicy == null) {
+ throw new NullPointerException("serializationPolicy");
+ }
+
if (serviceMethod != null && !RPC.isExpectedException(serviceMethod, cause)) {
throw new UnexpectedException("Service method '"
+ getSourceRepresentation(serviceMethod)
+ "' threw an unexpected exception: " + cause.toString(), cause);
}
- return encodeResponse(cause.getClass(), cause, true);
+ return encodeResponse(cause.getClass(), cause, true, serializationPolicy);
}
/**
@@ -296,10 +382,45 @@
*/
public static String encodeResponseForSuccess(Method serviceMethod,
Object object) throws SerializationException {
+ return encodeResponseForSuccess(serviceMethod, object,
+ getDefaultSerializationPolicy());
+ }
+
+ /**
+ * Returns a string that encodes the object. It is an error to try to encode
+ * an object that is not assignable to the service method's return type.
+ *
+ * <p>
+ * If the serializationPolicy parameter is not <code>null</code>, it is used to
+ * determine what types can be encoded as part of this response. If this
+ * parameter is <code>null</code>, then only subtypes of
+ * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
+ * types which have custom field serializers may be encoded.
+ * </p>
+ *
+ * @param serviceMethod the method whose result we are encoding
+ * @param object the instance that we wish to encode
+ * @param serializationPolicy determines the serialization policy to be used
+ * @return a string that encodes the object, if the object is compatible with
+ * the service method's declared return type
+ *
+ * @throws IllegalArgumentException if the result is not assignable to the
+ * service method's return type
+ * @throws NullPointerException if the serviceMethod or the
+ * serializationPolicy are <code>null</code>
+ * @throws SerializationException if the result cannot be serialized
+ */
+ public static String encodeResponseForSuccess(Method serviceMethod,
+ Object object, SerializationPolicy serializationPolicy)
+ throws SerializationException {
if (serviceMethod == null) {
throw new NullPointerException("serviceMethod cannot be null");
}
+ if (serializationPolicy == null) {
+ throw new NullPointerException("serializationPolicy");
+ }
+
Class methodReturnType = serviceMethod.getReturnType();
if (methodReturnType != void.class && object != null) {
Class actualReturnType;
@@ -318,7 +439,16 @@
}
}
- return encodeResponse(methodReturnType, object, false);
+ return encodeResponse(methodReturnType, object, false, serializationPolicy);
+ }
+
+ /**
+ * Returns a default serialization policy.
+ *
+ * @return the default serialization policy.
+ */
+ public static SerializationPolicy getDefaultSerializationPolicy() {
+ return LegacySerializationPolicy.getInstance();
}
/**
@@ -345,16 +475,60 @@
*/
public static String invokeAndEncodeResponse(Object target,
Method serviceMethod, Object[] args) throws SerializationException {
+ return invokeAndEncodeResponse(target, serviceMethod, args,
+ getDefaultSerializationPolicy());
+ }
+ /**
+ * Returns a string that encodes the result of calling a service method, which
+ * could be the value returned by the method or an exception thrown by it.
+ *
+ * <p>
+ * If the serializationPolicy parameter is not <code>null</code>, it is used to
+ * determine what types can be encoded as part of this response. If this
+ * parameter is <code>null</code>, then only subtypes of
+ * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
+ * types which have custom field serializers may be encoded.
+ * </p>
+ *
+ * <p>
+ * This method does no security checking; security checking must be done on
+ * the method prior to this invocation.
+ * </p>
+ *
+ * @param target instance on which to invoke the serviceMethod
+ * @param serviceMethod the method to invoke
+ * @param args arguments used for the method invocation
+ * @param serializationPolicy determines the serialization policy to be used
+ * @return a string which encodes either the method's return or a checked
+ * exception thrown by the method
+ *
+ * @throws NullPointerException if the serviceMethod or the
+ * serializationPolicy are <code>null</code>
+ * @throws SecurityException if the method cannot be accessed or if the number
+ * or type of actual and formal arguments differ
+ * @throws SerializationException if an object could not be serialized by the
+ * stream
+ * @throws UnexpectedException if the serviceMethod throws a checked exception
+ * that is not declared in its signature
+ */
+ public static String invokeAndEncodeResponse(Object target,
+ Method serviceMethod, Object[] args,
+ SerializationPolicy serializationPolicy) throws SerializationException {
if (serviceMethod == null) {
- throw new NullPointerException("serviceMethod cannot be null");
+ throw new NullPointerException("serviceMethod");
+ }
+
+ if (serializationPolicy == null) {
+ throw new NullPointerException("serializationPolicy");
}
String responsePayload;
try {
Object result = serviceMethod.invoke(target, args);
- responsePayload = encodeResponseForSuccess(serviceMethod, result);
+ responsePayload = encodeResponseForSuccess(serviceMethod, result,
+ serializationPolicy);
} catch (IllegalAccessException e) {
SecurityException securityException = new SecurityException(
formatIllegalAccessErrorMessage(target, serviceMethod));
@@ -370,7 +544,8 @@
//
Throwable cause = e.getCause();
- responsePayload = encodeResponseForFailure(serviceMethod, cause);
+ responsePayload = encodeResponseForFailure(serviceMethod, cause,
+ serializationPolicy);
}
return responsePayload;
@@ -380,7 +555,6 @@
* Returns a string that encodes the results of an RPC call. Private overload
* that takes a flag signaling the preamble of the response payload.
*
- * @param serviceMethod the method whose return object we are encoding
* @param object the object that we wish to send back to the client
* @param wasThrown if true, the object being returned was an exception thrown
* by the service method; if false, it was the result of the service
@@ -389,10 +563,11 @@
* @throws SerializationException if the object cannot be serialized
*/
private static String encodeResponse(Class responseClass, Object object,
- boolean wasThrown) throws SerializationException {
+ boolean wasThrown, SerializationPolicy serializationPolicy)
+ throws SerializationException {
ServerSerializationStreamWriter stream = new ServerSerializationStreamWriter(
- serializableTypeOracle);
+ serializationPolicy);
stream.prepareToWrite();
if (responseClass != void.class) {
@@ -507,19 +682,6 @@
}
/**
- * Obtain the special package-prefixes we use to check for custom serializers
- * that would like to live in a package that they cannot. For example,
- * "java.util.ArrayList" is in a sealed package, so instead we use this prefix
- * to check for a custom serializer in
- * "com.google.gwt.user.client.rpc.core.java.util.ArrayList". Right now, it's
- * hard-coded because we don't have a pressing need for this mechanism to be
- * extensible, but it is imaginable, which is why it's implemented this way.
- */
- private static String[] getPackagePaths() {
- return new String[] {"com.google.gwt.user.client.rpc.core"};
- }
-
- /**
* Returns the {@link java.lang.Class Class} for a primitive type given its
* corresponding wrapper {@link java.lang.Class Class}.
*
diff --git a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
index e21ed52..df4d846 100644
--- a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
+++ b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
@@ -18,8 +18,8 @@
import java.lang.reflect.Method;
/**
- * Describes an incoming RPC request in terms of a resolved
- * {@link Method} and an array of arguments.
+ * Describes an incoming RPC request in terms of a resolved {@link Method} and
+ * an array of arguments.
*/
public final class RPCRequest {
@@ -34,24 +34,43 @@
private final Object[] parameters;
/**
+ * {@link SerializationPolicy} used for decoding this request and for encoding
+ * the responses.
+ */
+ private final SerializationPolicy serializationPolicy;
+
+ /**
* Construct an RPCRequest.
*/
- public RPCRequest (Method method, Object[] parameters) {
+ public RPCRequest(Method method, Object[] parameters,
+ SerializationPolicy serializationPolicy) {
this.method = method;
this.parameters = parameters;
+ this.serializationPolicy = serializationPolicy;
}
/**
* Get the request's method.
*/
- public Method getMethod () {
+ public Method getMethod() {
return method;
}
/**
* Get the request's parameters.
*/
- public Object[] getParameters () {
+ public Object[] getParameters() {
return parameters;
}
+
+ /**
+ * Returns the {@link SerializationPolicy} used to decode this request. This
+ * is also the <code>SerializationPolicy</code> that should be used to
+ * encode responses.
+ *
+ * @return {@link SerializationPolicy} used to decode this request
+ */
+ public SerializationPolicy getSerializationPolicy() {
+ return serializationPolicy;
+ }
}
diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
index 0986296..20eaf92 100644
--- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
+++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
@@ -21,6 +21,11 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletContext;
@@ -34,18 +39,20 @@
* automatically deserializes incoming requests from the client and serializes
* outgoing responses for client/server RPCs.
*/
-public class RemoteServiceServlet extends HttpServlet {
+public class RemoteServiceServlet extends HttpServlet implements
+ SerializationPolicyProvider {
/*
* These members are used to get and set the different HttpServletResponse and
* HttpServletRequest headers.
*/
private static final String ACCEPT_ENCODING = "Accept-Encoding";
+
private static final String CHARSET_UTF8 = "UTF-8";
private static final String CONTENT_ENCODING = "Content-Encoding";
private static final String CONTENT_ENCODING_GZIP = "gzip";
+
private static final String CONTENT_TYPE_TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
private static final String GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";
-
/**
* Controls the compression threshold at and below which no compression will
* take place.
@@ -140,6 +147,12 @@
private final ThreadLocal perThreadResponse = new ThreadLocal();
/**
+ * A cache of moduleBaseURL and serialization policy strong name to
+ * {@link SerializationPolicy}.
+ */
+ private final Map /* <String, SerializationPolicy> */serializationPolicyCache = new HashMap();
+
+ /**
* The default constructor.
*/
public RemoteServiceServlet() {
@@ -193,6 +206,36 @@
}
}
+ public final SerializationPolicy getSerializationPolicy(String moduleBaseURL,
+ String strongName) {
+
+ SerializationPolicy serializationPolicy = getCachedSerializationPolicy(
+ moduleBaseURL, strongName);
+ if (serializationPolicy != null) {
+ return serializationPolicy;
+ }
+
+ serializationPolicy = doGetSerializationPolicy(getThreadLocalRequest(),
+ moduleBaseURL, strongName);
+
+ if (serializationPolicy == null) {
+ // Failed to get the requested serialization policy; use the default
+ getServletContext().log(
+ "WARNING: Failed to get the SerializationPolicy '"
+ + strongName
+ + "' for module '"
+ + moduleBaseURL
+ + "'; a legacy, 1.3.3 compatible, serialization policy will be used. You may experience SerializationExceptions as a result.");
+ serializationPolicy = RPC.getDefaultSerializationPolicy();
+ }
+
+ // This could cache null or an actual instance. Either way we will not
+ // attempt to lookup the policy again.
+ putCachedSerializationPolicy(moduleBaseURL, strongName, serializationPolicy);
+
+ return serializationPolicy;
+ }
+
/**
* Process a call originating from the given request. Uses the
* {@link RPC#invokeAndEncodeResponse(Object, java.lang.reflect.Method, Object[])}
@@ -218,15 +261,109 @@
*/
public String processCall(String payload) throws SerializationException {
try {
- RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass());
+ RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass(), this);
return RPC.invokeAndEncodeResponse(this, rpcRequest.getMethod(),
- rpcRequest.getParameters());
+ rpcRequest.getParameters(), rpcRequest.getSerializationPolicy());
} catch (IncompatibleRemoteServiceException ex) {
return RPC.encodeResponseForFailure(null, ex);
}
}
/**
+ * Gets the {@link SerializationPolicy} for given module base URL and strong
+ * name if there is one.
+ *
+ * Override this method to provide a {@link SerializationPolicy} using an
+ * alternative approach.
+ *
+ * @param request the HTTP request being serviced
+ * @param moduleBaseURL as specified in the incoming payload
+ * @param strongName a strong name that uniquely identifies a serialization
+ * policy file
+ * @return a {@link SerializationPolicy} for the given module base URL and
+ * strong name, or <code>null</code> if there is none
+ */
+ protected SerializationPolicy doGetSerializationPolicy(
+ HttpServletRequest request, String moduleBaseURL, String strongName) {
+ // The request can tell you the path of the web app relative to the
+ // container root.
+ String contextPath = request.getContextPath();
+
+ String modulePath = null;
+ if (moduleBaseURL != null) {
+ try {
+ modulePath = new URL(moduleBaseURL).getPath();
+ } catch (MalformedURLException ex) {
+ // log the information, we will default
+ getServletContext().log("Malformed moduleBaseURL: " + moduleBaseURL, ex);
+ }
+ }
+
+ SerializationPolicy serializationPolicy = null;
+
+ /*
+ * Check that the module path must be in the same web app as the servlet
+ * itself. If you need to implement a scheme different than this, override
+ * this method.
+ */
+ if (modulePath == null || !modulePath.startsWith(contextPath)) {
+ String message = "ERROR: The module path requested, "
+ + modulePath
+ + ", is not in the same web application as this servlet, "
+ + contextPath
+ + ". Your module may not be properly configured or your client and server code maybe out of date.";
+ getServletContext().log(message);
+ } else {
+ // Strip off the context path from the module base URL. It should be a
+ // strict prefix.
+ String contextRelativePath = modulePath.substring(contextPath.length());
+
+ String serializationPolicyFilePath = SerializationPolicyLoader.getSerializationPolicyFileName(contextRelativePath
+ + strongName);
+
+ // Open the RPC resource file read its contents.
+ InputStream is = getServletContext().getResourceAsStream(
+ serializationPolicyFilePath);
+ try {
+ if (is != null) {
+ try {
+ serializationPolicy = SerializationPolicyLoader.loadFromStream(is);
+ } catch (ParseException e) {
+ getServletContext().log(
+ "ERROR: Failed to parse the policy file '"
+ + serializationPolicyFilePath + "'", e);
+ } catch (ClassNotFoundException e) {
+ getServletContext().log(
+ "ERROR: Could not find class '" + e.getMessage()
+ + "' listed in the serialization policy file '"
+ + serializationPolicyFilePath + "'"
+ + "; your server's classpath may be misconfigured", e);
+ } catch (IOException e) {
+ getServletContext().log(
+ "ERROR: Could not read the policy file '"
+ + serializationPolicyFilePath + "'", e);
+ }
+ } else {
+ String message = "ERROR: The serialization policy file '"
+ + serializationPolicyFilePath
+ + "' was not found; did you forget to include it in this deployment?";
+ getServletContext().log(message);
+ }
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // Ignore this error
+ }
+ }
+ }
+ }
+
+ return serializationPolicy;
+ }
+
+ /**
* Override this method to control what should happen when an exception
* escapes the {@link #processCall(String)} method. The default implementation
* will log the failure and send a generic failure response to the client.<p/>
@@ -310,6 +447,22 @@
return estimateByteSize(responsePayload) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
}
+ private SerializationPolicy getCachedSerializationPolicy(
+ String moduleBaseURL, String strongName) {
+ synchronized (serializationPolicyCache) {
+ return (SerializationPolicy) serializationPolicyCache.get(moduleBaseURL
+ + strongName);
+ }
+ }
+
+ private void putCachedSerializationPolicy(String moduleBaseURL,
+ String strongName, SerializationPolicy serializationPolicy) {
+ synchronized (serializationPolicyCache) {
+ serializationPolicyCache.put(moduleBaseURL + strongName,
+ serializationPolicy);
+ }
+ }
+
/**
* Called when the machinery of this class itself has a problem, rather than
* the invoked third-party method. It writes a simple 500 message back to the
diff --git a/user/src/com/google/gwt/user/server/rpc/SerializationPolicy.java b/user/src/com/google/gwt/user/server/rpc/SerializationPolicy.java
new file mode 100644
index 0000000..5e31ab4
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/SerializationPolicy.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007 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.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+
+/**
+ * This is an abstract class for representing the serialization policy for a
+ * given module and
+ * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}.
+ */
+public abstract class SerializationPolicy {
+ /**
+ * Returns <code>true</code> if the class' fields should be deserialized.
+ *
+ * @param clazz the class to test
+ * @return <code>true</code> if the class' fields should be deserialized
+ */
+ public abstract boolean shouldDeserializeFields(Class clazz);
+
+ /**
+ * Returns <code>true</code> if the class' fields should be serialized.
+ *
+ * @param clazz the class to test
+ * @return <code>true</code> if the class' fields should be serialized
+ */
+ public abstract boolean shouldSerializeFields(Class clazz);
+
+ /**
+ * Validates that the specified class should be deserialized from a stream.
+ *
+ * @param clazz the class to validate
+ * @throws SerializationException if the class is not allowed to be
+ * deserialized
+ */
+ public abstract void validateDeserialize(Class clazz)
+ throws SerializationException;
+
+ /**
+ * Validates that the specified class should be serialized into a stream.
+ *
+ * @param clazz the class to validate
+ * @throws SerializationException if the class is not allowed to be serialized
+ */
+ public abstract void validateSerialize(Class clazz)
+ throws SerializationException;
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java
new file mode 100644
index 0000000..c75f16e
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyLoader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2007 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.user.server.rpc;
+
+import com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * API for loading a {@link SerializationPolicy}.
+ */
+public final class SerializationPolicyLoader {
+ /**
+ * Default encoding for serialization policy files.
+ */
+ public static final String SERIALIZATION_POLICY_FILE_ENCODING = "UTF-8";
+
+ private static final String FORMAT_ERROR_MESSAGE = "Expected: className, [true | false]";
+
+ /**
+ * Returns the serialization policy file name from the from the serialization
+ * policy strong name.
+ *
+ * @param serializationPolicyStrongName the serialization policy strong name
+ * @return the serialization policy file name from the from the serialization
+ * policy strong name
+ */
+ public static String getSerializationPolicyFileName(
+ String serializationPolicyStrongName) {
+ return serializationPolicyStrongName + ".gwt.rpc";
+ }
+
+ /**
+ * Loads a SerializationPolicy from an input stream.
+ *
+ * @param inputStream stream to load from
+ * @return a {@link SerializationPolicy} loaded from the input stream
+ *
+ * @throws IOException if an error occurs while reading the stream
+ * @throws ParseException if the input stream is not properly formatted
+ * @throws ClassNotFoundException if a class specified in the serialization
+ * policy cannot be loaded
+ */
+ public static SerializationPolicy loadFromStream(InputStream inputStream)
+ throws IOException, ParseException, ClassNotFoundException {
+ if (inputStream == null) {
+ throw new NullPointerException("inputStream");
+ }
+
+ Map /* <Class, Boolean> */whitelist = new HashMap();
+
+ InputStreamReader isr = new InputStreamReader(inputStream,
+ SERIALIZATION_POLICY_FILE_ENCODING);
+ BufferedReader br = new BufferedReader(isr);
+
+ String line = br.readLine();
+ int lineNum = 1;
+ while (line != null) {
+ line = line.trim();
+ if (line.length() > 0) {
+ String[] components = line.split(",");
+
+ if (components.length != 2) {
+ throw new ParseException(FORMAT_ERROR_MESSAGE, lineNum);
+ }
+
+ String binaryTypeName = components[0].trim();
+ String instantiable = components[1].trim();
+
+ if (binaryTypeName.length() == 0 || instantiable.length() == 0) {
+ throw new ParseException(FORMAT_ERROR_MESSAGE, lineNum);
+ }
+
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ Class clazz = Class.forName(binaryTypeName, false, contextClassLoader);
+ // TODO: Validate the instantiable string better.
+ whitelist.put(clazz, Boolean.valueOf(instantiable));
+ }
+
+ line = br.readLine();
+ lineNum++;
+ }
+
+ return new StandardSerializationPolicy(whitelist);
+ }
+
+ private SerializationPolicyLoader() {
+ }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/SerializationPolicyProvider.java b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyProvider.java
new file mode 100644
index 0000000..6b8bb87
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/SerializationPolicyProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2007 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.user.server.rpc;
+
+/**
+ * Used to obtain a {@link SerializationPolicy} for a given module base URL and
+ * serialization policy strong name.
+ */
+public interface SerializationPolicyProvider {
+ /**
+ * Returns a {@link SerializationPolicy} for a given module base URL and
+ * serialization policy strong name.
+ *
+ * @param moduleBaseURL the URL for the module
+ * @param serializationPolicyStrongName strong name of the serialization
+ * policy for the specified module URL
+ * @return a {@link SerializationPolicy} for a given module base URL and RPC
+ * strong name; must not return <code>null</code>
+ */
+ SerializationPolicy getSerializationPolicy(String moduleBaseURL,
+ String serializationPolicyStrongName);
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java b/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java
new file mode 100644
index 0000000..84689a5
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicy.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2007 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.user.server.rpc.impl;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.server.rpc.SerializationPolicy;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A serialization policy compatible with GWT 1.3.3 RPC. This is used when no
+ * serialization policy file is present.
+ *
+ * <p>
+ * The set of allowed types are:
+ * </p>
+ * <ol>
+ * <li>Primitives</li>
+ * <li>Types assignable to {@link IsSerializable}</li>
+ * <li>Types with custom field serializers</li>
+ * <li>Arrays of the above types</li>
+ * </ol>
+ * <p>
+ * Types that derive from {@link Serializable} but do not meet any of the above
+ * criteria may not be serialized as leaf types. However, their fields may be
+ * serialized as super types of a legal type.
+ * </p>
+ */
+public class LegacySerializationPolicy extends SerializationPolicy {
+
+ /**
+ * Many JRE types would appear to be {@link Serializable} on the server.
+ * However, clients would not see these types as being {@link Serializable}
+ * due to mismatches between the GWT JRE emulation and the real JRE. As a
+ * workaround, this blacklist specifies a list of problematic types which
+ * should be seen as not implementing {@link Serializable} for the purpose
+ * matching the client's expectations. Note that a type on this list may still
+ * be serializable via a custom serializer.
+ */
+ private static final Class[] JRE_BLACKLIST = {
+ java.lang.ArrayStoreException.class, java.lang.AssertionError.class,
+ java.lang.Boolean.class, java.lang.Byte.class, java.lang.Character.class,
+ java.lang.Class.class, java.lang.ClassCastException.class,
+ java.lang.Double.class, java.lang.Error.class, java.lang.Exception.class,
+ java.lang.Float.class, java.lang.IllegalArgumentException.class,
+ java.lang.IllegalStateException.class,
+ java.lang.IndexOutOfBoundsException.class, java.lang.Integer.class,
+ java.lang.Long.class, java.lang.NegativeArraySizeException.class,
+ java.lang.NullPointerException.class, java.lang.Number.class,
+ java.lang.NumberFormatException.class, java.lang.RuntimeException.class,
+ java.lang.Short.class, java.lang.StackTraceElement.class,
+ java.lang.String.class, java.lang.StringBuffer.class,
+ java.lang.StringIndexOutOfBoundsException.class,
+ java.lang.Throwable.class, java.lang.UnsupportedOperationException.class,
+ java.util.ArrayList.class,
+ java.util.ConcurrentModificationException.class, java.util.Date.class,
+ java.util.EmptyStackException.class, java.util.EventObject.class,
+ java.util.HashMap.class, java.util.HashSet.class,
+ java.util.MissingResourceException.class,
+ java.util.NoSuchElementException.class, java.util.Stack.class,
+ java.util.TooManyListenersException.class, java.util.Vector.class,};
+
+ private static final Set JRE_BLACKSET = new HashSet(
+ Arrays.asList(JRE_BLACKLIST));
+
+ private static final LegacySerializationPolicy sInstance = new LegacySerializationPolicy();
+
+ public static LegacySerializationPolicy getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * Singleton.
+ */
+ private LegacySerializationPolicy() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldDerializeFields(java.lang.String)
+ */
+ public boolean shouldDeserializeFields(Class clazz) {
+ return isFieldSerializable(clazz);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldSerializeFields(java.lang.String)
+ */
+ public boolean shouldSerializeFields(Class clazz) {
+ return isFieldSerializable(clazz);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateDeserialize(java.lang.String)
+ */
+ public void validateDeserialize(Class clazz) throws SerializationException {
+ if (!isInstantiable(clazz)) {
+ throw new SerializationException(
+ "Type '"
+ + clazz.getName()
+ + "' was not assignable to '"
+ + IsSerializable.class.getName()
+ + "' and did not have a custom field serializer. For security purposes, this type will not be deserialized.");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateSerialize(java.lang.String)
+ */
+ public void validateSerialize(Class clazz) throws SerializationException {
+ if (!isInstantiable(clazz)) {
+ throw new SerializationException(
+ "Type '"
+ + clazz.getName()
+ + "' was not assignable to '"
+ + IsSerializable.class.getName()
+ + "' and did not have a custom field serializer. For security purposes, this type will not be serialized.");
+ }
+ }
+
+ /**
+ * Field serializable types are primitives, {@line IsSerializable},
+ * {@link Serializable}, types with custom serializers, and any arrays of
+ * those types.
+ */
+ private boolean isFieldSerializable(Class clazz) {
+ if (isInstantiable(clazz)) {
+ return true;
+ }
+ if (Serializable.class.isAssignableFrom(clazz)) {
+ return !JRE_BLACKSET.contains(clazz);
+ }
+ return false;
+ }
+
+ /**
+ * Instantiable types are primitives, {@line IsSerializable}, types with
+ * custom serializers, and any arrays of those types. Merely
+ * {@link Serializable} types cannot be instantiated or serialized directly
+ * (only as super types of legacy serializable types).
+ */
+ private boolean isInstantiable(Class clazz) {
+ if (clazz.isPrimitive()) {
+ return true;
+ }
+ if (clazz.isArray()) {
+ return isInstantiable(clazz.getComponentType());
+ }
+ if (IsSerializable.class.isAssignableFrom(clazz)) {
+ return true;
+ }
+ return SerializabilityUtil.hasCustomFieldSerializer(clazz) != null;
+ }
+
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializableTypeOracleImpl.java b/user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java
similarity index 78%
rename from user/src/com/google/gwt/user/server/rpc/impl/ServerSerializableTypeOracleImpl.java
rename to user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java
index 43d7e6a..3fb9849 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializableTypeOracleImpl.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -15,9 +15,7 @@
*/
package com.google.gwt.user.server.rpc.impl;
-import com.google.gwt.user.client.rpc.IsSerializable;
-
-import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
@@ -30,12 +28,12 @@
import java.util.zip.CRC32;
/**
- * This class defines an implementation of a serializable type oracle that can
- * be used by the rpc servlet.
+ * Serialization utility class used by the server-side RPC code.
*/
-public class ServerSerializableTypeOracleImpl implements
- ServerSerializableTypeOracle {
+public class SerializabilityUtil {
+ public static final String DEFAULT_ENCODING = "UTF-8";
+
/**
* A permanent cache of all computed CRCs on classes. This is safe to do
* because a Class is guaranteed not to change within the lifetime of a
@@ -51,7 +49,10 @@
*/
private static final Map classCustomSerializerCache = new HashMap();
+ private static final String JRE_SERIALIZER_PACKAGE = "com.google.gwt.user.client.rpc.core";
+
private static final Map SERIALIZED_PRIMITIVE_TYPE_NAMES = new HashMap();
+
private static final Set TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES = new HashSet();
static {
@@ -79,44 +80,7 @@
TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.add(Throwable.class);
}
- private static boolean containsCachedSerializerForClass(Class instanceType) {
- synchronized (classCustomSerializerCache) {
- return classCustomSerializerCache.containsKey(instanceType);
- }
- }
-
- private static String getCachedCRCForClass(Class instanceType) {
- synchronized (classCRC32Cache) {
- return (String) classCRC32Cache.get(instanceType);
- }
- }
-
- private static Class getCachedSerializerForClass(Class instanceType) {
- synchronized (classCustomSerializerCache) {
- return (Class) classCustomSerializerCache.get(instanceType);
- }
- }
-
- private static void putCachedCRCForClass(Class instanceType, String crc32) {
- synchronized (classCRC32Cache) {
- classCRC32Cache.put(instanceType, crc32);
- }
- }
-
- private static void putCachedSerializerForClass(Class instanceType,
- Class customFieldSerializer) {
- synchronized (classCustomSerializerCache) {
- classCustomSerializerCache.put(instanceType, customFieldSerializer);
- }
- }
-
- private String[] packagePaths;
-
- public ServerSerializableTypeOracleImpl(String[] packagePaths) {
- this.packagePaths = packagePaths;
- }
-
- public Field[] applyFieldSerializationPolicy(Field[] fields) {
+ public static Field[] applyFieldSerializationPolicy(Field[] fields) {
ArrayList fieldList = new ArrayList();
for (int index = 0; index < fields.length; ++index) {
Field field = fields[index];
@@ -148,7 +112,7 @@
return fieldSubset;
}
- public SerializedInstanceReference decodeSerializedInstanceReference(
+ public static SerializedInstanceReference decodeSerializedInstanceReference(
String encodedSerializedInstanceReference) {
final String[] components = encodedSerializedInstanceReference.split(SerializedInstanceReference.SERIALIZED_REFERENCE_SEPARATOR);
return new SerializedInstanceReference() {
@@ -162,24 +126,29 @@
};
}
- public String encodeSerializedInstanceReference(Class instanceType) {
+ public static String encodeSerializedInstanceReference(Class instanceType) {
return instanceType.getName()
+ SerializedInstanceReference.SERIALIZED_REFERENCE_SEPARATOR
+ getSerializationSignature(instanceType);
}
- public String getSerializationSignature(Class instanceType) {
+ public static String getSerializationSignature(Class instanceType) {
String result = getCachedCRCForClass(instanceType);
if (result == null) {
CRC32 crc = new CRC32();
- generateSerializationSignature(instanceType, crc);
+ try {
+ generateSerializationSignature(instanceType, crc);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(
+ "Could not compute the serialization signature", e);
+ }
result = Long.toString(crc.getValue());
putCachedCRCForClass(instanceType, result);
}
return result;
}
- public String getSerializedTypeName(Class instanceType) {
+ public static String getSerializedTypeName(Class instanceType) {
if (instanceType.isPrimitive()) {
return (String) SERIALIZED_PRIMITIVE_TYPE_NAMES.get(instanceType.getName());
}
@@ -190,7 +159,7 @@
/**
* This method treats arrays in a special way.
*/
- public Class hasCustomFieldSerializer(Class instanceType) {
+ public static Class hasCustomFieldSerializer(Class instanceType) {
assert (instanceType != null);
Class result = getCachedSerializerForClass(instanceType);
if (result != null) {
@@ -207,24 +176,10 @@
return result;
}
- public boolean isSerializable(Class instanceType) {
- if (instanceType.isArray()) {
- return isSerializable(instanceType.getComponentType());
- }
- if (instanceType.isPrimitive()) {
- return true;
- }
- if (IsSerializable.class.isAssignableFrom(instanceType) ||
- Serializable.class.isAssignableFrom(instanceType)) {
- return true;
- }
- return hasCustomFieldSerializer(instanceType) != null;
- }
-
/**
* This method treats arrays in a special way.
*/
- private Class computeHasCustomFieldSerializer(Class instanceType) {
+ private static Class computeHasCustomFieldSerializer(Class instanceType) {
assert (instanceType != null);
String qualifiedTypeName;
@@ -244,26 +199,31 @@
qualifiedTypeName = instanceType.getName();
}
- Class customSerializer = getCustomFieldSerializer(qualifiedTypeName
- + "_CustomFieldSerializer");
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ String simpleSerializerName = qualifiedTypeName + "_CustomFieldSerializer";
+ Class customSerializer = getCustomFieldSerializer(classLoader,
+ simpleSerializerName);
if (customSerializer != null) {
return customSerializer;
}
// Try with the regular name
- String simpleSerializerName = qualifiedTypeName + "_CustomFieldSerializer";
- for (int i = 0; i < packagePaths.length; ++i) {
- Class customSerializerClass = getCustomFieldSerializer(packagePaths[i]
- + "." + simpleSerializerName);
- if (customSerializerClass != null) {
- return customSerializerClass;
- }
+ Class customSerializerClass = getCustomFieldSerializer(classLoader,
+ JRE_SERIALIZER_PACKAGE + "." + simpleSerializerName);
+ if (customSerializerClass != null) {
+ return customSerializerClass;
}
return null;
}
- private boolean excludeImplementationFromSerializationSignature(
+ private static boolean containsCachedSerializerForClass(Class instanceType) {
+ synchronized (classCustomSerializerCache) {
+ return classCustomSerializerCache.containsKey(instanceType);
+ }
+ }
+
+ private static boolean excludeImplementationFromSerializationSignature(
Class instanceType) {
if (TYPES_WHOSE_IMPLEMENTATION_IS_EXCLUDED_FROM_SIGNATURES.contains(instanceType)) {
return true;
@@ -271,8 +231,10 @@
return false;
}
- private void generateSerializationSignature(Class instanceType, CRC32 crc) {
- crc.update(getSerializedTypeName(instanceType).getBytes());
+ private static void generateSerializationSignature(Class instanceType,
+ CRC32 crc) throws UnsupportedEncodingException {
+ crc.update(getSerializedTypeName(instanceType).getBytes(
+ DEFAULT_ENCODING));
if (excludeImplementationFromSerializationSignature(instanceType)) {
return;
@@ -289,8 +251,9 @@
Field field = fields[i];
assert (field != null);
- crc.update(field.getName().getBytes());
- crc.update(getSerializedTypeName(field.getType()).getBytes());
+ crc.update(field.getName().getBytes(DEFAULT_ENCODING));
+ crc.update(getSerializedTypeName(field.getType()).getBytes(
+ DEFAULT_ENCODING));
}
Class superClass = instanceType.getSuperclass();
@@ -300,18 +263,39 @@
}
}
- private Class getCustomFieldSerializer(String qualifiedSerialzierName) {
- Class customSerializerClass;
+ private static String getCachedCRCForClass(Class instanceType) {
+ synchronized (classCRC32Cache) {
+ return (String) classCRC32Cache.get(instanceType);
+ }
+ }
+
+ private static Class getCachedSerializerForClass(Class instanceType) {
+ synchronized (classCustomSerializerCache) {
+ return (Class) classCustomSerializerCache.get(instanceType);
+ }
+ }
+
+ private static Class getCustomFieldSerializer(ClassLoader classLoader,
+ String qualifiedSerialzierName) {
try {
- customSerializerClass = Class.forName(qualifiedSerialzierName, false,
- this.getClass().getClassLoader());
+ Class customSerializerClass = Class.forName(qualifiedSerialzierName,
+ false, classLoader);
return customSerializerClass;
} catch (ClassNotFoundException e) {
return null;
}
}
- private String[] getPackagePaths() {
- return packagePaths;
+ private static void putCachedCRCForClass(Class instanceType, String crc32) {
+ synchronized (classCRC32Cache) {
+ classCRC32Cache.put(instanceType, crc32);
+ }
+ }
+
+ private static void putCachedSerializerForClass(Class instanceType,
+ Class customFieldSerializer) {
+ synchronized (classCustomSerializerCache) {
+ classCustomSerializerCache.put(instanceType, customFieldSerializer);
+ }
}
}
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializableTypeOracle.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializableTypeOracle.java
deleted file mode 100644
index 2b4593a..0000000
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializableTypeOracle.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2006 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.user.server.rpc.impl;
-
-import java.lang.reflect.Field;
-
-/**
- * This interface defines a serializable type oracle that can be used by the rpc
- * servlet.
- */
-public interface ServerSerializableTypeOracle {
-
- /**
- * Given a set of fields for a given instance type, return an array of fields
- * that represents the fields that can actually be serialized.
- */
- Field[] applyFieldSerializationPolicy(Field[] declaredFields);
-
- /**
- * Given an encoded serialized instance reference, return an object
- * that can be queried for its component parts.
- */
- SerializedInstanceReference decodeSerializedInstanceReference(
- String encodedSerializedInstanceReference);
-
- /**
- * Given an instance type generate an encoded string that represents the
- * serialized instance.
- */
- String encodeSerializedInstanceReference(Class instanceType);
-
- /**
- * Get the serialization signature for a given instance type.
- */
- String getSerializationSignature(Class instanceType);
-
- /**
- * Get the serialized name of a type instance.
- */
- String getSerializedTypeName(Class instanceType);
-
- /**
- * Return the class object for the custom field serializer for a given
- * instance type.
- *
- * @param instanceType
- * @return Class object for the custom field serializer for the instance type
- * or null if there is no custom field serializer
- */
- Class hasCustomFieldSerializer(Class instanceType);
-
- /**
- * Returns true if the instance type is serializable.
- *
- * @param instanceType
- * @return true if the instance type is serializable
- */
- boolean isSerializable(Class instanceType);
-}
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 1c1b9e8..f41fbf3 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -18,6 +18,9 @@
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamReader;
+import com.google.gwt.user.server.rpc.RPC;
+import com.google.gwt.user.server.rpc.SerializationPolicy;
+import com.google.gwt.user.server.rpc.SerializationPolicyProvider;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
@@ -35,19 +38,20 @@
private final ClassLoader classLoader;
- private ServerSerializableTypeOracle serializableTypeOracle;
-
private String[] stringTable;
- private ArrayList tokenList = new ArrayList();
+ private final ArrayList tokenList = new ArrayList();
+
+ private SerializationPolicy serializationPolicy = RPC.getDefaultSerializationPolicy();
private int tokenListIndex;
- public ServerSerializationStreamReader(
- ServerSerializableTypeOracle serializableTypeOracle,
- ClassLoader classLoader) {
+ private final SerializationPolicyProvider serializationPolicyProvider;
+
+ public ServerSerializationStreamReader(ClassLoader classLoader,
+ SerializationPolicyProvider serializationPolicyProvider) {
this.classLoader = classLoader;
- this.serializableTypeOracle = serializableTypeOracle;
+ this.serializationPolicyProvider = serializationPolicyProvider;
}
public Object deserializeValue(Class type) throws SerializationException {
@@ -74,6 +78,10 @@
return readObject();
}
+ public SerializationPolicy getSerializationPolicy() {
+ return serializationPolicy;
+ }
+
public void prepareToRead(String encodedTokens) throws SerializationException {
tokenList.clear();
tokenListIndex = 0;
@@ -91,6 +99,22 @@
// Read the type name table
//
deserializeStringTable();
+
+ // If this stream encodes resource file information, read it and get a
+ // SerializationPolicy
+ if (hasSerializationPolicyInfo()) {
+ String moduleBaseURL = readString();
+ String strongName = readString();
+ if (serializationPolicyProvider != null) {
+ serializationPolicy = serializationPolicyProvider.getSerializationPolicy(
+ moduleBaseURL, strongName);
+
+ if (serializationPolicy == null) {
+ throw new NullPointerException(
+ "serializationPolicyProvider.getSerializationPolicy()");
+ }
+ }
+ }
}
public boolean readBoolean() {
@@ -126,27 +150,26 @@
return Short.parseShort(extract());
}
- public String readString() throws SerializationException {
+ public String readString() {
return getString(readInt());
}
protected Object deserialize(String typeSignature)
throws SerializationException {
Object instance = null;
- SerializedInstanceReference serializedInstRef = serializableTypeOracle.decodeSerializedInstanceReference(typeSignature);
+ SerializedInstanceReference serializedInstRef = SerializabilityUtil.decodeSerializedInstanceReference(typeSignature);
try {
Class instanceClass = Class.forName(serializedInstRef.getName(), false,
classLoader);
- if (!serializableTypeOracle.isSerializable(instanceClass)) {
- throw new SerializationException("Class '" + instanceClass.getName()
- + "' is not serializable");
- }
+ assert (serializationPolicy != null);
+
+ serializationPolicy.validateDeserialize(instanceClass);
validateTypeVersions(instanceClass, serializedInstRef);
- Class customSerializer = serializableTypeOracle.hasCustomFieldSerializer(instanceClass);
+ Class customSerializer = SerializabilityUtil.hasCustomFieldSerializer(instanceClass);
instance = instantiate(customSerializer, instanceClass);
@@ -224,7 +247,7 @@
Object instance) throws SerializationException, IllegalAccessException,
NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
Field[] declFields = instanceClass.getDeclaredFields();
- Field[] serializableFields = serializableTypeOracle.applyFieldSerializationPolicy(declFields);
+ Field[] serializableFields = SerializabilityUtil.applyFieldSerializationPolicy(declFields);
for (int index = 0; index < serializableFields.length; ++index) {
Field declField = serializableFields[index];
@@ -249,9 +272,8 @@
}
Class superClass = instanceClass.getSuperclass();
- if (superClass != null && serializableTypeOracle.isSerializable(superClass)) {
- deserializeImpl(
- serializableTypeOracle.hasCustomFieldSerializer(superClass),
+ if (serializationPolicy.shouldDeserializeFields(superClass)) {
+ deserializeImpl(SerializabilityUtil.hasCustomFieldSerializer(superClass),
superClass, instance);
}
}
@@ -295,7 +317,7 @@
return;
}
- String serverTypeSignature = serializableTypeOracle.getSerializationSignature(instanceClass);
+ String serverTypeSignature = SerializabilityUtil.getSerializationSignature(instanceClass);
if (!clientTypeSignature.equals(serverTypeSignature)) {
throw new SerializationException("Invalid type signature for "
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
index df6e1dc..d3883bc 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -18,6 +18,7 @@
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter;
+import com.google.gwt.user.server.rpc.SerializationPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -120,8 +121,7 @@
* character escape sequence. This is necessary if the raw character could be
* consumed and/or interpreted as a special character when the JSON encoded
* response is evaluated. For example, 0x2028 and 0x2029 are alternate line
- * endings for JS per ECMA-232, which are respected by Firefox and
- * Mozilla.
+ * endings for JS per ECMA-232, which are respected by Firefox and Mozilla.
*
* @param ch character to check
* @return <code>true</code> if the character requires the \\uXXXX unicode
@@ -211,8 +211,6 @@
private IdentityHashMap objectMap = new IdentityHashMap();
- private ServerSerializableTypeOracle serializableTypeOracle;
-
private HashMap stringMap = new HashMap();
private ArrayList stringTable = new ArrayList();
@@ -221,9 +219,11 @@
private int tokenListCharCount;
+ private final SerializationPolicy serializationPolicy;
+
public ServerSerializationStreamWriter(
- ServerSerializableTypeOracle serializableTypeOracle) {
- this.serializableTypeOracle = serializableTypeOracle;
+ SerializationPolicy serializationPolicy) {
+ this.serializationPolicy = serializationPolicy;
}
public void prepareToWrite() {
@@ -313,9 +313,9 @@
protected String getObjectTypeSignature(Object instance) {
if (shouldEnforceTypeVersioning()) {
- return serializableTypeOracle.encodeSerializedInstanceReference(instance.getClass());
+ return SerializabilityUtil.encodeSerializedInstanceReference(instance.getClass());
} else {
- return serializableTypeOracle.getSerializedTypeName(instance.getClass());
+ return SerializabilityUtil.getSerializedTypeName(instance.getClass());
}
}
@@ -325,7 +325,12 @@
protected void serialize(Object instance, String typeSignature)
throws SerializationException {
- serializeImpl(instance, instance.getClass());
+ assert (instance != null);
+
+ Class clazz = instance.getClass();
+ serializationPolicy.validateSerialize(clazz);
+
+ serializeImpl(instance, clazz);
}
private void serializeClass(Object instance, Class instanceClass)
@@ -333,7 +338,7 @@
assert (instance != null);
Field[] declFields = instanceClass.getDeclaredFields();
- Field[] serializableFields = serializableTypeOracle.applyFieldSerializationPolicy(declFields);
+ Field[] serializableFields = SerializabilityUtil.applyFieldSerializationPolicy(declFields);
for (int index = 0; index < serializableFields.length; ++index) {
Field declField = serializableFields[index];
assert (declField != null);
@@ -365,17 +370,16 @@
}
Class superClass = instanceClass.getSuperclass();
- if (superClass != null && serializableTypeOracle.isSerializable(superClass)) {
+ if (serializationPolicy.shouldSerializeFields(superClass)) {
serializeImpl(instance, superClass);
}
}
private void serializeImpl(Object instance, Class instanceClass)
throws SerializationException {
-
assert (instance != null);
- Class customSerializer = serializableTypeOracle.hasCustomFieldSerializer(instanceClass);
+ Class customSerializer = SerializabilityUtil.hasCustomFieldSerializer(instanceClass);
if (customSerializer != null) {
serializeWithCustomSerializer(customSerializer, instance, instanceClass);
} else {
@@ -432,7 +436,7 @@
buffer.append(",");
buffer.append(getFlags());
buffer.append(",");
- buffer.append(SERIALIZATION_STREAM_VERSION);
+ buffer.append(getVersion());
}
private void writePayload(StringBuffer buffer) {
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java b/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java
new file mode 100644
index 0000000..fbccbb7
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicy.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2007 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.user.server.rpc.impl;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.server.rpc.SerializationPolicy;
+
+import java.util.Map;
+
+/**
+ * Standard implementation of a {@link SerializationPolicy}.
+ */
+public class StandardSerializationPolicy extends SerializationPolicy {
+ private final Map/* <Class,Boolean> */whitelist;
+
+ /**
+ * Constructs a {@link SerializationPolicy} from a {@link Map}.
+ */
+ public StandardSerializationPolicy(Map/* <Class,Boolean> */whitelist) {
+ if (whitelist == null) {
+ throw new NullPointerException("whitelist");
+ }
+
+ this.whitelist = whitelist;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldDerializeFields(java.lang.String)
+ */
+ public boolean shouldDeserializeFields(Class clazz) {
+ return isFieldSerializable(clazz);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#shouldSerializeFields(java.lang.String)
+ */
+ public boolean shouldSerializeFields(Class clazz) {
+ return isFieldSerializable(clazz);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateDeserialize(java.lang.String)
+ */
+ public void validateDeserialize(Class clazz) throws SerializationException {
+ if (!isInstantiable(clazz)) {
+ throw new SerializationException(
+ "Type '"
+ + clazz.getName()
+ + "' was not included in the set of types which can be deserialized by this SerializationPolicy. For security purposes, this type will not be deserialized.");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.server.rpc.SerializationPolicy#validateSerialize(java.lang.String)
+ */
+ public void validateSerialize(Class clazz) throws SerializationException {
+ if (!isInstantiable(clazz)) {
+ throw new SerializationException(
+ "Type '"
+ + clazz.getName()
+ + "' was not included in the set of types which can be serialized by this SerializationPolicy. For security purposes, this type will not be serialized.");
+ }
+ }
+
+ /**
+ * Field serializable types are primitives and types on the whitelist.
+ */
+ private boolean isFieldSerializable(Class clazz) {
+ if (clazz.isPrimitive()) {
+ return true;
+ }
+ return whitelist.containsKey(clazz);
+ }
+
+ /**
+ * Instantiable types are primitives and types on the whitelist which can be
+ * instantiated.
+ */
+ private boolean isInstantiable(Class clazz) {
+ if (clazz.isPrimitive()) {
+ return true;
+ }
+ Boolean instantiable = (Boolean) whitelist.get(clazz);
+ return (instantiable != null && instantiable.booleanValue());
+ }
+}
diff --git a/user/test/com/google/gwt/user/RPCSuite.java b/user/test/com/google/gwt/user/RPCSuite.java
index bf12f4a..6c5cec9 100644
--- a/user/test/com/google/gwt/user/RPCSuite.java
+++ b/user/test/com/google/gwt/user/RPCSuite.java
@@ -19,30 +19,38 @@
import com.google.gwt.user.client.rpc.CustomFieldSerializerTest;
import com.google.gwt.user.client.rpc.InheritanceTest;
import com.google.gwt.user.client.rpc.ObjectGraphTest;
-import com.google.gwt.user.client.rpc.RemoteServiceServletTest;
import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
import com.google.gwt.user.client.rpc.ValueTypesTest;
+import com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilderTest;
import com.google.gwt.user.server.rpc.RPCTest;
+import com.google.gwt.user.server.rpc.SerializationPolicyLoaderTest;
+import com.google.gwt.user.server.rpc.impl.LegacySerializationPolicyTest;
+import com.google.gwt.user.server.rpc.impl.StandardSerializationPolicyTest;
import junit.framework.Test;
import junit.framework.TestSuite;
/**
- * TODO: document me.
+ * A collection of TestCases for the RPC system.
*/
public class RPCSuite {
public static Test suite() {
TestSuite suite = new TestSuite("Test for com.google.gwt.user.client.rpc");
+ suite.addTestSuite(SerializableTypeOracleBuilderTest.class);
suite.addTestSuite(RPCTest.class);
suite.addTestSuite(ValueTypesTest.class);
suite.addTestSuite(InheritanceTest.class);
suite.addTestSuite(CollectionsTest.class);
suite.addTestSuite(CustomFieldSerializerTest.class);
suite.addTestSuite(ObjectGraphTest.class);
- suite.addTestSuite(RemoteServiceServletTest.class);
+ suite.addTestSuite(com.google.gwt.user.client.rpc.RemoteServiceServletTest.class);
+ suite.addTestSuite(com.google.gwt.user.server.rpc.RemoteServiceServletTest.class);
suite.addTestSuite(UnicodeEscapingTest.class);
-
+ suite.addTestSuite(LegacySerializationPolicyTest.class);
+ suite.addTestSuite(StandardSerializationPolicyTest.class);
+ suite.addTestSuite(SerializationPolicyLoaderTest.class);
+
return suite;
}
}
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
index 3e62eca..94d5518 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -22,6 +22,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Vector;
/**
@@ -30,6 +31,62 @@
public class CollectionsTest extends GWTTestCase {
private static final int TEST_DELAY = 5000;
+ private CollectionsTestServiceAsync collectionsTestService;
+
+ public void _testDateArray() {
+ delayTestFinish(TEST_DELAY);
+
+ CollectionsTestServiceAsync service = getServiceAsync();
+ final Date[] expected = TestSetFactory.createDateArray();
+ service.echo(expected, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(Object result) {
+ assertNotNull(result);
+ assertTrue(TestSetValidator.equals(expected, (Date[]) result));
+ finishTest();
+ }
+ });
+ }
+
+ public void disabledTestLongArray() {
+ delayTestFinish(TEST_DELAY);
+
+ CollectionsTestServiceAsync service = getServiceAsync();
+ final Long[] expected = TestSetFactory.createLongArray();
+ service.echo(expected, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(Object result) {
+ assertNotNull(result);
+ assertTrue(TestSetValidator.equals(expected, (Long[]) result));
+ finishTest();
+ }
+ });
+ }
+
+ public void disabledTestPrimitiveLongArray() {
+ delayTestFinish(TEST_DELAY);
+
+ CollectionsTestServiceAsync service = getServiceAsync();
+ final long[] expected = TestSetFactory.createPrimitiveLongArray();
+ service.echo(expected, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(Object result) {
+ assertNotNull(result);
+ assertTrue(TestSetValidator.equals(expected, (long[]) result));
+ finishTest();
+ }
+ });
+ }
+
public String getModuleName() {
return "com.google.gwt.user.RPCSuite";
}
@@ -40,7 +97,7 @@
CollectionsTestServiceAsync service = getServiceAsync();
service.echo(TestSetFactory.createArrayList(), new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -58,7 +115,7 @@
final Boolean[] expected = TestSetFactory.createBooleanArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -76,7 +133,7 @@
final Byte[] expected = TestSetFactory.createByteArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -94,7 +151,7 @@
final Character[] expected = TestSetFactory.createCharArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -105,24 +162,6 @@
});
}
- public void _testDateArray() {
- delayTestFinish(TEST_DELAY);
-
- CollectionsTestServiceAsync service = getServiceAsync();
- final Date[] expected = TestSetFactory.createDateArray();
- service.echo(expected, new AsyncCallback() {
- public void onFailure(Throwable caught) {
- fail(caught.toString());
- }
-
- public void onSuccess(Object result) {
- assertNotNull(result);
- assertTrue(TestSetValidator.equals(expected, (Date[]) result));
- finishTest();
- }
- });
- }
-
public void testDoubleArray() {
delayTestFinish(TEST_DELAY);
@@ -130,7 +169,7 @@
final Double[] expected = TestSetFactory.createDoubleArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -141,6 +180,34 @@
});
}
+ /**
+ * This method checks that attempting to return
+ * {@link java.util.Arrays#asList(Object[])} from the server will result in an
+ * InvocationException on the client.
+ */
+ public void testFailureWhenReturningArraysAsList() {
+ delayTestFinish(TEST_DELAY);
+
+ CollectionsTestServiceAsync service = getServiceAsync();
+ final List expected = new ArrayList();
+ for (byte i = 0; i < 10; ++i) {
+ expected.add(new Byte(i));
+ }
+
+ service.getArraysAsList(expected, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ assertTrue(GWT.getTypeName(caught)
+ + " should have been an InvocationException",
+ caught instanceof InvocationException);
+ finishTest();
+ }
+
+ public void onSuccess(Object result) {
+ fail("Expected an InvocationException");
+ }
+ });
+ }
+
public void testFloatArray() {
delayTestFinish(TEST_DELAY);
@@ -148,7 +215,7 @@
final Float[] expected = TestSetFactory.createFloatArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -166,7 +233,7 @@
final HashMap expected = TestSetFactory.createHashMap();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -184,7 +251,7 @@
final HashSet expected = TestSetFactory.createHashSet();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -202,7 +269,7 @@
final Integer[] expected = TestSetFactory.createIntegerArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -213,24 +280,6 @@
});
}
- public void disabledTestLongArray() {
- delayTestFinish(TEST_DELAY);
-
- CollectionsTestServiceAsync service = getServiceAsync();
- final Long[] expected = TestSetFactory.createLongArray();
- service.echo(expected, new AsyncCallback() {
- public void onFailure(Throwable caught) {
- fail(caught.toString());
- }
-
- public void onSuccess(Object result) {
- assertNotNull(result);
- assertTrue(TestSetValidator.equals(expected, (Long[]) result));
- finishTest();
- }
- });
- }
-
public void testPrimitiveBooleanArray() {
delayTestFinish(TEST_DELAY);
@@ -238,7 +287,7 @@
CollectionsTestServiceAsync service = getServiceAsync();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -255,7 +304,7 @@
CollectionsTestServiceAsync service = getServiceAsync();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -272,7 +321,7 @@
final char[] expected = TestSetFactory.createPrimitiveCharArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -290,7 +339,7 @@
final double[] expected = TestSetFactory.createPrimitiveDoubleArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -308,7 +357,7 @@
final float[] expected = TestSetFactory.createPrimitiveFloatArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -326,7 +375,7 @@
final int[] expected = TestSetFactory.createPrimitiveIntegerArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -337,24 +386,6 @@
});
}
- public void disabledTestPrimitiveLongArray() {
- delayTestFinish(TEST_DELAY);
-
- CollectionsTestServiceAsync service = getServiceAsync();
- final long[] expected = TestSetFactory.createPrimitiveLongArray();
- service.echo(expected, new AsyncCallback() {
- public void onFailure(Throwable caught) {
- fail(caught.toString());
- }
-
- public void onSuccess(Object result) {
- assertNotNull(result);
- assertTrue(TestSetValidator.equals(expected, (long[]) result));
- finishTest();
- }
- });
- }
-
public void testPrimitiveShortArray() {
delayTestFinish(TEST_DELAY);
@@ -362,7 +393,7 @@
final short[] expected = TestSetFactory.createPrimitiveShortArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -373,41 +404,6 @@
});
}
- public void testStringArray() {
- delayTestFinish(TEST_DELAY);
-
- CollectionsTestServiceAsync service = getServiceAsync();
- final String[] expected = TestSetFactory.createStringArray();
- service.echo(expected, new AsyncCallback() {
- public void onFailure(Throwable caught) {
- fail(caught.toString());
- }
-
- public void onSuccess(Object result) {
- assertNotNull(result);
- assertTrue(TestSetValidator.equals(expected, (String[]) result));
- finishTest();
- }
- });
- }
-
- public void testStringArrayArray() {
- delayTestFinish(TEST_DELAY);
-
- CollectionsTestServiceAsync service = getServiceAsync();
- final String[][] expected = new String[][] { new String[] { "hello" }, new String[] { "bye" } };
- service.echo(expected, new AsyncCallback() {
- public void onFailure(Throwable caught) {
- fail(caught.toString());
- }
-
- public void onSuccess(Object result) {
- assertNotNull(result);
- finishTest();
- }
- });
- }
-
public void testShortArray() {
delayTestFinish(TEST_DELAY);
@@ -415,7 +411,7 @@
final Short[] expected = TestSetFactory.createShortArray();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -426,6 +422,42 @@
});
}
+ public void testStringArray() {
+ delayTestFinish(TEST_DELAY);
+
+ CollectionsTestServiceAsync service = getServiceAsync();
+ final String[] expected = TestSetFactory.createStringArray();
+ service.echo(expected, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(Object result) {
+ assertNotNull(result);
+ assertTrue(TestSetValidator.equals(expected, (String[]) result));
+ finishTest();
+ }
+ });
+ }
+
+ public void testStringArrayArray() {
+ delayTestFinish(TEST_DELAY);
+
+ CollectionsTestServiceAsync service = getServiceAsync();
+ final String[][] expected = new String[][] {
+ new String[] {"hello"}, new String[] {"bye"}};
+ service.echo(expected, new AsyncCallback() {
+ public void onFailure(Throwable caught) {
+ TestSetValidator.rethrowException(caught);
+ }
+
+ public void onSuccess(Object result) {
+ assertNotNull(result);
+ finishTest();
+ }
+ });
+ }
+
public void testVector() {
delayTestFinish(TEST_DELAY);
@@ -433,7 +465,7 @@
final Vector expected = TestSetFactory.createVector();
service.echo(expected, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -452,6 +484,4 @@
}
return collectionsTestService;
}
-
- private CollectionsTestServiceAsync collectionsTestService;
}
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
index 710b96a..cd739ad 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
@@ -19,6 +19,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Vector;
/**
@@ -94,7 +95,7 @@
Short[] echo(Short[] value) throws CollectionsTestServiceException;
String[] echo(String[] value) throws CollectionsTestServiceException;
-
+
String[][] echo(String[][] value) throws CollectionsTestServiceException;
/**
@@ -102,4 +103,13 @@
* @gwt.typeArgs <com.google.gwt.user.client.rpc.IsSerializable>
*/
Vector echo(Vector value) throws CollectionsTestServiceException;
+
+ /**
+ * This method is used to test that trying to return Arrays.asList will result
+ * in an InvocationException on the client.
+ *
+ * @gwt.typeArgs value <java.lang.Byte>
+ * @gwt.typeArgs <java.lang.Byte>
+ */
+ List getArraysAsList(List value);
}
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
index bcd4c71..103e0ee 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
@@ -19,6 +19,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Vector;
/**
@@ -66,8 +67,10 @@
void echo(Short[] value, AsyncCallback callback);
void echo(String[] value, AsyncCallback callback);
-
+
void echo(String[][] value, AsyncCallback callback);
void echo(Vector value, AsyncCallback callback);
+
+ void getArraysAsList(List value, AsyncCallback callback);
}
diff --git a/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java b/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java
index 4a87a10..bd37556 100644
--- a/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java
@@ -27,7 +27,6 @@
* TODO: document me.
*/
public class InheritanceTest extends GWTTestCase {
- // private static final int TEST_DELAY = Integer.MAX_VALUE;
private static final int TEST_DELAY = 5000;
private InheritanceTestServiceAsync inheritanceTestService;
@@ -70,7 +69,7 @@
InheritanceTestServiceAsync service = getServiceAsync();
service.echo(InheritanceTestSetFactory.createCircle(), new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail("Unexpected failure");
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -91,7 +90,7 @@
service.echo(new InheritanceTestSetFactory.JavaSerializableClass(3),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -145,7 +144,7 @@
service.echo(InheritanceTestSetFactory.createSerializableClass(),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -166,7 +165,7 @@
service.echo(InheritanceTestSetFactory.createSerializableSubclass(),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -188,7 +187,7 @@
InheritanceTestSetFactory.createSerializableClassWithTransientField(),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
diff --git a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
index 37a6008..e351c71 100644
--- a/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/ObjectGraphTest.java
@@ -36,7 +36,7 @@
service.echo_AcyclicGraph(TestSetFactory.createAcyclicGraph(),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -54,7 +54,7 @@
service.echo_ComplexCyclicGraph(TestSetFactory.createComplexCyclicGraph(),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -72,7 +72,7 @@
final SerializableDoublyLinkedNode node = TestSetFactory.createComplexCyclicGraph();
service.echo_ComplexCyclicGraph(node, node, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -90,7 +90,7 @@
service.echo_TrivialCyclicGraph(TestSetFactory.createTrivialCyclicGraph(),
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
diff --git a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
index cb75234..354c86f 100644
--- a/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/RemoteServiceServletTest.java
@@ -55,7 +55,7 @@
service.test(new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
index 0c48ad8..0bb64d1 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -410,4 +410,13 @@
return true;
}
+
+ public static void rethrowException(Throwable caught) {
+ if (caught instanceof RuntimeException) {
+ throw (RuntimeException) caught;
+ } else {
+ throw new RuntimeException(caught);
+ }
+ }
+
}
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
index bb5976c..b331c97 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
@@ -52,7 +52,7 @@
getService().getStringContainingCharacterRange(0, CHARACTER_RANGE_SIZE,
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
diff --git a/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java b/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java
index dca3d8c..2d3d190 100644
--- a/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/ValueTypesTest.java
@@ -34,7 +34,7 @@
service.echo_FALSE(false, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -51,7 +51,7 @@
service.echo_TRUE(true, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -68,7 +68,7 @@
service.echo((byte) (Byte.MAX_VALUE / (byte) 2), new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -85,7 +85,7 @@
service.echo_MAX_VALUE(Byte.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -102,7 +102,7 @@
service.echo_MIN_VALUE(Byte.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -119,7 +119,7 @@
service.echo((char) (Character.MAX_VALUE / (char) 2), new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -137,7 +137,7 @@
service.echo_MAX_VALUE(Character.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -154,7 +154,7 @@
service.echo_MIN_VALUE(Character.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -171,7 +171,7 @@
service.echo(Double.MAX_VALUE / 2, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -188,7 +188,7 @@
service.echo_MAX_VALUE(Double.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -205,7 +205,7 @@
service.echo_MIN_VALUE(Double.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -226,7 +226,7 @@
service.echo(Double.NaN, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -246,7 +246,7 @@
service.echo(Double.NEGATIVE_INFINITY, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -267,7 +267,7 @@
service.echo(Double.POSITIVE_INFINITY, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -285,7 +285,7 @@
service.echo(Float.MAX_VALUE / 2, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -302,7 +302,7 @@
service.echo_MAX_VALUE(Float.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -319,7 +319,7 @@
service.echo_MIN_VALUE(Float.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -340,7 +340,7 @@
service.echo(Float.NaN, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -360,7 +360,7 @@
service.echo(Float.NEGATIVE_INFINITY, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -381,7 +381,7 @@
service.echo(Float.POSITIVE_INFINITY, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -399,7 +399,7 @@
service.echo(Integer.MAX_VALUE / 2, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -416,7 +416,7 @@
service.echo_MAX_VALUE(Integer.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -433,7 +433,7 @@
service.echo_MIN_VALUE(Integer.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -450,7 +450,7 @@
service.echo(Long.MAX_VALUE / 2, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -471,7 +471,7 @@
service.echo_MAX_VALUE(Long.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -488,7 +488,7 @@
service.echo_MIN_VALUE(Long.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -505,7 +505,7 @@
service.echo((short) (Short.MAX_VALUE / (short) 2), new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -522,7 +522,7 @@
service.echo_MAX_VALUE(Short.MAX_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -539,7 +539,7 @@
service.echo_MIN_VALUE(Short.MIN_VALUE, new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index 25a15a9..d16f8fc 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -15,7 +15,6 @@
*/
package com.google.gwt.user.rebind.rpc;
-import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
@@ -40,14 +39,46 @@
import junit.framework.TestCase;
import java.util.Arrays;
+import java.util.Comparator;
/**
* Used to test the {@link SerializableTypeOracleBuilder}.
*/
public class SerializableTypeOracleBuilderTest extends TestCase {
+ /**
+ * Used to test the results produced by the {@link SerializableTypeOracle}.
+ */
+ static class TypeInfo {
+ boolean maybeInstantiated;
+ final String sourceName;
+
+ TypeInfo(String binaryName, boolean maybeInstantiated) {
+ this.sourceName = makeSourceName(binaryName);
+ this.maybeInstantiated = maybeInstantiated;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof TypeInfo)) {
+ return false;
+ }
+
+ TypeInfo other = (TypeInfo) obj;
+ return sourceName.equals(other.sourceName)
+ && maybeInstantiated == other.maybeInstantiated;
+ }
+
+ public String toString() {
+ return "{ " + sourceName + ", " + Boolean.toString(maybeInstantiated)
+ + " }";
+ }
+ }
+
private static class MockPropertyOracle implements PropertyOracle {
- public String getPropertyValue(TreeLogger logger, String propertyName)
- throws BadPropertyValueException {
+ public String getPropertyValue(TreeLogger logger, String propertyName) {
// Could mock "gwt.enforceRPCTypeVersioning" etc here
return "";
}
@@ -64,40 +95,67 @@
ModuleDefLoader.forceInherit("com.google.gwt.junit.JUnit");
}
- private static String[] getSortedSerializableTypeNames(
- SerializableTypeOracle sto) {
- JType[] actualTypes = sto.getSerializableTypes();
- String[] names = new String[actualTypes.length];
- for (int i = 0; i < actualTypes.length; ++i) {
- names[i] = actualTypes[i].getParameterizedQualifiedSourceName();
+ private static TypeInfo[] getActualTypeInfo(SerializableTypeOracle sto) {
+ JType[] types = sto.getSerializableTypes();
+ TypeInfo[] actual = new TypeInfo[types.length];
+ for (int i = 0; i < types.length; ++i) {
+ JType type = types[i];
+ actual[i] = new TypeInfo(type.getParameterizedQualifiedSourceName(),
+ sto.maybeInstantiated(type));
}
- Arrays.sort(names);
- return names;
+ sort(actual);
+ return actual;
}
private static String makeSourceName(String binaryName) {
return binaryName.replace('$', '.');
}
- private static String toString(String[] strings) {
+ private static void sort(TypeInfo[] typeInfos) {
+ Arrays.sort(typeInfos, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2) {
+ return 0;
+ }
+
+ TypeInfo ti1 = (TypeInfo) o1;
+ TypeInfo ti2 = (TypeInfo) o2;
+
+ return ti1.sourceName.compareTo(ti2.sourceName);
+ }
+ });
+ }
+
+ private static String toString(TypeInfo[] typeInfos) {
StringBuffer sb = new StringBuffer();
sb.append("[");
- for (int i = 0; i < strings.length; ++i) {
+ for (int i = 0; i < typeInfos.length; ++i) {
if (i != 0) {
sb.append(",");
}
- sb.append(strings[i]);
+ sb.append(typeInfos[i].toString());
+ sb.append("\n");
}
sb.append("]");
return sb.toString();
}
+ private static void validateSTO(SerializableTypeOracle sto,
+ TypeInfo[] expected) {
+ sort(expected);
+ TypeInfo[] actual = getActualTypeInfo(sto);
+
+ assertTrue("Expected: \n" + toString(expected) + ",\n Actual: \n"
+ + toString(actual), Arrays.equals(expected, actual));
+ }
+
/**
* This could be your own tree logger, perhaps validating the error output.
*/
private final TreeLogger logger = SUPPRESS_LOGGER_OUTPUT ? TreeLogger.NULL
: new PrintWriterTreeLogger();
+
private final ModuleDef moduleDef;
private final PropertyOracle propertyOracle = new MockPropertyOracle();
@@ -126,30 +184,28 @@
logger, typeOracle);
SerializableTypeOracle sto = stob.build(propertyOracle, testServiceClass);
- String[] actualTypes = getSortedSerializableTypeNames(sto);
- String[] expectedTypes = new String[] {
- IncompatibleRemoteServiceException.class.getName(),
- makeSourceName(CovariantArrays.AA.class.getName()) + "[]",
- makeSourceName(CovariantArrays.BB.class.getName()) + "[]",
- makeSourceName(CovariantArrays.CC.class.getName()) + "[]",
- makeSourceName(CovariantArrays.DD.class.getName()) + "[]",
- makeSourceName(CovariantArrays.A.class.getName()) + "[]",
- makeSourceName(CovariantArrays.B.class.getName()) + "[]",
- makeSourceName(CovariantArrays.B.class.getName()),
- makeSourceName(CovariantArrays.C.class.getName()) + "[]",
- makeSourceName(CovariantArrays.D.class.getName()) + "[]",
- makeSourceName(CovariantArrays.D.class.getName()),
- String.class.getName()};
- Arrays.sort(expectedTypes);
- assertTrue("Expected: " + toString(expectedTypes) + ", Actual: "
- + toString(actualTypes), Arrays.equals(expectedTypes, actualTypes));
+ TypeInfo[] expected = new TypeInfo[] {
+ new TypeInfo(IncompatibleRemoteServiceException.class.getName(), true),
+ new TypeInfo(CovariantArrays.AA.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.BB.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.CC.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.DD.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.A.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.B.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.B.class.getName(), true),
+ new TypeInfo(CovariantArrays.C.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.D.class.getName() + "[]", true),
+ new TypeInfo(CovariantArrays.D.class.getName(), true),
+ new TypeInfo(String.class.getName(), true)};
+ validateSTO(sto, expected);
}
/**
* Tests that a manually serialized type with a field that is not serializable
* does not cause the generator to fail.
*/
- public void testManualSerialization() throws NotFoundException, UnableToCompleteException {
+ public void testManualSerialization() throws NotFoundException,
+ UnableToCompleteException {
JClassType testServiceClass = typeOracle.getType(ManualSerialization.class.getName());
SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
logger, typeOracle);
@@ -197,15 +253,15 @@
logger, typeOracle);
SerializableTypeOracle sto = stob.build(propertyOracle, testServiceClass);
- String[] actualTypes = getSortedSerializableTypeNames(sto);
- String[] expectedTypes = new String[] {
- IncompatibleRemoteServiceException.class.getName(),
- makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()),
- makeSourceName(NotAllSubtypesAreSerializable.D.class.getName()),
- String.class.getName()};
- Arrays.sort(expectedTypes);
- assertTrue("Expected: " + toString(expectedTypes) + ", Actual: "
- + toString(actualTypes), Arrays.equals(expectedTypes, actualTypes));
+ TypeInfo[] expected = new TypeInfo[] {
+ new TypeInfo(IncompatibleRemoteServiceException.class.getName(), true),
+ new TypeInfo(
+ makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()),
+ true),
+ new TypeInfo(
+ makeSourceName(NotAllSubtypesAreSerializable.D.class.getName()),
+ true), new TypeInfo(String.class.getName(), true)};
+ validateSTO(sto, expected);
}
/**
@@ -248,6 +304,12 @@
JClassType testServiceClass = typeOracle.getType(AbstractSerializableTypes.class.getName());
SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
logger, typeOracle);
- stob.build(propertyOracle, testServiceClass);
+ SerializableTypeOracle sto = stob.build(propertyOracle, testServiceClass);
+ TypeInfo[] expected = new TypeInfo[] {
+ new TypeInfo(IncompatibleRemoteServiceException.class.getName(), true),
+ new TypeInfo(AbstractSerializableTypes.AbstractClass.class.getName(),
+ false), new TypeInfo(String.class.getName(), true)};
+
+ validateSTO(sto, expected);
}
}
diff --git a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
index e7cab54..6cb91b0 100644
--- a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
@@ -24,6 +24,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Vector;
/**
@@ -262,4 +263,17 @@
return actual;
}
+
+ /**
+ * Return the result of Arrays.asList(Object[]) to force an
+ * InvocationException on the client.
+ */
+ public List getArraysAsList(List value) {
+ Byte[] retVal = new Byte[10];
+ for (byte i = 0; i < 10; ++i) {
+ retVal[i] = (Byte) value.get(i);
+ }
+
+ return Arrays.asList(retVal);
+ }
}
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
index 2b968b0..fc8e00f 100644
--- a/user/test/com/google/gwt/user/server/rpc/RPCTest.java
+++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -18,6 +18,7 @@
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.SerializableException;
+import com.google.gwt.user.client.rpc.SerializationException;
import junit.framework.TestCase;
@@ -40,7 +41,46 @@
void method1();
}
- private final String VALID_ENCODED_REQUEST = "0\uffff" + // version
+ private final String VALID_ENCODED_REQUEST = "3\uffff" + // version
+ "0\uffff" + // flags
+ "4\uffff" + // string table entry count
+ A.class.getName() + "\uffff" + // string table entry #0
+ "method2" + "\uffff" + // string table entry #1
+ "moduleBaseURL" + "\uffff" + // string table entry #2
+ "whitelistHashcode" + "\uffff" + // string table entry #4
+ "3\uffff" + // module base URL
+ "4\uffff" + // whitelist hashcode
+ "1\uffff" + // interface name
+ "2\uffff" + // method name
+ "0\uffff"; // param count
+
+ private final String INVALID_METHOD_REQUEST = "3\uffff" + // version
+ "0\uffff" + // flags
+ "4\uffff" + // string table entry count
+ A.class.getName() + "\uffff" + // string table entry #0
+ "method3" + "\uffff" + // string table entry #1
+ "moduleBaseURL" + "\uffff" + // string table entry #2
+ "whitelistHashcode" + "\uffff" + // string table entry #4
+ "3\uffff" + // module base URL
+ "4\uffff" + // whitelist hashcode
+ "1\uffff" + // interface name
+ "2\uffff" + // method name
+ "0\uffff"; // param count
+
+ private final String INVALID_INTERFACE_REQUEST = "3\uffff" + // version
+ "0\uffff" + // flags
+ "4\uffff" + // string table entry count
+ B.class.getName() + "\uffff" + // string table entry #0
+ "method1" + "\uffff" + // string table entry #1
+ "moduleBaseURL" + "\uffff" + // string table entry #2
+ "whitelistHashcode" + "\uffff" + // string table entry #4
+ "3\uffff" + // module base URL
+ "4\uffff" + // whitelist hashcode
+ "1\uffff" + // interface name
+ "2\uffff" + // method name
+ "0\uffff"; // param count
+
+ private final String VALID_PRE_RPC_RESOURCE_ENCODED_REQUEST = "2\uffff" + // version
"0\uffff" + // flags
"2\uffff" + // string table entry count
A.class.getName() + "\uffff" + // string table entry #0
@@ -49,24 +89,6 @@
"2\uffff" + // method name
"0\uffff"; // param count
- private final String INVALID_METHOD_REQUEST = "0\uffff" + // version
- "0\uffff" + // flags
- "2\uffff" + // string table entry count
- A.class.getName() + "\uffff" + // string table entry #0
- "method3" + "\uffff" + // string table entry #1
- "1\uffff" + // interface name
- "2\uffff" + // method name
- "0\uffff"; // param count
-
- private final String INVALID_INTERFACE_REQUEST = "0\uffff" + // version
- "0\uffff" + // flags
- "2\uffff" + // string table entry count
- B.class.getName() + "\uffff" + // string table entry #0
- "method1" + "\uffff" + // string table entry #1
- "1\uffff" + // interface name
- "2\uffff" + // method name
- "0\uffff"; // param count
-
/**
* Tests for method {@link RPC#decodeRequest(String)}
*
@@ -95,12 +117,7 @@
}
// Case 3
- try {
- RPCRequest request = RPC.decodeRequest(VALID_ENCODED_REQUEST);
- } catch (Throwable e) {
- // not expected to get here
- fail(e.getClass().getName() + " should not have been thrown by RPC.decodeRequest(String)");
- }
+ RPC.decodeRequest(VALID_ENCODED_REQUEST);
}
/**
@@ -153,19 +170,35 @@
// Case 5
try {
request = RPC.decodeRequest(INVALID_INTERFACE_REQUEST, B.class);
+ fail("Expected IncompatibleRemoteServiceException");
} catch (IncompatibleRemoteServiceException e) {
// should get here
}
// Case 6
try {
request = RPC.decodeRequest(INVALID_METHOD_REQUEST, A.class);
+ fail("Expected IncompatibleRemoteServiceException");
} catch (IncompatibleRemoteServiceException e) {
// should get here
}
}
/**
- * Tests for method {@link RPC#encodeResponseForFailure(Method, Throwable)}
+ * Tests that method
+ * {@link RPC#decodeRequest(String, Class, SerializationPolicyProvider)} can
+ * handle the decoding of requests from pre-RPC resource (whitelist) clients.
+ *
+ * @throws SerializationException
+ */
+ public void testDecodeRequestPreRPCResourceFile() {
+ RPCRequest rpcRequest = RPC.decodeRequest(
+ VALID_PRE_RPC_RESOURCE_ENCODED_REQUEST, A.class, null);
+ SerializationPolicy serializationPolicy = rpcRequest.getSerializationPolicy();
+ assertEquals(RPC.getDefaultSerializationPolicy(), serializationPolicy);
+ }
+
+ /**
+ * Tests for method {@link RPC#encodeResponseForFailure(Method, Throwable)}.
*
* Cases:
* <ol>
@@ -175,21 +208,18 @@
* <li>Method is specified to throw an exception of the given type</li>
* </ol>
*
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ * @throws SerializationException
+ *
*/
- public void testEncodeResponseForFailure() {
+ public void testEncodeResponseForFailure() throws SecurityException,
+ NoSuchMethodException, SerializationException {
// Case 1
- try {
- RPC.encodeResponseForFailure(null, new Throwable());
- } catch (Throwable e) {
- fail(e.getMessage());
- }
+ RPC.encodeResponseForFailure(null, new SerializableException());
Method A_method1 = null;
- try {
- A_method1 = A.class.getMethod("method1", null);
- } catch (Throwable e) {
- fail(e.getMessage());
- }
+ A_method1 = A.class.getMethod("method1", null);
// Case 2
try {
@@ -197,8 +227,6 @@
fail("Expected NullPointerException");
} catch (NullPointerException e) {
// expected to get here
- } catch (Throwable e) {
- fail(e.getMessage());
}
// Case 3
@@ -208,18 +236,12 @@
fail("Expected UnexpectedException");
} catch (UnexpectedException e) {
// expected to get here
- } catch (Throwable e) {
- fail(e.getMessage());
}
// Case 4
- try {
- String str = RPC.encodeResponseForFailure(A.class.getMethod("method1",
- null), new SerializableException());
- assertTrue(str.indexOf("SerializableException") != -1);
- } catch (Throwable e) {
- fail(e.getMessage());
- }
+ String str = RPC.encodeResponseForFailure(
+ A.class.getMethod("method1", null), new SerializableException());
+ assertTrue(str.indexOf("SerializableException") != -1);
}
/**
@@ -232,16 +254,17 @@
* <li>Method is not specified to return the given type</li>
* <li>Method is specified to return the given type</li>
* </ol>
+ *
+ * @throws SerializationException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
*/
- public void testEncodeResponseForSuccess() {
+ public void testEncodeResponseForSuccess() throws SerializationException,
+ SecurityException, NoSuchMethodException {
Method A_method1 = null;
Method A_method2 = null;
- try {
- A_method1 = A.class.getMethod("method1", null);
- A_method2 = A.class.getMethod("method2", null);
- } catch (Throwable e) {
- fail(e.getMessage());
- }
+ A_method1 = A.class.getMethod("method1", null);
+ A_method2 = A.class.getMethod("method2", null);
// Case 1
try {
@@ -249,36 +272,21 @@
fail("Expected NullPointerException");
} catch (NullPointerException e) {
// expected to get here
- } catch (Throwable e) {
- fail(e.getMessage());
}
// Case 2
- try {
- RPC.encodeResponseForSuccess(A_method1, null);
- } catch (NullPointerException e) {
- // expected to get here
- } catch (Throwable e) {
- fail(e.getMessage());
- }
+ RPC.encodeResponseForSuccess(A_method1, null);
// Case 3
try {
RPC.encodeResponseForSuccess(A_method2, new SerializableException());
+ fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// expected to get here
- } catch (Throwable e) {
- e.printStackTrace();
- fail(e.getMessage());
}
// Case 4
- try {
- RPC.encodeResponseForSuccess(A_method2, new Integer(1));
- } catch (Throwable e) {
- e.printStackTrace();
- fail(e.getMessage());
- }
+ RPC.encodeResponseForSuccess(A_method2, new Integer(1));
}
/**
@@ -295,18 +303,17 @@
*
* @throws NoSuchMethodException
* @throws SecurityException
+ * @throws SerializationException
*
*/
public void testInvokeAndEncodeResponse() throws SecurityException,
- NoSuchMethodException {
+ NoSuchMethodException, SerializationException {
// Case 1
try {
RPC.invokeAndEncodeResponse(null, null, null);
+ fail("Expected NullPointerException");
} catch (NullPointerException e) {
// expected to get here
- } catch (Throwable e) {
- e.printStackTrace();
- fail(e.getMessage());
}
Method A_method1 = A.class.getMethod("method1", null);
@@ -317,11 +324,9 @@
public void method1() {
}
}, A_method1, null);
+ fail("Expected a SecurityException");
} catch (SecurityException e) {
// expected to get here
- } catch (Throwable e) {
- e.printStackTrace();
- fail(e.getMessage());
}
// Case 3
@@ -338,11 +343,9 @@
return 0;
}
}, A_method1, new Integer[] {new Integer(1)});
+ fail("Expected a SecurityException");
} catch (SecurityException e) {
// expected to get here
- } catch (Throwable e) {
- e.printStackTrace();
- fail(e.getMessage());
}
// Case 4
@@ -360,30 +363,24 @@
return 0;
}
}, A_method1, null);
+ fail("Expected an UnexpectedException");
} catch (UnexpectedException e) {
// expected to get here
- } catch (Throwable e) {
- e.printStackTrace();
- fail(e.getMessage());
}
// Case 5
- try {
- RPC.invokeAndEncodeResponse(new A() {
- public void method1() throws SerializableException {
- throw new SerializableException();
- }
+ RPC.invokeAndEncodeResponse(new A() {
+ public void method1() throws SerializableException {
+ throw new SerializableException();
+ }
- public int method2() {
- return 0;
- }
+ public int method2() {
+ return 0;
+ }
- public int method3(int val) {
- return 0;
- }
- }, A_method1, null);
- } catch (Throwable e) {
- fail(e.getMessage());
- }
+ public int method3(int val) {
+ return 0;
+ }
+ }, A_method1, null);
}
}
diff --git a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java
new file mode 100644
index 0000000..04fefc9
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright 2007 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.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+import com.google.gwt.user.client.rpc.SerializationException;
+
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * Test some of the failure modes associated with
+ * {@link RemoteServiceServlet#doGetSerializationPolicy(HttpServletRequest, String, String)}.
+ *
+ * TODO: test caching of policies?
+ */
+public class RemoteServiceServletTest extends TestCase {
+
+ private static class Bar implements Serializable {
+ }
+
+ private static class Baz {
+ }
+
+ private static class Foo implements IsSerializable {
+ }
+
+ private static class MockHttpServletRequest implements HttpServletRequest {
+ private String contextPath;
+
+ public Object getAttribute(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getAttributeNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getAuthType() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getCharacterEncoding() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getContentLength() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getContentType() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getContextPath() {
+ return contextPath;
+ }
+
+ public Cookie[] getCookies() {
+ throw new UnsupportedOperationException();
+ }
+
+ public long getDateHeader(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getHeader(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getHeaderNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getHeaders(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public ServletInputStream getInputStream() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getIntHeader(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getLocalAddr() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Locale getLocale() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getLocales() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getLocalName() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getLocalPort() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getMethod() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getParameter(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Map getParameterMap() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getParameterNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getParameterValues(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getPathInfo() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getPathTranslated() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getProtocol() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getQueryString() {
+ throw new UnsupportedOperationException();
+ }
+
+ public BufferedReader getReader() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRealPath(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRemoteAddr() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRemoteHost() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getRemotePort() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRemoteUser() {
+ throw new UnsupportedOperationException();
+ }
+
+ public RequestDispatcher getRequestDispatcher(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRequestedSessionId() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRequestURI() {
+ throw new UnsupportedOperationException();
+ }
+
+ public StringBuffer getRequestURL() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getScheme() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getServerName() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getServerPort() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getServletPath() {
+ throw new UnsupportedOperationException();
+ }
+
+ public HttpSession getSession() {
+ throw new UnsupportedOperationException();
+ }
+
+ public HttpSession getSession(boolean arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Principal getUserPrincipal() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isRequestedSessionIdFromCookie() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isRequestedSessionIdFromUrl() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isRequestedSessionIdFromURL() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isRequestedSessionIdValid() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isSecure() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isUserInRole(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void removeAttribute(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setAttribute(String arg0, Object arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setCharacterEncoding(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private class MockServletConfig implements ServletConfig {
+ private ServletContext context;
+
+ public String getInitParameter(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getInitParameterNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ServletContext getServletContext() {
+ return context;
+ }
+
+ public String getServletName() {
+ throw new UnsupportedOperationException();
+ }
+
+ void setContext(ServletContext context) {
+ this.context = context;
+ }
+ }
+
+ private class MockServletContext implements ServletContext {
+ private ServletConfig config;
+ private Throwable exLogged;
+ private String messageLogged;
+
+ public MockServletContext() {
+ }
+
+ public Object getAttribute(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getAttributeNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ServletContext getContext(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getInitParameter(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getInitParameterNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getMajorVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getMimeType(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getMinorVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ public RequestDispatcher getNamedDispatcher(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getRealPath(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public RequestDispatcher getRequestDispatcher(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public URL getResource(String arg0) throws MalformedURLException {
+ throw new UnsupportedOperationException();
+ }
+
+ public InputStream getResourceAsStream(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set getResourcePaths(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getServerInfo() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Servlet getServlet(String arg0) throws ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getServletContextName() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getServletNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration getServlets() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void log(Exception arg0, String arg1) {
+ log(arg1, arg0);
+ }
+
+ public void log(String arg0) {
+ log(arg0, null);
+ }
+
+ public void log(String arg0, Throwable arg1) {
+ messageLogged = arg0;
+ exLogged = arg1;
+ }
+
+ public void removeAttribute(String arg0) {
+ }
+
+ public void setAttribute(String arg0, Object arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ void setConfig(ServletConfig config) {
+ this.config = config;
+ }
+ }
+
+ public void testDoGetSerializationPolicy_FailToOpenMD5Resource()
+ throws ServletException {
+ MockServletConfig mockConfig = new MockServletConfig();
+ MockServletContext mockContext = new MockServletContext() {
+ public InputStream getResourceAsStream(String resource) {
+ return null;
+ }
+ };
+ mockConfig.context = mockContext;
+ mockContext.config = mockConfig;
+
+ RemoteServiceServlet rss = new RemoteServiceServlet();
+
+ MockHttpServletRequest mockRequest = new MockHttpServletRequest();
+ rss.init(mockConfig);
+
+ mockRequest.contextPath = "/MyModule";
+
+ SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy(
+ mockRequest, "http://www.google.com/MyModule", "12345");
+ assertNull(serializationPolicy);
+ assertNotNull(mockContext.messageLogged);
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)}.
+ *
+ * This method tests that if the module path is in a different context than
+ * the RemoteServiceServlet which is processing the request, a message will be
+ * logged and null is returned for the SerializationPolicy.
+ */
+ public void testDoGetSerializationPolicy_ModuleInSeparateServlet()
+ throws ServletException {
+ MockServletConfig mockConfig = new MockServletConfig();
+ MockServletContext mockContext = new MockServletContext();
+ mockConfig.context = mockContext;
+ mockContext.config = mockConfig;
+
+ RemoteServiceServlet rss = new RemoteServiceServlet();
+
+ MockHttpServletRequest mockRequest = new MockHttpServletRequest();
+ rss.init(mockConfig);
+
+ mockRequest.contextPath = "/foo";
+ SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy(
+ mockRequest, "http://www.google.com/MyModule", "");
+ assertNotNull(mockContext.messageLogged);
+ assertNull(serializationPolicy);
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doGetSerializationPolicy(javax.servlet.http.HttpServletRequest, java.lang.String, java.lang.String)}.
+ *
+ * This method tests the success case. The resource exists and is in the same
+ * path at the web application.
+ */
+ public void testDoGetSerializationPolicy_Success() throws ServletException,
+ SerializationException {
+ final String resourceHash = "12345";
+ final String resourcePath = SerializationPolicyLoader.getSerializationPolicyFileName(resourceHash);
+ MockServletConfig mockConfig = new MockServletConfig();
+ MockServletContext mockContext = new MockServletContext() {
+ public InputStream getResourceAsStream(String resource) {
+ if (resourcePath.equals(resource)) {
+ try {
+ String payLoad = Foo.class.getName() + ",true\n"
+ + Bar.class.getName() + ",false\n";
+ return new ByteArrayInputStream(
+ payLoad.getBytes(SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING));
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ return null;
+ }
+ };
+ mockConfig.context = mockContext;
+ mockContext.config = mockConfig;
+
+ RemoteServiceServlet rss = new RemoteServiceServlet();
+
+ MockHttpServletRequest mockRequest = new MockHttpServletRequest();
+ rss.init(mockConfig);
+
+ mockRequest.contextPath = "/MyModule";
+
+ SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy(
+ mockRequest, "http://www.google.com/MyModule", resourceHash);
+ assertNotNull(serializationPolicy);
+
+ assertDeserializeFields(serializationPolicy, Foo.class);
+ assertValidDeserialize(serializationPolicy, Foo.class);
+
+ assertDeserializeFields(serializationPolicy, Bar.class);
+ assertNotValidDeserialize(serializationPolicy, Bar.class);
+
+ assertNotDeserializeFields(serializationPolicy, Baz.class);
+ assertNotValidDeserialize(serializationPolicy, Baz.class);
+ }
+
+ private void assertDeserializeFields(SerializationPolicy policy, Class clazz) {
+ assertTrue(policy.shouldDeserializeFields(clazz));
+ }
+
+ private void assertNotDeserializeFields(SerializationPolicy policy,
+ Class clazz) {
+ assertFalse(policy.shouldDeserializeFields(clazz));
+ }
+
+ private void assertNotValidDeserialize(SerializationPolicy policy, Class clazz) {
+ try {
+ policy.validateDeserialize(clazz);
+ fail("assertNotValidDeserialize: " + clazz.getName()
+ + " failed to throw an exception");
+ } catch (SerializationException e) {
+ // expected
+ }
+ }
+
+ private void assertValidDeserialize(SerializationPolicy policy, Class clazz)
+ throws SerializationException {
+ policy.validateDeserialize(clazz);
+ }
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java b/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java
new file mode 100644
index 0000000..b90dba9
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/SerializationPolicyLoaderTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2007 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.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+
+/**
+ * Test the {@link SerializationPolicyLoader} class.
+ */
+public class SerializationPolicyLoaderTest extends TestCase {
+ // allowed by the policy
+ static class A {
+ }
+
+ // not allowed by the policy
+ static class B {
+ }
+
+ // missing the instantiable attribute
+ private static String POLICY_FILE_MISSING_FIELD = A.class.getName();
+
+ private static String POLICY_FILE_TRIGGERS_CLASSNOTFOUND = "C,false";
+
+ private static String VALID_POLICY_FILE_CONTENTS = A.class.getName()
+ + ", true";
+
+ public static InputStream getInputStreamFromString(String content)
+ throws UnsupportedEncodingException {
+ return new ByteArrayInputStream(
+ content.getBytes(SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING));
+ }
+
+ public void testPolicyFileMissingField() throws IOException,
+ ClassNotFoundException {
+ InputStream is = getInputStreamFromString(POLICY_FILE_MISSING_FIELD);
+ try {
+ SerializationPolicyLoader.loadFromStream(is);
+ fail("Expected ParseException");
+ } catch (ParseException e) {
+ // expected to get here
+ }
+ }
+
+ public void testPolicyFileTriggersClassNotFound() throws IOException,
+ ParseException {
+ InputStream is = getInputStreamFromString(POLICY_FILE_TRIGGERS_CLASSNOTFOUND);
+ try {
+ SerializationPolicyLoader.loadFromStream(is);
+ fail("Expected ClassNotFoundException");
+ } catch (ClassNotFoundException e) {
+ // expected to get here
+ }
+ }
+
+ /**
+ * Test that a valid policy file will allow the types in the policy to be used
+ * and reject those that are not.
+ *
+ * @throws ClassNotFoundException
+ * @throws ParseException
+ */
+ public void testValidSerializationPolicy() throws IOException,
+ SerializationException, ParseException, ClassNotFoundException {
+
+ InputStream is = getInputStreamFromString(VALID_POLICY_FILE_CONTENTS);
+ SerializationPolicy sp = SerializationPolicyLoader.loadFromStream(is);
+ assertTrue(sp.shouldDeserializeFields(A.class));
+ assertTrue(sp.shouldSerializeFields(A.class));
+
+ assertFalse(sp.shouldDeserializeFields(B.class));
+ assertFalse(sp.shouldSerializeFields(B.class));
+
+ sp.validateDeserialize(A.class);
+ sp.validateSerialize(A.class);
+
+ try {
+ sp.validateDeserialize(B.class);
+ fail("Expected SerializationException");
+ } catch (SerializationException ex) {
+ // should get here
+ }
+
+ try {
+ sp.validateSerialize(B.class);
+ fail("Expected SerializationException");
+ } catch (SerializationException ex) {
+ // should get here
+ }
+ }
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicyTest.java b/user/test/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicyTest.java
new file mode 100644
index 0000000..9727faf
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/impl/LegacySerializationPolicyTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 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.user.server.rpc.impl;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.server.rpc.SerializationPolicy;
+
+import junit.framework.TestCase;
+
+import java.io.Serializable;
+
+/**
+ * Tests {@link LegacySerializationPolicy}.
+ */
+public class LegacySerializationPolicyTest extends TestCase {
+
+ private static class Bar implements Serializable {
+ }
+
+ private static class Baz {
+ }
+
+ private static class Foo implements IsSerializable {
+ }
+
+ public void testSerializability() throws SerializationException {
+
+ SerializationPolicy serializationPolicy = LegacySerializationPolicy.getInstance();
+
+ assertDeserializeFields(serializationPolicy, Foo.class);
+ assertValidDeserialize(serializationPolicy, Foo.class);
+
+ assertDeserializeFields(serializationPolicy, Bar.class);
+ assertNotValidDeserialize(serializationPolicy, Bar.class);
+
+ assertNotDeserializeFields(serializationPolicy, Baz.class);
+ assertNotValidDeserialize(serializationPolicy, Baz.class);
+ }
+
+ private void assertDeserializeFields(SerializationPolicy policy, Class clazz) {
+ assertTrue(policy.shouldDeserializeFields(clazz));
+ }
+
+ private void assertNotDeserializeFields(SerializationPolicy policy,
+ Class clazz) {
+ assertFalse(policy.shouldDeserializeFields(clazz));
+ }
+
+ private void assertNotValidDeserialize(SerializationPolicy policy, Class clazz) {
+ try {
+ policy.validateDeserialize(clazz);
+ fail("assertNotValidDeserialize: " + clazz.getName()
+ + " failed to throw an exception");
+ } catch (SerializationException e) {
+ // expected
+ }
+ }
+
+ private void assertValidDeserialize(SerializationPolicy policy, Class clazz)
+ throws SerializationException {
+ policy.validateDeserialize(clazz);
+ }
+}
diff --git a/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java b/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java
new file mode 100644
index 0000000..bae526f
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/impl/StandardSerializationPolicyTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2007 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.user.server.rpc.impl;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+
+/**
+ * Tests for the {@link StandardSerializationPolicy} class.
+ */
+public class StandardSerializationPolicyTest extends TestCase {
+
+ // This type will be included
+ class A {
+ // purposely empty
+ }
+
+ // This type will not be included
+ class B {
+ // purposely empty
+ }
+
+ // This type is serializable but not instantiable
+ class C {
+ // purposely empty
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy#shouldDeserializeFields(java.lang.Class)}.
+ */
+ public void testShouldDerializeFields() {
+ StandardSerializationPolicy ssp = getStandardSerializationPolicy();
+ assertTrue(ssp.shouldDeserializeFields(A.class));
+ assertFalse(ssp.shouldDeserializeFields(B.class));
+ assertTrue(ssp.shouldDeserializeFields(C.class));
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy#shouldSerializeFields(java.lang.Class)}.
+ */
+ public void testShouldSerializeFields() {
+ StandardSerializationPolicy ssp = getStandardSerializationPolicy();
+ assertTrue(ssp.shouldSerializeFields(A.class));
+ assertFalse(ssp.shouldSerializeFields(B.class));
+ assertTrue(ssp.shouldSerializeFields(C.class));
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy#validateDeserialize(java.lang.Class)}.
+ *
+ * @throws SerializationException
+ */
+ public void testValidateDeserialize() throws SerializationException {
+ StandardSerializationPolicy ssp = getStandardSerializationPolicy();
+
+ ssp.validateDeserialize(A.class);
+
+ try {
+ ssp.validateDeserialize(B.class);
+ fail("Expected SerializationException");
+ } catch (SerializationException e) {
+ // should get here
+ }
+
+ try {
+ ssp.validateDeserialize(C.class);
+ fail("Expected SerializationException");
+ } catch (SerializationException e) {
+ // should get here
+ }
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy#validateSerialize(java.lang.Class)}.
+ *
+ * @throws SerializationException
+ */
+ public void testValidateSerialize() throws SerializationException {
+ StandardSerializationPolicy ssp = getStandardSerializationPolicy();
+
+ ssp.validateSerialize(A.class);
+
+ try {
+ ssp.validateSerialize(B.class);
+ fail("Expected SerializationException");
+ } catch (SerializationException e) {
+ // should get here
+ }
+
+ try {
+ ssp.validateSerialize(C.class);
+ fail("Expected SerializationException");
+ } catch (SerializationException e) {
+ // should get here
+ }
+ }
+
+ StandardSerializationPolicy getStandardSerializationPolicy() {
+ java.util.Map map = new HashMap();
+ map.put(A.class, Boolean.TRUE);
+ map.put(C.class, Boolean.FALSE);
+ return new StandardSerializationPolicy(map);
+ }
+}
diff --git a/user/test/test/ServletMappingTest.java b/user/test/test/ServletMappingTest.java
index 8d88a96..5dcb363 100644
--- a/user/test/test/ServletMappingTest.java
+++ b/user/test/test/ServletMappingTest.java
@@ -19,6 +19,7 @@
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.user.client.rpc.TestSetValidator;
/**
* TODO: document me.
@@ -35,10 +36,9 @@
* Should call the implementation that returns 1.
*/
public void testServletMapping1() {
- String url = "test";
makeAsyncCall(GWT.getModuleBaseURL() + "test", new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -54,7 +54,7 @@
public void testServletMapping2() {
makeAsyncCall(GWT.getModuleBaseURL() + "test/longer", new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -70,7 +70,7 @@
public void testServletMapping3() {
makeAsyncCall(GWT.getModuleBaseURL() + "test/long", new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -121,7 +121,7 @@
makeAsyncCall(GWT.getModuleBaseURL() + "test/long?a=b&c=d",
new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {
@@ -138,7 +138,7 @@
makeAsyncCall(GWT.getModuleBaseURL()
+ "totally/different/but/valid?a=b&c=d", new AsyncCallback() {
public void onFailure(Throwable caught) {
- fail(caught.toString());
+ TestSetValidator.rethrowException(caught);
}
public void onSuccess(Object result) {