Revision: 8938
Author: [email protected]
Date: Tue Oct 5 10:23:33 2010
Log: Original change by Ray Cromwell
Add validation of context methods and proxy against domain objects.
Add verbose diagnostic messages.
Add test cases for RequestFactoryModel.
Review at http://gwt-code-reviews.appspot.com/951801
http://code.google.com/p/google-web-toolkit/source/detail?r=8938
Added:
/trunk/user/test/com/google/gwt/requestfactory/rebind
/trunk/user/test/com/google/gwt/requestfactory/rebind/model
/trunk/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
/trunk/user/test/com/google/gwt/requestfactory/server/TestContextImpl.java
/trunk/user/test/com/google/gwt/requestfactory/server/TestContextNoIdImpl.java
/trunk/user/test/com/google/gwt/requestfactory/server/TestContextNoVersionImpl.java
Modified:
/trunk/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
/trunk/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
/trunk/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
/trunk/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
/trunk/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
/trunk/user/test/com/google/gwt/requestfactory/server/SimpleFooString.java
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
Tue Oct 5 10:23:33 2010
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.requestfactory.rebind.model;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.impl.JavaResourceBase;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.requestfactory.server.TestContextImpl;
+import com.google.gwt.requestfactory.server.TestContextNoIdImpl;
+import com.google.gwt.requestfactory.server.TestContextNoVersionImpl;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.impl.Property;
+
+import junit.framework.TestCase;
+
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+/**
+ * Test case for {...@link
com.google.gwt.requestfactory.rebind.model.RequestFactoryModel}
+ * that uses mock CompilationStates.
+ */
+public class RequestFactoryModelTest extends TestCase {
+
+ /**
+ * Constructs an empty interface representation of a type.
+ */
+ private static class EmptyMockJavaResource extends MockJavaResource {
+
+ private final StringBuilder code = new StringBuilder();
+
+ public EmptyMockJavaResource(Class<?> clazz) {
+ super(clazz.getName());
+
+ code.append("package ").append(clazz.getPackage().getName())
+ .append(";\n");
+ code.append("public interface ").append(clazz.getSimpleName());
+
+ int numParams = clazz.getTypeParameters().length;
+ if (numParams != 0) {
+ code.append("<");
+ for (int i = 0; i < numParams; i++) {
+ if (i != 0) {
+ code.append(",");
+ }
+ code.append("T").append(i);
+ }
+ code.append(">");
+ }
+
+ code.append("{}\n");
+ }
+
+ @Override
+ protected CharSequence getContent() {
+ return code;
+ }
+ }
+
+ /**
+ * Loads the actual source of a type. This should be used only for types
+ * directly tested by this test. Note that use of this class requires
your
+ * source files to be on your classpath.
+ */
+ private static class RealJavaResource extends MockJavaResource {
+
+ public RealJavaResource(Class<?> clazz) {
+ super(clazz.getName());
+ }
+
+ @Override
+ protected CharSequence getContent() {
+ String resourceName = getTypeName().replace('.', '/') + ".java";
+ InputStream stream = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(resourceName);
+ return Util.readStreamAsString(stream);
+ }
+ }
+
+ private static TreeLogger createCompileLogger() {
+ PrintWriterTreeLogger logger = new PrintWriterTreeLogger(
+ new PrintWriter(System.err, true));
+ logger.setMaxDetail(TreeLogger.ERROR);
+ return logger;
+ }
+
+ private TreeLogger logger;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ logger = createCompileLogger();
+ }
+
+ public void testBadCollectionType() {
+ testModelWithMethodDecl("Request<SortedSet<Integer>> badReturnType();",
+ "Requests that return collections may be declared with
java.util.List or java.util.Set only");
+ }
+
+ public void testBadCollectionTypeNotParameterized() {
+ testModelWithMethodDecl("Request<SortedSet> badReturnType();",
+ "Requests that return collections of List or Set must be
parameterized");
+ }
+
+ public void testBadReturnType() {
+ testModelWithMethodDecl("Request<Iterable> badReturnType();",
+ "Invalid Request parameterization java.lang.Iterable");
+ }
+
+ public void testMismatchedArityInstance() {
+ testModelWithMethodDecl(
+ "InstanceRequest<TestProxy, String>
mismatchedArityInstance(TestProxy p, int x);",
+ "Parameter 0 of method TestContext.mismatchedArityInstance does
not match method
com.google.gwt.requestfactory.server.TestContextImpl.mismatchedArityInstance");
+ }
+
+ public void testMismatchedArityStatic() {
+ testModelWithMethodDecl("Request<String> mismatchedArityStatic(int
x);",
+ "Method TestContext.mismatchedArityStatic parameters do not match
same method on com.google.gwt.requestfactory.server.TestContextImpl");
+ }
+
+ public void testMismatchedModifierNonStatic() {
+ testModelWithMethodDecl(
+ "InstanceRequest<TestProxy, String> mismatchedNonStatic();",
+ "Method TestContext.mismatchedNonStatic is an instance method,
while the corresponding method on
com.google.gwt.requestfactory.server.TestContextImpl is static");
+ }
+
+ public void testMismatchedModifierStatic() {
+ testModelWithMethodDecl("Request<String> mismatchedStatic();",
+ "Method TestContext.mismatchedStatic is a static method, while the
corresponding method on
com.google.gwt.requestfactory.server.TestContextImpl is not");
+ }
+
+ public void testMismatchedParamType() {
+ testModelWithMethodDecl("Request<String> mismatchedParamType(Integer
x);",
+ "Parameter 0 of method TestContext.mismatchedParamType does not
match method
com.google.gwt.requestfactory.server.TestContextImpl.mismatchedParamType");
+ }
+
+ public void testMismatchedReturnType() {
+ testModelWithMethodDecl("Request<String> mismatchedReturnType();",
+ "Return type of method TestContext.mismatchedReturnType does not
match method
com.google.gwt.requestfactory.server.TestContextImpl.mismatchedReturnType");
+ }
+
+ public void testMissingId() {
+ testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
+ TestContextNoIdImpl.class.getName(),
+ TestContextNoIdImpl.class.getName(),
+ "The class
com.google.gwt.requestfactory.server.TestContextNoIdImpl is missing method
getId()");
+ }
+
+ public void testMissingMethod() {
+ testModelWithMethodDecl("Request<String> missingMethod();",
+ "Method t.TestContext.missingMethod has no corresponding public
method on"
+ + " com.google.gwt.requestfactory.server.TestContextImpl");
+ }
+
+ public void testMissingProxyFor() {
+ testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
+ TestContextImpl.class.getName(), null,
+ "The t.TestProxy type does not have a @ProxyFor annotation");
+ }
+
+ public void testMissingService() {
+ testModelWithMethodDeclArgs("Request<String> okMethod();", null,
+ TestContextImpl.class.getName(),
+ "RequestContext subtype t.TestContext is missing a @Service
annotation");
+ }
+
+ public void testMissingVersion() {
+ testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
+ TestContextNoVersionImpl.class.getName(),
+ TestContextNoVersionImpl.class.getName(),
+ "The class
com.google.gwt.requestfactory.server.TestContextNoVersionImpl is missing
method getVersion()");
+ }
+
+ public void testModelWithMethodDecl(final String clientMethodDecls,
+ String... expected) {
+ testModelWithMethodDeclArgs(clientMethodDecls,
+ TestContextImpl.class.getName(), TestContextImpl.class.getName(),
+ expected);
+ }
+
+ public void testModelWithMethodDeclArgs(final String clientMethodDecls,
+ final String serviceClass, String proxyClass, String... expected) {
+ Set<Resource> javaResources = getJavaResources(proxyClass);
+ javaResources.add(new MockJavaResource("t.TestRequestFactory") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + RequestFactory.class.getName() + ";\n");
+ code.append("interface TestRequestFactory extends RequestFactory
{\n");
+ code.append("TestContext testContext();");
+ code.append("}");
+ return code;
+ }
+ });
+ javaResources.add(new MockJavaResource("t.TestContext") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + Request.class.getName() + ";\n");
+ code.append("import " + InstanceRequest.class.getName() + ";\n");
+
+ code.append("import " + RequestContext.class.getName() + ";\n");
+ code.append("import " + SortedSet.class.getName() + ";\n");
+ code.append("import " + List.class.getName() + ";\n");
+ code.append("import " + Set.class.getName() + ";\n");
+ code.append("import " + Service.class.getName() + ";\n");
+ code.append("import " + TestContextImpl.class.getName() + ";\n");
+
+ if (serviceClass != null) {
+ code.append("@Service(" + serviceClass + ".class)");
+ }
+ code.append("interface TestContext extends RequestContext {\n");
+ code.append(clientMethodDecls);
+ code.append("}");
+ return code;
+ }
+ });
+
+ CompilationState state = CompilationStateBuilder
+ .buildFrom(logger, javaResources);
+
+ UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+ builder.setLowestLogLevel(TreeLogger.ERROR);
+ for (String expectedMsg : expected) {
+ builder.expectError(expectedMsg, null);
+ }
+ builder.expectError(RequestFactoryModel.poisonedMessage(), null);
+ UnitTestTreeLogger testLogger = builder.createLogger();
+ try {
+ new RequestFactoryModel(testLogger,
+ state.getTypeOracle().findType("t.TestRequestFactory"));
+ fail("Should have complained");
+ } catch (UnableToCompleteException e) {
+ }
+ testLogger.assertCorrectLogEntries();
+ }
+
+ public void testOverloadedMethod() {
+ testModelWithMethodDecl("Request<String> overloadedMethod();",
+ "Method t.TestContext.overloadedMethod is overloaded on
com.google.gwt.requestfactory.server.TestContextImpl");
+ }
+
+ private Set<Resource> getJavaResources(final String proxyClass) {
+ MockJavaResource[] javaFiles = {new MockJavaResource("t.AddressProxy")
{
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + ProxyFor.class.getName() + ";\n");
+ code.append("import " + EntityProxy.class.getName() + ";\n");
+ if (proxyClass != null) {
+ code.append("@ProxyFor(" + proxyClass + ".class)");
+ }
+ code.append("interface TestProxy extends EntityProxy {\n");
+ code.append("}");
+ return code;
+ }
+ }, new MockJavaResource("java.util.List") {
+ // Tests a Driver interface that extends more than RFED
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package java.util;\n");
+ code.append("public interface List<T> extends Collection<T> {\n");
+ code.append("}");
+ return code;
+ }
+ }, new MockJavaResource("java.util.Set") {
+ // Tests a Driver interface that extends more than RFED
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package java.util;\n");
+ code.append("public interface Set<T> extends Collection<T> {\n");
+ code.append("}");
+ return code;
+ }
+ }, new MockJavaResource("java.util.SortedSet") {
+ // Tests a Driver interface that extends more than RFED
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package java.util;\n");
+ code.append("public interface SortedSet<T> extends Set<T> {\n");
+ code.append("}");
+ return code;
+ }
+ }};
+
+ Set<Resource> toReturn = new
HashSet<Resource>(Arrays.asList(javaFiles));
+
+ toReturn.addAll(Arrays.asList(
+ new Resource[]{new EmptyMockJavaResource(Iterable.class),
+ new EmptyMockJavaResource(Property.class),
+ new EmptyMockJavaResource(EntityProxy.class),
+ new EmptyMockJavaResource(InstanceRequest.class),
+ new EmptyMockJavaResource(RequestFactory.class),
+ new EmptyMockJavaResource(Receiver.class),
+
+ new RealJavaResource(Request.class),
+ new RealJavaResource(Service.class),
+ new RealJavaResource(ProxyFor.class),
+ new EmptyMockJavaResource(RequestContext.class),}));
+
toReturn.addAll(Arrays.asList(JavaResourceBase.getStandardResources()));
+ return toReturn;
+ }
+}
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/requestfactory/server/TestContextImpl.java
Tue Oct 5 10:23:33 2010
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+/**
+ * Bad service method declarations.
+ */
+public class TestContextImpl {
+
+ public static Class badReturnType() {
+ return null;
+ }
+
+ public static String mismatchedArityStatic(int x, int y, int z) {
+ return null;
+ }
+
+ public static String mismatchedParamType(String param) {
+ return null;
+ }
+
+ public static Integer mismatchedReturnType() {
+ return null;
+ }
+
+ public static String overloadedMethod() {
+ return null;
+ }
+
+ public static String overloadedMethod(String foo) {
+ return null;
+ }
+ public String getId() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+
+ public String mismatchedArityInstance(int x, int y) {
+ return null;
+ }
+
+ public String mismatchedStatic(String param) {
+ return null;
+ }
+
+ public static String okMethod() {
+ return null;
+ }
+
+ public static TestContextImpl okMethodProxy() {
+ return null;
+ }
+
+ public static String mismatchedNonStatic(String param) {
+ return null;
+ }
+}
+
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/requestfactory/server/TestContextNoIdImpl.java
Tue Oct 5 10:23:33 2010
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+/**
+ * Bad proxy, no id.
+ */
+public class TestContextNoIdImpl {
+
+ public static TestContextNoIdImpl okMethodProxy() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+}
+
=======================================
--- /dev/null
+++
/trunk/user/test/com/google/gwt/requestfactory/server/TestContextNoVersionImpl.java
Tue Oct 5 10:23:33 2010
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+/**
+ * Bad proxy, no version.
+ */
+public class TestContextNoVersionImpl {
+
+ public static TestContextNoVersionImpl okMethodProxy() {
+ return null;
+ }
+
+ public String getId() {
+ return null;
+ }
+}
+
=======================================
--- /trunk/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java Fri
Oct 1 18:15:55 2010
+++ /trunk/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java Tue
Oct 5 10:23:33 2010
@@ -19,11 +19,13 @@
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.util.collect.HashMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -31,11 +33,27 @@
*/
public class ModelUtils {
+ static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
+
static final Set<String> VALUE_TYPES = Collections.unmodifiableSet(new
HashSet<String>(
Arrays.asList(Boolean.class.getName(), Character.class.getName(),
Class.class.getName(), Date.class.getName(),
Enum.class.getName(),
Number.class.getName(), String.class.getName(),
Void.class.getName())));
+ static {
+ Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
+ autoBoxMap.put(byte.class, Byte.class);
+ autoBoxMap.put(char.class, Character.class);
+ autoBoxMap.put(double.class, Double.class);
+ autoBoxMap.put(float.class, Float.class);
+ autoBoxMap.put(int.class, Integer.class);
+ autoBoxMap.put(long.class, Long.class);
+ autoBoxMap.put(short.class, Short.class);
+ autoBoxMap.put(void.class, Void.class);
+ AUTOBOX_MAP = Collections.unmodifiableMap(autoBoxMap);
+ }
+
+
public static JClassType[] findParameterizationOf(JClassType intfType,
JClassType subType) {
assert intfType.isAssignableFrom(subType) :
subType.getParameterizedQualifiedSourceName()
@@ -69,6 +87,11 @@
}
return false;
}
+
+ public static Class<?> maybeAutobox(Class<?> domainType) {
+ Class<?> autoBoxType = AUTOBOX_MAP.get(domainType);
+ return autoBoxType == null ? domainType : autoBoxType;
+ }
private ModelUtils() {
}
=======================================
---
/trunk/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
Fri Oct 1 18:15:55 2010
+++
/trunk/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
Tue Oct 5 10:23:33 2010
@@ -20,6 +20,7 @@
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.editor.rebind.model.ModelUtils;
@@ -32,6 +33,8 @@
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -53,6 +56,10 @@
requestInterface.getSimpleSourceName(),
instanceRequestInterface.getSimpleSourceName());
}
+
+ static String poisonedMessage() {
+ return "Unable to create RequestFactoryModel model due to previous
errors";
+ }
private final TreeLogger logger;
private final JClassType collectionInterface;
@@ -118,10 +125,10 @@
}
if (poisoned) {
- die("Unable to complete due to previous errors");
+ die(poisonedMessage());
}
}
-
+
public Collection<EntityProxyModel> getAllProxyModels() {
return Collections.unmodifiableCollection(peers.values());
}
@@ -150,14 +157,15 @@
* Examine a RequestContext subtype to populate a ContextMethod.
*/
private void buildContextMethod(ContextMethod.Builder contextBuilder,
- JClassType contextType) {
+ JClassType contextType) throws UnableToCompleteException {
Service serviceAnnotation = contextType.getAnnotation(Service.class);
if (serviceAnnotation == null) {
poison("RequestContext subtype %s is missing a @%s annotation",
contextType.getQualifiedSourceName(),
Service.class.getSimpleName());
return;
}
- contextBuilder.setServiceClass(serviceAnnotation.value());
+ Class<?> serviceClass = serviceAnnotation.value();
+ contextBuilder.setServiceClass(serviceClass);
List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
for (JMethod method : contextType.getInheritableMethods()) {
@@ -169,7 +177,7 @@
RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
methodBuilder.setDeclarationMethod(method);
- if (!validateContextMethodAndSetDataType(methodBuilder, method)) {
+ if (!validateContextMethodAndSetDataType(methodBuilder, method,
serviceClass)) {
continue;
}
@@ -184,7 +192,22 @@
throw new UnableToCompleteException();
}
- private EntityProxyModel getEntityProxyType(JClassType entityProxyType) {
+ /**
+ * Return a list of public methods that match the given methodName.
+ */
+ private List<Method> findMethods(Class<?> domainType, String methodName)
{
+ List<Method> toReturn = new ArrayList<Method>();
+ for (Method method : domainType.getMethods()) {
+ if (methodName.equals(method.getName())
+ && (method.getModifiers() & Modifier.PUBLIC) != 0) {
+ toReturn.add(method);
+ }
+ }
+ return toReturn;
+ }
+
+ private EntityProxyModel getEntityProxyType(JClassType entityProxyType)
+ throws UnableToCompleteException {
EntityProxyModel toReturn = peers.get(entityProxyType);
if (toReturn == null) {
EntityProxyModel.Builder inProgress =
peerBuilders.get(entityProxyType);
@@ -204,8 +227,12 @@
poison("The %s type does not have a @%s annotation",
entityProxyType.getQualifiedSourceName(),
ProxyFor.class.getSimpleName());
+ // early exit, because further processing causes NPEs in numerous
spots
+ die(poisonedMessage());
} else {
- builder.setProxyFor(proxyFor.value());
+ Class<?> domainType = proxyFor.value();
+ builder.setProxyFor(domainType);
+ validateDomainType(domainType);
}
// Look at the methods declared on the EntityProxy
@@ -237,7 +264,10 @@
continue;
}
validateTransportableType(methodBuilder, transportedType, false);
- requestMethods.add(methodBuilder.build());
+ RequestMethod requestMethod = methodBuilder.build();
+ if (validateDomainBeanMethod(requestMethod, builder)) {
+ requestMethods.add(requestMethod);
+ }
}
builder.setRequestMethods(requestMethods);
@@ -247,6 +277,20 @@
}
return toReturn;
}
+
+ private boolean isStatic(Method domainMethod) {
+ return (domainMethod.getModifiers() & Modifier.STATIC) != 0;
+ }
+
+ private String methodLocation(Method domainMethod) {
+ return domainMethod.getDeclaringClass().getName() + "."
+ + domainMethod.getName();
+ }
+
+ private String methodLocation(JMethod proxyMethod) {
+ return proxyMethod.getEnclosingType().getName() + "."
+ + proxyMethod.getName();
+ }
private void poison(String message, Object... args) {
logger.log(TreeLogger.ERROR, String.format(message, args));
@@ -257,7 +301,8 @@
* Examine a RequestContext method to see if it returns a transportable
type.
*/
private boolean validateContextMethodAndSetDataType(
- RequestMethod.Builder methodBuilder, JMethod method) {
+ RequestMethod.Builder methodBuilder, JMethod method, Class<?>
serviceClass)
+ throws UnableToCompleteException {
JClassType requestReturnType = method.getReturnType().isInterface();
JClassType invocationReturnType;
if (requestReturnType == null) {
@@ -266,15 +311,45 @@
instanceRequestInterface));
return false;
}
+
+ /*
+ * TODO: bad assumption
+ * Implicit assumption is that the Service and ProxyFor
+ * classes are the same. This is because an instance method should
+ * technically be looked up on the class that is the instance
parameter,
+ * and not on the serviceClass, which consists of static service
methods.
+ * Can't be fixed until it is fixed in JsonRequestProcessor.
+ */
+ Method domainMethod = validateExistsAndNotOverriden(method,
serviceClass,
+ false);
+ if (domainMethod == null) {
+ return false;
+ }
+
if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
+ if (isStatic(domainMethod)) {
+ poison("Method %s.%s is an instance method, "
+ + "while the corresponding method on %s is static",
+ method.getEnclosingType().getName(),
+ method.getName(),
+ serviceClass.getName());
+ return false;
+ }
// Instance method invocation
JClassType[] params = ModelUtils.findParameterizationOf(
instanceRequestInterface, requestReturnType);
methodBuilder.setInstanceType(getEntityProxyType(params[0]));
invocationReturnType = params[1];
-
} else if (requestInterface.isAssignableFrom(requestReturnType)) {
+ if (!isStatic(domainMethod)) {
+ poison("Method %s.%s is a static method, "
+ + "while the corresponding method on %s is not",
+ method.getEnclosingType().getName(),
+ method.getName(),
+ serviceClass.getName());
+ return false;
+ }
// Static method invocation
JClassType[] params =
ModelUtils.findParameterizationOf(requestInterface,
requestReturnType);
@@ -289,21 +364,168 @@
// Validate the parameters
boolean paramsOk = true;
- for (JParameter param : method.getParameters()) {
+ JParameter[] params = method.getParameters();
+ Class<?>[] domainParams = domainMethod.getParameterTypes();
+ if (params.length != domainParams.length) {
+ poison("Method %s.%s parameters do not match same method on %s",
+ method.getEnclosingType().getName(),
+ method.getName(),
+ serviceClass.getName());
+ }
+ for (int i = 0; i < params.length; ++i) {
+ JParameter param = params[i];
+ Class<?> domainParam = domainParams[i];
paramsOk = validateTransportableType(new RequestMethod.Builder(),
param.getType(), false)
&& paramsOk;
+ paramsOk = validateProxyAndDomainTypeEquals(param.getType(),
domainParam, i,
+ methodLocation(method), methodLocation(domainMethod))
+ && paramsOk;
}
return validateTransportableType(methodBuilder, invocationReturnType,
true)
+ && validateProxyAndDomainTypeEquals(invocationReturnType,
+ domainMethod.getReturnType(), -1, methodLocation(method),
+ methodLocation(domainMethod))
&& paramsOk;
}
+
+ /**
+ * Examine a domain method to see if it matches the proxy method.
+ */
+ private boolean validateDomainBeanMethod(RequestMethod requestMethod,
+ EntityProxyModel.Builder entityBuilder) throws
UnableToCompleteException {
+ JMethod proxyMethod = requestMethod.getDeclarationMethod();
+ // check if method exists on domain object
+ Class<?> domainType = entityBuilder.peek().getProxyFor();
+ Method domainMethod = validateExistsAndNotOverriden(proxyMethod,
domainType,
+ true);
+ if (domainMethod == null) {
+ return false;
+ }
+
+ boolean isGetter = proxyMethod.getName().startsWith("get");
+ if (isGetter) {
+ // compare return type of domain to proxy return type
+ String returnTypeName = domainMethod.getReturnType().getName();
+ // isEntityType() returns true for collections, but we want the
Collection
+ String propertyTypeName =
+ requestMethod.isCollectionType() || requestMethod.isValueType() ?
+ requestMethod.getDataType().getQualifiedBinaryName() :
+ requestMethod.getEntityType().getProxyFor().getName();
+ if (!returnTypeName.equals(propertyTypeName)) {
+ poison("Method %s.%s return type %s does not match
return type %s "
+ + " of method %s.%s", domainType.getName(),
+ domainMethod.getName(), returnTypeName,
+ propertyTypeName,
+ proxyMethod.getEnclosingType().getName(),
proxyMethod.getName());
+ return false;
+ }
+ }
+ JParameter[] proxyParams = proxyMethod.getParameters();
+ Class<?>[] domainParams = domainMethod.getParameterTypes();
+ if (proxyParams.length != domainParams.length) {
+ poison("Method %s.%s parameter mismatch with %s.%s",
+ proxyMethod.getEnclosingType().getName(),
+ proxyMethod.getName(),
+ domainType.getName(),
+ domainMethod.getName());
+ return false;
+ }
+ for (int i = 0; i < proxyParams.length; i++) {
+ JType proxyParam = proxyParams[i].getType();
+ Class<?> domainParam = domainParams[i];
+ if (!validateProxyAndDomainTypeEquals(proxyParam, domainParam, i,
+ methodLocation(proxyMethod), methodLocation(domainMethod))) {
+ poison("Parameter %d of %s.%s doesn't match method %s.%s",
+ i, proxyMethod.getEnclosingType().getName(),
+ proxyMethod.getName(),
+ domainType.getName(),
+ domainMethod.getName());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Examine a domain type and see if it includes a getId() method.
+ */
+ private boolean validateDomainType(Class<?> domainType) {
+ try {
+ domainType.getMethod("getId");
+ } catch (NoSuchMethodException e) {
+ poison("The class %s is missing method getId()",
domainType.getName());
+ return false;
+ }
+ try {
+ domainType.getMethod("getVersion");
+ } catch (NoSuchMethodException e) {
+ poison("The class %s is missing method getVersion()",
domainType.getName());
+ return false;
+ }
+ return true;
+ }
+
+ private Method validateExistsAndNotOverriden(JMethod clientMethod,
+ Class<?> serverType, boolean isGetterOrSetter) {
+ List<Method> domainMethods = findMethods(serverType,
clientMethod.getName());
+ if (domainMethods.size() == 0) {
+ poison("Method %s.%s has no corresponding public method on %s",
+ clientMethod.getEnclosingType().getQualifiedBinaryName(),
+ clientMethod.getName(), serverType.getName());
+ return null;
+ }
+ if (domainMethods.size() > 1) {
+ poison("Method %s.%s is overloaded on %s",
+ clientMethod.getEnclosingType().getQualifiedBinaryName(),
+ clientMethod.getName(), serverType.getName());
+ return null;
+ }
+ Method domainMethod = domainMethods.get(0);
+ if (isGetterOrSetter && isStatic(domainMethod)) {
+ poison("Method %s.%s is declared static", serverType.getName(),
+ domainMethod.getName());
+ return null;
+ }
+ return domainMethod;
+ }
+
+ /**
+ * Compare type from Proxy and Domain.
+ */
+ private boolean validateProxyAndDomainTypeEquals(JType proxyType,
+ Class<?> domainType, int paramNumber, String clientMethod,
+ String serverMethod) throws UnableToCompleteException {
+ boolean matchOk = false;
+ if (ModelUtils.isValueType(oracle, proxyType)
+ ||
collectionInterface.isAssignableFrom(proxyType.isClassOrInterface())) {
+ // allow int to match int or Integer
+ matchOk = proxyType.getQualifiedSourceName().equals(
+ ModelUtils.maybeAutobox(domainType).getName())
+ || proxyType.getQualifiedSourceName().equals(domainType.getName());
+ } else {
+ matchOk = getEntityProxyType(
+ proxyType.isClassOrInterface()).getProxyFor().equals(domainType);
+ }
+ if (!matchOk) {
+ if (paramNumber < 0) {
+ poison("Return type of method %s does not match method %s",
clientMethod,
+ serverMethod);
+ } else {
+ poison("Parameter %d of method %s does not match method %s",
+ paramNumber, clientMethod, serverMethod);
+ }
+ }
+ return matchOk;
+ }
/**
* Examines a type to see if it can be transported.
*/
private boolean validateTransportableType(
- RequestMethod.Builder methodBuilder, JType type, boolean
requireObject) {
+ RequestMethod.Builder methodBuilder, JType type, boolean
requireObject)
+ throws UnableToCompleteException {
JClassType transportedClass = type.isClassOrInterface();
if (transportedClass == null) {
if (requireObject) {
@@ -324,9 +546,14 @@
methodBuilder.setEntityType(getEntityProxyType(transportedClass));
} else if (collectionInterface.isAssignableFrom(transportedClass)) {
// Only allow certain collections for now
- if
(listInterface.equals(transportedClass.isParameterized().getBaseType())) {
+ JParameterizedType parameterized =
transportedClass.isParameterized();
+ if (parameterized == null) {
+ poison("Requests that return collections of List or Set must be
parameterized");
+ return false;
+ }
+ if (listInterface.equals(parameterized.getBaseType())) {
methodBuilder.setCollectionType(CollectionType.LIST);
- } else if
(setInterface.equals(transportedClass.isParameterized().getBaseType())) {
+ } else if (setInterface.equals(parameterized.getBaseType())) {
methodBuilder.setCollectionType(CollectionType.SET);
} else {
poison("Requests that return collections may be declared with"
=======================================
---
/trunk/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
Sun Oct 3 11:34:32 2010
+++
/trunk/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
Tue Oct 5 10:23:33 2010
@@ -16,6 +16,7 @@
package com.google.gwt.requestfactory;
import com.google.gwt.requestfactory.client.impl.SimpleEntityProxyIdTest;
+import com.google.gwt.requestfactory.rebind.model.RequestFactoryModelTest;
import com.google.gwt.requestfactory.server.JsonRequestProcessorTest;
import
com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistryTest;
import com.google.gwt.requestfactory.server.RequestPropertyTest;
@@ -33,6 +34,7 @@
suite.addTestSuite(SimpleEntityProxyIdTest.class);
suite.addTestSuite(JsonRequestProcessorTest.class);
suite.addTestSuite(ReflectionBasedOperationRegistryTest.class);
+ suite.addTestSuite(RequestFactoryModelTest.class);
suite.addTestSuite(RequestPropertyTest.class);
return suite;
}
=======================================
--- /trunk/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
Mon Oct 4 15:35:00 2010
+++ /trunk/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
Tue Oct 5 10:23:33 2010
@@ -80,7 +80,7 @@
Map<String, SimpleBar> value = (Map<String, SimpleBar>)
req.getSession().getAttribute(
SimpleBar.class.getCanonicalName());
if (value == null) {
- value = reset();
+ value = resetImpl();
}
return value;
}
@@ -95,7 +95,11 @@
return toReturn;
}
- public static synchronized Map<String, SimpleBar> reset() {
+ public static void reset() {
+ resetImpl();
+ }
+
+ public static synchronized Map<String, SimpleBar> resetImpl() {
Map<String, SimpleBar> instance = new HashMap<String, SimpleBar>();
// fixtures
SimpleBar s1 = new SimpleBar();
=======================================
--- /trunk/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
Tue Oct 5 09:11:00 2010
+++ /trunk/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
Tue Oct 5 10:23:33 2010
@@ -97,7 +97,7 @@
Map<Long, SimpleFoo> value = (Map<Long, SimpleFoo>)
req.getSession().getAttribute(
SimpleFoo.class.getCanonicalName());
if (value == null) {
- value = reset();
+ value = resetImpl();
}
return value;
}
@@ -232,7 +232,11 @@
}
}
- public static synchronized Map<Long, SimpleFoo> reset() {
+ public static void reset() {
+ resetImpl();
+ }
+
+ public static synchronized Map<Long, SimpleFoo> resetImpl() {
Map<Long, SimpleFoo> instance = new HashMap<Long, SimpleFoo>();
// fixtures
SimpleFoo s1 = new SimpleFoo();
=======================================
---
/trunk/user/test/com/google/gwt/requestfactory/server/SimpleFooString.java
Mon Oct 4 15:35:00 2010
+++
/trunk/user/test/com/google/gwt/requestfactory/server/SimpleFooString.java
Tue Oct 5 10:23:33 2010
@@ -73,7 +73,7 @@
SimpleFooString value = (SimpleFooString)
req.getSession().getAttribute(
SimpleFooString.class.getCanonicalName());
if (value == null) {
- value = reset();
+ value = resetImpl();
}
return value;
}
@@ -99,7 +99,11 @@
return get();
}
- public static synchronized SimpleFooString reset() {
+ public static void reset() {
+ resetImpl();
+ }
+
+ public static synchronized SimpleFooString resetImpl() {
SimpleFooString instance = new SimpleFooString();
HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
if (req == null) {
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors