Fixes issue http://code.google.com/p/google-web-toolkit/issues/detail?id=5578
Restores the domain class upcasting behavior of RequestFactoryServlet,
which is relied upon by our UserInformation class.
Also fixes the fact that UserInformation never provided a finder
method, which we're now less forgiving of. Really, though,
UserInformation should be a value object, not a proxy at all. It acts
the way it does to hack around our lack of such things.
Also introduces unit tests to ensure that UserInformation and
LoggingService keep working.
Review at http://gwt-code-reviews.appspot.com/1098801
Review by: robertvawter@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9254 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/requestfactory/server/Logging.java b/user/src/com/google/gwt/requestfactory/server/Logging.java
index 4c9e04e..cbcb1b0 100644
--- a/user/src/com/google/gwt/requestfactory/server/Logging.java
+++ b/user/src/com/google/gwt/requestfactory/server/Logging.java
@@ -21,6 +21,8 @@
import com.google.gwt.logging.server.StackTraceDeobfuscator;
import com.google.gwt.user.client.rpc.RpcRequestBuilder;
+import javax.servlet.http.HttpServletRequest;
+
/**
* Server side object that handles log messages sent by
* {@link com.google.gwt.requestfactory.client.RequestFactoryLogHandler}.
@@ -33,15 +35,22 @@
/**
* Logs a message.
*
- * @param logRecordJson a log record in JSON format.
+ * @param serializedLogRecordString a json serialized LogRecord, as provided by
+ * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil.logRecordAsJsonObject(LogRecord)}
* @throws RemoteLoggingException if logging fails
*/
public static void logMessage(String logRecordJson)
throws RemoteLoggingException {
- // if the header does not exist, we pass null, which is handled gracefully
- // by the deobfuscation code.
- String strongName = RequestFactoryServlet.getThreadLocalRequest().getHeader(
- RpcRequestBuilder.STRONG_NAME_HEADER);
+ /*
+ * if the header does not exist, we pass null, which is handled gracefully
+ * by the deobfuscation code.
+ */
+ HttpServletRequest threadLocalRequest = RequestFactoryServlet.getThreadLocalRequest();
+ String strongName = null;
+ if (threadLocalRequest != null) {
+ // can be null during tests
+ threadLocalRequest.getHeader(RpcRequestBuilder.STRONG_NAME_HEADER);
+ }
RemoteLoggingServiceUtil.logOnServer(logRecordJson, strongName,
deobfuscator, null);
}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index 2108542..ed21fc4 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -326,7 +326,7 @@
if (!seen.add(name)) {
return;
}
- if (!"java/lang/Object".equals(superName)) {
+ if (!objectType.getInternalName().equals(superName)) {
RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
}
if (interfaces != null) {
@@ -384,7 +384,7 @@
private class SupertypeCollector extends EmptyVisitor {
private final ErrorContext logger;
private final Set<String> seen = new HashSet<String>();
- private final List<Type> supertypes = new ArrayList<Type>();
+ private final List<Type> supers = new ArrayList<Type>();
public SupertypeCollector(ErrorContext logger) {
this.logger = logger;
@@ -393,7 +393,7 @@
public List<Type> exec(Type type) {
RequestFactoryInterfaceValidator.this.visit(logger,
type.getInternalName(), this);
- return supertypes;
+ return supers;
}
@Override
@@ -402,8 +402,8 @@
if (!seen.add(name)) {
return;
}
- supertypes.add(Type.getObjectType(name));
- if (!"java/lang/Object".equals(name)) {
+ supers.add(Type.getObjectType(name));
+ if (!objectType.getInternalName().equals(name)) {
RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
}
if (interfaces != null) {
@@ -787,22 +787,46 @@
String clientTypeBinaryName) {
Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeBinaryName));
List<Type> found = domainToClientType.get(key);
+
+ /*
+ * If nothing was found look for proxyable supertypes the domain object can
+ * be upcast to.
+ */
+ if (found == null || found.isEmpty()) {
+ List<Type> types = getSupertypes(parentLogger, key);
+ for (Type type : types) {
+ if (objectType.equals(type)) {
+ break;
+ }
+
+ found = domainToClientType.get(type);
+ if (found != null && !found.isEmpty()) {
+ break;
+ }
+ }
+ }
+
if (found == null || found.isEmpty()) {
return null;
}
+
+ Type typeToReturn = null;
+
// Common case
if (found.size() == 1) {
- return found.get(0).getClassName();
- }
-
- // Search for the first assignable type
- Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
- for (Type t : found) {
- if (isAssignable(parentLogger, assignableTo, t)) {
- return t.getClassName();
+ typeToReturn = found.get(0);
+ } else {
+ // Search for the first assignable type
+ Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
+ for (Type t : found) {
+ if (isAssignable(parentLogger, assignableTo, t)) {
+ typeToReturn = t;
+ break;
+ }
}
}
- return null;
+
+ return typeToReturn == null ? null : typeToReturn.getClassName();
}
/**
@@ -1099,7 +1123,7 @@
*/
private Type getReturnType(ErrorContext logger, RFMethod method) {
logger = logger.setMethod(method);
- final String[] returnType = {"java/lang/Object"};
+ final String[] returnType = { objectType.getInternalName() };
String signature = method.getSignature();
final int expectedCount;
@@ -1147,7 +1171,9 @@
if (toReturn != null) {
return toReturn;
}
+
logger = logger.setType(type);
+
toReturn = new SupertypeCollector(logger).exec(type);
supertypes.put(type, Collections.unmodifiableList(toReturn));
return toReturn;
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformation.java b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
index c16554b..4d3ac97 100644
--- a/user/src/com/google/gwt/requestfactory/server/UserInformation.java
+++ b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
@@ -18,36 +18,63 @@
/**
* A base class for providing authentication related information about the user.
- * Services that want real authentication should subclass this class.
+ * Services that want real authentication should subclass this class with a
+ * matching constructor, and set their class name via
+ * {@link #setUserInformationImplClass(String)}.
*/
public abstract class UserInformation {
+ /**
+ * Reset by {@link #getCurrentUserInformation}, which is called by
+ * {@link RequestFactoryServlet#doPost} at the start of each request, so
+ * shouldn't leak between re-used threads.
+ */
+ private static final ThreadLocal<UserInformation> currentUser = new ThreadLocal<UserInformation>();
+
private static String userInformationImplClass = "";
/**
- * Returns the current user information for a given redirect URL. If
- * {@link #setUserInformationImplClass(String)} has been called with a class
- * name, that class is used to gather the information by calling a (String)
- * constructor. If the impl class name is "", or if the class cannont be
- * instantiated, dummy user info is returned.
+ * Instance finder method required by RequestFactory. Returns the last
+ * UserInformation established for this thread by a call to
+ * getCurrentUserInformation, or null if non has been set.
+ *
+ * @param id ignored, required by RequestFactoryServlet
+ */
+ public static UserInformation findUserInformation(Long id) {
+ return currentUser.get();
+ }
+
+ /**
+ * Called by {@link RequestFactoryServlet#doPost} at the start of each request
+ * received. Establishes the current user information for this request, and
+ * notes a redirect url to be provided back to the client if the user's bona
+ * fides cannot be established. All succeeding calls to
+ * {@link #findUserInformation(Long)} made from the same thread will return
+ * the same UserInfo instance.
+ * <p>
+ * If {@link #setUserInformationImplClass(String)} has been called with a
+ * class name, that class is used to gather the information by calling a
+ * (String) constructor. If the impl class name is "", or if the class cannont
+ * be instantiated, dummy user info is returned.
*
* @param redirectUrl the redirect URL as a String
* @return a {@link UserInformation} instance
*/
public static UserInformation getCurrentUserInformation(String redirectUrl) {
- UserInformation userInfo = null;
+ currentUser.remove();
if (!"".equals(userInformationImplClass)) {
try {
- userInfo = (UserInformation) Class.forName(userInformationImplClass).getConstructor(
- String.class).newInstance(redirectUrl);
+ currentUser.set((UserInformation) Class.forName(
+ userInformationImplClass).getConstructor(String.class).newInstance(
+ redirectUrl));
} catch (Exception e) {
e.printStackTrace();
}
}
- if (userInfo == null) {
- userInfo = new UserInformationSimpleImpl(redirectUrl);
+ if (currentUser.get() == null) {
+ currentUser.set(new UserInformationSimpleImpl(redirectUrl));
}
- return userInfo;
+ return currentUser.get();
}
/**
diff --git a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
index 53d7802..776d9f4 100644
--- a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
@@ -30,7 +30,8 @@
/**
* Log a message on the server.
*
- * @param serializedLogRecordString a String
+ * @param serializedLogRecordString a json serialized LogRecord, as provided by
+ * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil.logRecordAsJsonObject(LogRecord)}
* @return a Void {@link Request}
*/
Request<Void> logMessage(String serializedLogRecordString);
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 19d5130..b50b784 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -28,6 +28,7 @@
import com.google.gwt.requestfactory.shared.SimpleFooProxy;
import com.google.gwt.requestfactory.shared.SimpleFooRequest;
import com.google.gwt.requestfactory.shared.SimpleValueProxy;
+import com.google.gwt.requestfactory.shared.UserInformationProxy;
import com.google.gwt.requestfactory.shared.Violation;
import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
@@ -382,6 +383,18 @@
});
}
+ public void testDomainUpcast() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnSimpleFooSubclass().fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(42, response.getIntId().intValue());
+ finishTestAndReset();
+ }
+ });
+ }
+
public void testDummyCreate() {
delayTestFinish(DELAY_TEST_FINISH);
@@ -689,6 +702,26 @@
}
});
}
+
+ /**
+ * Make sure our stock RF logging service keeps receiving.
+ */
+ public void testLoggingService() {
+ String logRecordJson = new StringBuilder("{").append("\"level\": \"ALL\", ")
+ .append("\"loggerName\": \"logger\", ")
+ .append("\"msg\": \"Hi mom\", ")
+ .append("\"timestamp\": \"1234567890\",")
+ .append("\"thrown\": {}")
+ .append("}")
+ .toString();
+
+ req.loggingRequest().logMessage(logRecordJson).fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTestAndReset();
+ }
+ });
+ }
/*
* tests that (a) any method can have a side effect that is handled correctly.
@@ -1921,6 +1954,25 @@
}
/**
+ * We provide a simple UserInformation class to give GAE developers a hand,
+ * and other developers a hint. Make sure RF doesn't break it (it relies on
+ * server side upcasting, and a somewhat sleazey reflective lookup mechanism
+ * in a static method on UserInformation).
+ */
+ public void testUserInfo() {
+ req.userInformationRequest().getCurrentUserInformation("").fire(
+ new Receiver<UserInformationProxy>() {
+ @Override
+ public void onSuccess(UserInformationProxy getResponse) {
+ assertEquals("Dummy Email", getResponse.getEmail());
+ assertEquals("Dummy User", getResponse.getName());
+ assertEquals("", getResponse.getLoginUrl());
+ assertEquals("", getResponse.getLogoutUrl());
+ }
+ });
+ }
+
+ /**
* Check if a graph of unpersisted objects can be echoed.
*/
public void testUnpersistedEchoComplexGraph() {
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 460621d..4a1bfb7 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -278,6 +278,11 @@
public static String returnNullString() {
return null;
}
+
+ public static SimpleFoo returnSimpleFooSubclass() {
+ return new SimpleFoo() {
+ };
+ }
public static SimpleValue returnValueProxy() {
SimpleValue toReturn = new SimpleValue();
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
index a166f9e..cd174be 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -94,6 +94,8 @@
Request<String> returnNullString();
+ Request<SimpleFooProxy> returnSimpleFooSubclass();
+
Request<SimpleValueProxy> returnValueProxy();
InstanceRequest<SimpleFooProxy, Integer> sum(List<Integer> values);
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
index aaea4dc..812858a 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
@@ -16,11 +16,16 @@
package com.google.gwt.requestfactory.shared;
/**
- * Creates SimpleFooRequests.
+ * Simple RequesetFactory interface with two domain objects, and our standard
+ * UserInformation and Logging services.
*/
public interface SimpleRequestFactory extends RequestFactory {
- SimpleFooRequest simpleFooRequest();
+ LoggingRequest loggingRequest();
SimpleBarRequest simpleBarRequest();
+
+ SimpleFooRequest simpleFooRequest();
+
+ UserInformationRequest userInformationRequest();
}