Adding 2.1.0 tag
git-svn-id: https://google-web-toolkit.googlecode.com/svn/tags/2.1.0@9145 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/build.xml b/dev/build.xml
index 00a5c2b..8af5836 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -41,6 +41,7 @@
<pathelement location="${gwt.tools.lib}/w3c/sac/sac-1.3.jar" />
<pathelement location="${gwt.tools.lib}/w3c/flute/flute-1.3-gg1.jar" />
<pathelement location="${gwt.tools}/redist/json/r2_20080312/json-1.5.jar" />
+ <pathelement location="${gwt.tools.lib}/hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
<pathelement location="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
<pathelement location="${gwt.build.lib}/gwt-dev-${build.host.platform}.jar" />
<pathelement location="${alldeps.jar}" />
diff --git a/distro-source/build.xml b/distro-source/build.xml
index 1bb9439..7918e52 100755
--- a/distro-source/build.xml
+++ b/distro-source/build.xml
@@ -22,6 +22,7 @@
<zipfileset file="${gwt.build.lib}/gwt-dev.jar" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-user.jar" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-servlet.jar" prefix="${project.distname}" />
+ <zipfileset file="${gwt.build.lib}/gwt-servlet-deps.jar" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-benchmark-viewer.war" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-soyc-vis.jar" prefix="${project.distname}" />
<zipfileset file="${gwt.build.lib}/gwt-api-checker.jar" prefix="${project.distname}" />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
index 81dae75..51ce66f 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
@@ -97,6 +97,7 @@
}
}
}, KeyUpEvent.getType());
+ this.favorite.setVisible(false);
}
/**
@@ -156,8 +157,9 @@
private void edit(RequestContext requestContext) {
editorDriver = GWT.create(Driver.class);
editorDriver.initialize(requestFactory, personEditor);
-
+
if (requestContext == null) {
+ this.favorite.setVisible(true);
fetchAndEdit();
return;
}
diff --git a/samples/validation/build.xml b/samples/validation/build.xml
index 5f960a7..844f146 100755
--- a/samples/validation/build.xml
+++ b/samples/validation/build.xml
@@ -20,7 +20,9 @@
<pathelement location="${gwt.tools.lib}/javax/xml/stream/stax-api-1.0-2.jar" />
</path>
<fileset id="sample.server.libs" dir="${gwt.tools.lib}">
+ <include name="javax/validation/validation-api-1.0.0.GA-sources.jar" />
<include name="hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
+ <include name="hibernate/validator/hibernate-validator-4.1.0.Final-sources.jar" />
<include name="apache/log4j/log4j-1.2.16.jar" />
<include name="slf4j/slf4j-api/slf4j-api-1.6.1.jar" />
<include name="slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar" />
diff --git a/servlet/build.xml b/servlet/build.xml
index 6440445..e30dace 100755
--- a/servlet/build.xml
+++ b/servlet/build.xml
@@ -4,12 +4,29 @@
<import file="${gwt.root}/common.ant.xml" />
<property.ensure name="gwt.user.root" location="${gwt.root}/user" />
+ <property.ensure name="gwt.dev.build" location="${gwt.build.out}/dev" />
<property.ensure name="gwt.user.build" location="${gwt.build.out}/user" />
+ <property.ensure name="gwt.dev.bin" location="${gwt.dev.build}/bin" />
<property.ensure name="gwt.user.bin" location="${gwt.user.build}/bin" />
- <target name="build" description="Packages this project into a jar">
+ <property name="project.lib.deps"
+ location="${gwt.build.lib}/gwt-${ant.project.name}-deps.jar" />
+
+ <target name="build" description="Packages this project into a jar"
+ depends="-servlet, -deps" />
+
+ <target name="clean" description="Cleans this project's intermediate and output files">
+ <delete file="${project.lib}" />
+ <delete file="${project.lib.deps}" />
+ </target>
+
+ <target name="-servlet" description="Packages this project into a jar">
<mkdir dir="${gwt.build.lib}" />
<gwt.jar>
+ <fileset dir="${gwt.dev.bin}">
+ <include name="com/google/gwt/dev/asm/**" />
+ <include name="com/google/gwt/dev/util/Name*.class" />
+ </fileset>
<fileset dir="${gwt.user.bin}">
<exclude name="**/rebind/**" />
<exclude name="**/tools/**" />
@@ -21,13 +38,15 @@
<exclude name="com/google/gwt/junit/server/**" />
<exclude name="com/google/gwt/benchmarks/*" />
</fileset>
+ </gwt.jar>
+ </target>
+
+ <target name="-deps" description="Packages this project's dependencies into a jar">
+ <mkdir dir="${gwt.build.lib}" />
+ <gwt.jar destfile="${project.lib.deps}">
<!-- The following two jars satisfy RequestFactory dependencies. -->
<zipfileset src="${gwt.tools.redist}/json/r2_20080312/json-1.5.jar" />
<zipfileset src="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
</gwt.jar>
</target>
-
- <target name="clean" description="Cleans this project's intermediate and output files">
- <delete file="${project.lib}" />
- </target>
</project>
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
index be744e4..9922620 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
@@ -52,17 +52,12 @@
public void setRequestMethods(List<RequestMethod> requestMethods) {
toReturn.requestMethods = requestMethods;
}
-
- public void setServiceClass(Class<?> serviceClass) {
- toReturn.serviceClass = serviceClass;
- }
}
private String interfaceName;
private String methodName;
private String packageName;
private List<RequestMethod> requestMethods;
- private Class<?> serviceClass;
private String simpleSourceName;
private ContextMethod() {
@@ -95,10 +90,6 @@
return Collections.unmodifiableList(requestMethods);
}
- public Class<?> getServiceClass() {
- return serviceClass;
- }
-
public String getSimpleSourceName() {
return simpleSourceName;
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index 3803b1f..77ce999 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -28,13 +28,13 @@
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.ProxyForName;
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.ServiceName;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -159,13 +159,12 @@
private void buildContextMethod(ContextMethod.Builder contextBuilder,
JClassType contextType) throws UnableToCompleteException {
Service serviceAnnotation = contextType.getAnnotation(Service.class);
- if (serviceAnnotation == null) {
+ ServiceName serviceNameAnnotation = contextType.getAnnotation(ServiceName.class);
+ if (serviceAnnotation == null && serviceNameAnnotation == null) {
poison("RequestContext subtype %s is missing a @%s annotation",
contextType.getQualifiedSourceName(), Service.class.getSimpleName());
return;
}
- Class<?> serviceClass = serviceAnnotation.value();
- contextBuilder.setServiceClass(serviceClass);
List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
for (JMethod method : contextType.getInheritableMethods()) {
@@ -177,8 +176,7 @@
RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
methodBuilder.setDeclarationMethod(method);
- if (!validateContextMethodAndSetDataType(methodBuilder, method,
- serviceClass)) {
+ if (!validateContextMethodAndSetDataType(methodBuilder, method)) {
continue;
}
@@ -193,20 +191,6 @@
throw new UnableToCompleteException();
}
- /**
- * Returns 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 {
entityProxyType = ModelUtils.ensureBaseType(entityProxyType);
@@ -225,16 +209,13 @@
// Get the server domain object type
ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
- if (proxyFor == null) {
- poison("The %s type does not have a @%s annotation",
+ ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class);
+ if (proxyFor == null && proxyForName == null) {
+ poison("The %s type does not have a @%s or @%s annotation",
entityProxyType.getQualifiedSourceName(),
- ProxyFor.class.getSimpleName());
+ ProxyFor.class.getSimpleName(), ProxyForName.class.getSimpleName());
// early exit, because further processing causes NPEs in numerous spots
die(poisonedMessage());
- } else {
- Class<?> domainType = proxyFor.value();
- builder.setProxyFor(domainType);
- validateDomainType(domainType);
}
// Look at the methods declared on the EntityProxy
@@ -267,9 +248,7 @@
}
validateTransportableType(methodBuilder, transportedType, false);
RequestMethod requestMethod = methodBuilder.build();
- if (validateDomainBeanMethod(requestMethod, builder)) {
- requestMethods.add(requestMethod);
- }
+ requestMethods.add(requestMethod);
}
builder.setRequestMethods(requestMethods);
@@ -280,20 +259,6 @@
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));
poisoned = true;
@@ -303,7 +268,7 @@
* Examine a RequestContext method to see if it returns a transportable type.
*/
private boolean validateContextMethodAndSetDataType(
- RequestMethod.Builder methodBuilder, JMethod method, Class<?> serviceClass)
+ RequestMethod.Builder methodBuilder, JMethod method)
throws UnableToCompleteException {
JClassType requestReturnType = method.getReturnType().isInterface();
JClassType invocationReturnType;
@@ -314,40 +279,13 @@
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);
@@ -363,152 +301,14 @@
// Validate the parameters
boolean paramsOk = true;
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;
+ return validateTransportableType(methodBuilder, invocationReturnType, true);
}
/**
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
index 7b60641..cdcb90f 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -16,25 +16,31 @@
package com.google.gwt.requestfactory.server;
import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
/**
* A security provider that enforces
* {@link com.google.gwt.requestfactory.shared.Service} annotations.
*/
-public class DefaultSecurityProvider implements RequestSecurityProvider {
+class DefaultSecurityProvider implements RequestSecurityProvider {
public void checkClass(Class<?> clazz) throws SecurityException {
Service service = clazz.getAnnotation(Service.class);
- if (service == null) {
- throw new SecurityException(
- "Class " + clazz.getName() + " does not have a @Service annotation.");
+ ServiceName serviceName = clazz.getAnnotation(ServiceName.class);
+ String className;
+ if (service != null) {
+ className = service.value().getName();
+ } else if (serviceName != null) {
+ className = serviceName.value();
+ } else {
+ throw new SecurityException("Class " + clazz.getName()
+ + " does not have a @Service annotation.");
}
try {
- Class.forName(service.value().getCanonicalName());
+ Class.forName(className);
} catch (ClassNotFoundException e) {
- throw new SecurityException(
- "Class " + service.value() + " from @Service annotation on " + clazz
- + " cannot be loaded.");
+ throw new SecurityException("Class " + className
+ + " from @Service annotation on " + clazz + " cannot be loaded.");
}
}
diff --git a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
index 5257e62..d067044 100644
--- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
@@ -18,6 +18,7 @@
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ProxyForName;
import com.google.gwt.requestfactory.shared.ServerFailure;
import com.google.gwt.requestfactory.shared.WriteOperation;
import com.google.gwt.requestfactory.shared.impl.CollectionProperty;
@@ -59,7 +60,7 @@
/**
* An implementation of RequestProcessor for JSON encoded payloads.
*/
-public class JsonRequestProcessor implements RequestProcessor<String> {
+class JsonRequestProcessor implements RequestProcessor<String> {
// TODO should we consume String, InputStream, or JSONObject?
private class DvsData {
@@ -159,7 +160,12 @@
private static final Logger log = Logger.getLogger(JsonRequestProcessor.class.getName());
- // Decodes a string encoded as web-safe base64
+ /**
+ * Decodes a String encoded as web-safe base64.
+ *
+ * @param encoded the encoded String
+ * @return a decoded String
+ */
public static String base64Decode(String encoded) {
byte[] decodedBytes;
try {
@@ -171,7 +177,12 @@
return new String(decodedBytes);
}
- // Encodes a string with web-safe base64
+ /**
+ * Encodes a String as web-safe base64.
+ *
+ * @param decoded the decoded String
+ * @return an encoded String
+ */
public static String base64Encode(String decoded) {
byte[] decodedBytes = decoded.getBytes();
return Base64Utils.toBase64(decodedBytes);
@@ -218,7 +229,7 @@
private Map<EntityKey, EntityData> afterDvsDataMap = new HashMap<EntityKey, EntityData>();
- @SuppressWarnings(value = {"unchecked", "rawtypes"})
+ // TODO - document
public Collection<Property<?>> allProperties(
Class<? extends EntityProxy> clazz) throws IllegalArgumentException {
return getPropertiesFromRecordProxyType(clazz).values();
@@ -344,8 +355,9 @@
* 2. Merge the following and the object resolution code in getEntityKey.
* 3. Update the involvedKeys set.
*/
- ProxyFor service = parameterType.getAnnotation(ProxyFor.class);
- if (service != null) {
+ ProxyFor proxyFor = parameterType.getAnnotation(ProxyFor.class);
+ ProxyForName proxyForName = parameterType.getAnnotation(ProxyForName.class);
+ if (proxyFor != null || proxyForName != null) {
EntityKey entityKey = getEntityKey(parameterValue.toString());
DvsData dvsData = dvsDataMap.get(entityKey);
@@ -361,8 +373,9 @@
}
if (EntityProxyId.class.isAssignableFrom(parameterType)) {
EntityKey entityKey = getEntityKey(parameterValue.toString());
- ProxyFor service = entityKey.proxyType.getAnnotation(ProxyFor.class);
- if (service == null) {
+ ProxyFor proxyFor = entityKey.proxyType.getAnnotation(ProxyFor.class);
+ ProxyForName proxyForName = entityKey.proxyType.getAnnotation(ProxyForName.class);
+ if (proxyFor == null && proxyForName == null) {
throw new IllegalArgumentException("Unknown service, unable to decode "
+ parameterValue);
}
@@ -375,6 +388,9 @@
/*
* Encode a property value to be sent across the wire.
+ *
+ * @param value a value Object
+ * @return an encoded value Object
*/
public Object encodePropertyValue(Object value) {
if (value == null) {
@@ -568,6 +584,16 @@
if (dtoAnn != null) {
return (Class<Object>) dtoAnn.value();
}
+ ProxyForName name = record.getAnnotation(ProxyForName.class);
+ if (name != null) {
+ try {
+ return (Class<Object>) Class.forName(name.value(), false,
+ Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Could not find ProxyForName class",
+ e);
+ }
+ }
throw new IllegalArgumentException("Proxy class " + record.getName()
+ " missing ProxyFor annotation");
}
@@ -710,7 +736,6 @@
/**
* Returns the property fields (name => type) for a record.
*/
- @SuppressWarnings("unchecked")
public Map<String, Property<?>> getPropertiesFromRecordProxyType(
Class<? extends EntityProxy> record) throws SecurityException {
if (!EntityProxy.class.isAssignableFrom(record)) {
@@ -722,7 +747,7 @@
for (Method method : methods) {
String methodName = method.getName();
String propertyName = null;
- Property newProperty = null;
+ Property<?> newProperty = null;
if (methodName.startsWith("get")) {
propertyName = Introspector.decapitalize(methodName.substring(3));
if (propertyName.length() == 0) {
@@ -743,7 +768,7 @@
if (newProperty == null) {
continue;
}
- Property existing = properties.put(propertyName, newProperty);
+ Property<?> existing = properties.put(propertyName, newProperty);
if (existing != null && !existing.equals(newProperty)) {
throw new IllegalStateException(String.format(
"In %s, mismatched getter and setter types for property %s, "
@@ -754,8 +779,8 @@
return properties;
}
- @SuppressWarnings("unchecked")
- public Property getPropertyFromGenericType(String propertyName, Type type) {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public Property<?> getPropertyFromGenericType(String propertyName, Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Class<?> rawType = (Class<Object>) pType.getRawType();
@@ -765,7 +790,7 @@
Type leafType = typeArgs[0];
if (leafType instanceof Class) {
return new CollectionProperty(propertyName, rawType,
- (Class) leafType);
+ (Class<?>) leafType);
}
}
}
@@ -830,6 +855,21 @@
operation = getOperation(operationName);
Class<?> domainClass = Class.forName(operation.getDomainClassName());
+
+ /*
+ * The use of JRP's classloader mirrors the use of Class.forName elsewhere.
+ * It's wrong, but it's consistently wrong.
+ */
+ RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
+ log, new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+ JsonRequestProcessor.class.getClassLoader()));
+ validator.validateRequestContext(operationName.substring(0,
+ operationName.indexOf("::")));
+ if (validator.isPoisoned()) {
+ log.severe("Unable to validate RequestContext");
+ throw new RuntimeException();
+ }
+
Method domainMethod = domainClass.getMethod(
operation.getDomainMethod().getName(), operation.getParameterTypes());
if (Modifier.isStatic(domainMethod.getModifiers()) == operation.isInstance()) {
@@ -1210,9 +1250,8 @@
Object newId = getRawPropertyValueFromDatastore(entityInstance,
Constants.ENTITY_ID_PROPERTY);
if (newId == null) {
- log.warning("Record with futureId " + originalEntityKey.encodedId
- + " not persisted");
- return null; // no changeRecord for this CREATE.
+ // no changeRecord for this CREATE.
+ return null;
}
newId = encodeId(newId);
@@ -1562,6 +1601,14 @@
if (pFor != null) {
fieldType = pFor.value();
}
+ ProxyForName pFN = fieldType.getAnnotation(ProxyForName.class);
+ if (pFN != null) {
+ try {
+ fieldType = Class.forName(pFN.value(), false,
+ Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
// TODO: remove override declared method return type with field type
if (!fieldType.equals(field.getType())) {
fieldType = field.getType();
diff --git a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
index c769ee0..0b82f15 100644
--- a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
+++ b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
@@ -18,7 +18,7 @@
/**
* Maps operation name to {RequestDefinition}.
*/
-public interface OperationRegistry {
+interface OperationRegistry {
RequestDefinition getOperation(String operationName);
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
index b2c89bf..0c81170 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -18,8 +18,10 @@
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.ProxyForName;
import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -30,13 +32,12 @@
/**
* OperationRegistry which uses the operation name as a convention for
- * reflection to a method on a class, and returns an appropriate {@link
- * com.google.gwt.requestfactory.server.RequestDefinition}.
+ * reflection to a method on a class, and returns an appropriate
+ * {@link com.google.gwt.requestfactory.server.RequestDefinition}.
*/
-public class ReflectionBasedOperationRegistry implements OperationRegistry {
+class ReflectionBasedOperationRegistry implements OperationRegistry {
- class ReflectiveRequestDefinition
- implements RequestDefinition {
+ class ReflectiveRequestDefinition implements RequestDefinition {
private Class<?> requestClass;
@@ -49,7 +50,8 @@
private boolean isInstance;
public ReflectiveRequestDefinition(Class<?> requestClass,
- Method requestMethod, Class<?> domainClass, Method domainMethod, boolean isInstance) {
+ Method requestMethod, Class<?> domainClass, Method domainMethod,
+ boolean isInstance) {
this.requestClass = requestClass;
this.requestMethod = requestMethod;
this.domainClass = domainClass;
@@ -94,20 +96,29 @@
Class<?> requestReturnType = getReturnTypeFromParameter(requestMethod,
requestMethod.getGenericReturnType());
if (EntityProxy.class.isAssignableFrom(requestReturnType)) {
- ProxyFor annotation =
- requestReturnType.getAnnotation(ProxyFor.class);
+ ProxyFor annotation = requestReturnType.getAnnotation(ProxyFor.class);
+ ProxyForName nameAnnotation = requestReturnType.getAnnotation(ProxyForName.class);
+
+ Class<?> dtoClass = null;
if (annotation != null) {
- Class<?> dtoClass = annotation.value();
- if (!dtoClass.equals(domainReturnType)) {
+ dtoClass = annotation.value();
+ } else if (nameAnnotation != null) {
+ try {
+ dtoClass = Class.forName(nameAnnotation.value(), false,
+ Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
- "Type mismatch between " + domainMethod + " return type, and "
- + requestReturnType + "'s ProxyFor annotation "
- + dtoClass);
+ "Unknown type specified in ProxyForName annotation", e);
}
} else {
throw new IllegalArgumentException(
"Missing ProxyFor annotation on proxy type " + requestReturnType);
}
+ if (!dtoClass.equals(domainReturnType)) {
+ throw new IllegalArgumentException("Type mismatch between "
+ + domainMethod + " return type, and " + requestReturnType
+ + "'s ProxyFor annotation " + dtoClass);
+ }
return requestReturnType;
}
// primitive ?
@@ -172,7 +183,7 @@
} else {
return null;
}
-
+
if (toExamine instanceof ParameterizedType) {
// if type is for example, RequestObject<List<T>> we return T
return getTypeArgument((ParameterizedType) toExamine);
@@ -186,13 +197,18 @@
private RequestSecurityProvider securityProvider;
+ /**
+ * Constructs a {@link ReflectionBasedOperationRegistry} instance with a given
+ * {@link RequestSecurityProvider}.
+ *
+ * @param securityProvider a {@link RequestSecurityProvider} instance.
+ */
public ReflectionBasedOperationRegistry(
RequestSecurityProvider securityProvider) {
this.securityProvider = securityProvider;
}
/**
-
* Turns an operation in the form of package.requestClass::method into a
* RequestDefinition via reflection.
*/
@@ -208,25 +224,34 @@
this.getClass().getClassLoader());
securityProvider.checkClass(requestClass);
Service domainClassAnnotation = requestClass.getAnnotation(Service.class);
+ ServiceName domainClassNameAnnotation = requestClass.getAnnotation(ServiceName.class);
+ Class<?> domainClass;
if (domainClassAnnotation != null) {
- Class<?> domainClass = domainClassAnnotation.value();
- Method requestMethod = findMethod(requestClass, domainMethodName);
- Method domainMethod = findMethod(domainClass, domainMethodName);
- if (requestMethod != null && domainMethod != null) {
- boolean isInstance = InstanceRequest.class.isAssignableFrom(requestMethod.getReturnType());
- if (isInstance == Modifier.isStatic(domainMethod.getModifiers())) {
- throw new IllegalArgumentException("domain method " + domainMethod
- + " and interface method " + requestMethod
- + " don't match wrt instance/static");
- }
- return new ReflectiveRequestDefinition(requestClass, requestMethod,
- domainClass, domainMethod, isInstance);
- }
+ domainClass = domainClassAnnotation.value();
+ } else if (domainClassNameAnnotation != null) {
+ domainClass = Class.forName(domainClassNameAnnotation.value(), false,
+ Thread.currentThread().getContextClassLoader());
+ } else {
+ return null;
}
+
+ Method requestMethod = findMethod(requestClass, domainMethodName);
+ Method domainMethod = findMethod(domainClass, domainMethodName);
+ if (requestMethod != null && domainMethod != null) {
+ boolean isInstance = InstanceRequest.class.isAssignableFrom(requestMethod.getReturnType());
+ if (isInstance == Modifier.isStatic(domainMethod.getModifiers())) {
+ throw new IllegalArgumentException("domain method " + domainMethod
+ + " and interface method " + requestMethod
+ + " don't match wrt instance/static");
+ }
+ return new ReflectiveRequestDefinition(requestClass, requestMethod,
+ domainClass, domainMethod, isInstance);
+ }
+
return null;
} catch (ClassNotFoundException e) {
- throw new SecurityException(
- "Access to non-existent class " + reqClassName);
+ throw new SecurityException("Access to non-existent class "
+ + reqClassName);
}
}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java b/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
index 1e0db57..48ed9be 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
@@ -22,7 +22,7 @@
* Implemented by enums that define the mapping between request objects and
* service methods.
*/
-public interface RequestDefinition {
+interface RequestDefinition {
/**
* Returns the name of the (domain) class that contains the method to be
* invoked on the server.
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
new file mode 100644
index 0000000..4903bea
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -0,0 +1,1070 @@
+/*
+ * 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;
+
+import com.google.gwt.dev.asm.AnnotationVisitor;
+import com.google.gwt.dev.asm.ClassReader;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.EmptyVisitor;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.asm.signature.SignatureReader;
+import com.google.gwt.dev.asm.signature.SignatureVisitor;
+import com.google.gwt.dev.util.Name;
+import com.google.gwt.dev.util.Name.BinaryName;
+import com.google.gwt.dev.util.Name.SourceOrBinaryName;
+import com.google.gwt.requestfactory.client.impl.FindRequest;
+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.ProxyForName;
+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.ServiceName;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Encapsulates validation logic to determine if a {@link RequestFactory}
+ * interface, its {@link RequestContext}, and associated {@link EntityProxy}
+ * interfaces match their domain counterparts. This implementation examines the
+ * classfiles directly in order to avoid the need to load the types into the
+ * JVM.
+ */
+public class RequestFactoryInterfaceValidator {
+ /**
+ * An implementation of {@link Loader} that uses a {@link ClassLoader} to
+ * retrieve the class files.
+ */
+ public static class ClassLoaderLoader implements Loader {
+ private final ClassLoader loader;
+
+ public ClassLoaderLoader(ClassLoader loader) {
+ this.loader = loader;
+ }
+
+ public boolean exists(String resource) {
+ return loader.getResource(resource) != null;
+ }
+
+ public InputStream getResourceAsStream(String resource) {
+ return loader.getResourceAsStream(resource);
+ }
+ }
+
+ /**
+ * Abstracts the mechanism by which class files are loaded.
+ *
+ * @see ClassLoaderLoader
+ */
+ public interface Loader {
+ /**
+ * Returns true if the specified resource can be loaded.
+ */
+ boolean exists(String resource);
+
+ /**
+ * Returns an InputStream to access the specified resource, or
+ * <code>null</code> if no such resource exists.
+ */
+ InputStream getResourceAsStream(String resource);
+ }
+
+ /**
+ * Used internally as a placeholder for types that cannot be mapped to a
+ * domain object.
+ */
+ interface MissingDomainType {
+ }
+
+ /**
+ * Collects the ProxyFor or Service annotation from an EntityProxy or
+ * RequestContext type.
+ */
+ private class DomainMapper extends EmptyVisitor {
+ private final ErrorContext logger;
+ private String domainInternalName;
+
+ public DomainMapper(ErrorContext logger) {
+ this.logger = logger;
+ logger.spam("Finding domain mapping annotation");
+ }
+
+ public String getDomainInternalName() {
+ return domainInternalName;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if ((access & Opcodes.ACC_INTERFACE) == 0) {
+ logger.poison("Type must be an interface");
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ boolean foundProxy = desc.equals(Type.getDescriptor(ProxyFor.class));
+ boolean foundProxyName = desc.equals(Type.getDescriptor(ProxyForName.class));
+ boolean foundService = desc.equals(Type.getDescriptor(Service.class));
+ boolean foundServiceName = desc.equals(Type.getDescriptor(ServiceName.class));
+
+ if (foundProxy || foundService) {
+ return new EmptyVisitor() {
+
+ @Override
+ public void visit(String name, Object value) {
+ domainInternalName = ((Type) value).getInternalName();
+ }
+
+ };
+ }
+
+ if (foundProxyName || foundServiceName) {
+ return new EmptyVisitor() {
+ @Override
+ public void visit(String name, Object value) {
+ String sourceName = (String) value;
+
+ /*
+ * The input is a source name, so we need to convert it to an
+ * internal name. We'll do this by substituting dollar signs for the
+ * last slash in the name until there are no more slashes.
+ */
+ StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
+ while (!loader.exists(desc.toString() + ".class")) {
+ logger.spam("Did not find " + desc.toString());
+ int idx = desc.lastIndexOf("/");
+ if (idx == -1) {
+ return;
+ }
+ desc.setCharAt(idx, '$');
+ }
+
+ domainInternalName = desc.toString();
+ logger.spam(domainInternalName);
+ }
+ };
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Improves error messages by providing context for the user.
+ */
+ private class ErrorContext {
+ private final Logger logger;
+ private final ErrorContext parent;
+ private Type currentType;
+ private Method currentMethod;
+
+ public ErrorContext(Logger logger) {
+ this.logger = logger;
+ this.parent = null;
+ }
+
+ private ErrorContext(ErrorContext parent) {
+ this.logger = parent.logger;
+ this.parent = parent;
+ }
+
+ public void poison(String msg, Object... args) {
+ logger.logp(Level.SEVERE, currentType(), currentMethod(),
+ String.format(msg, args));
+ poisoned = true;
+ }
+
+ public void poison(String msg, Throwable t) {
+ logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
+ poisoned = true;
+ }
+
+ public ErrorContext setMethod(Method method) {
+ ErrorContext toReturn = new ErrorContext(this);
+ toReturn.currentMethod = method;
+ return toReturn;
+ }
+
+ public ErrorContext setType(Type type) {
+ ErrorContext toReturn = new ErrorContext(this);
+ toReturn.currentType = type;
+ return toReturn;
+ }
+
+ public void spam(String msg, Object... args) {
+ logger.logp(Level.FINEST, currentType(), currentMethod(),
+ String.format(msg, args));
+ }
+
+ private String currentMethod() {
+ if (currentMethod != null) {
+ return print(currentMethod);
+ }
+ if (parent != null) {
+ return parent.currentMethod();
+ }
+ return null;
+ }
+
+ private String currentType() {
+ if (currentType != null) {
+ return print(currentType);
+ }
+ if (parent != null) {
+ return parent.currentType();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Collects information about domain objects. This visitor is intended to be
+ * iteratively applied to collect all methods in a type hierarchy.
+ */
+ private class MethodsInHierarchyCollector extends EmptyVisitor {
+ private final ErrorContext logger;
+ private Set<RFMethod> methods = new LinkedHashSet<RFMethod>();
+ private Set<String> seen = new HashSet<String>();
+
+ private MethodsInHierarchyCollector(ErrorContext logger) {
+ this.logger = logger;
+ }
+
+ public Set<RFMethod> exec(String internalName) {
+ RequestFactoryInterfaceValidator.this.visit(logger, internalName, this);
+ return methods;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (!seen.add(name)) {
+ return;
+ }
+ if (!"java/lang/Object".equals(superName)) {
+ RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+ }
+ if (interfaces != null) {
+ for (String intf : interfaces) {
+ RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+ }
+ }
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ RFMethod method = new RFMethod(name, desc);
+ method.setDeclaredStatic((access & Opcodes.ACC_STATIC) != 0);
+ method.setDeclaredSignature(signature);
+ methods.add(method);
+ return null;
+ }
+ }
+
+ private static class RFMethod extends Method {
+ private boolean isDeclaredStatic;
+ private String signature;
+
+ public RFMethod(String name, String desc) {
+ super(name, desc);
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public boolean isDeclaredStatic() {
+ return isDeclaredStatic;
+ }
+
+ public void setDeclaredSignature(String signature) {
+ this.signature = signature;
+ }
+
+ public void setDeclaredStatic(boolean value) {
+ isDeclaredStatic = value;
+ }
+
+ @Override
+ public String toString() {
+ return (isDeclaredStatic ? "static " : "") + super.toString();
+ }
+ }
+
+ 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>();
+
+ public SupertypeCollector(ErrorContext logger) {
+ this.logger = logger;
+ }
+
+ public List<Type> exec(Type type) {
+ RequestFactoryInterfaceValidator.this.visit(logger,
+ type.getInternalName(), this);
+ return supertypes;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (!seen.add(name)) {
+ return;
+ }
+ supertypes.add(Type.getObjectType(name));
+ if (!"java/lang/Object".equals(name)) {
+ RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+ }
+ if (interfaces != null) {
+ for (String intf : interfaces) {
+ RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
+ Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
+ Enum.class, Number.class, String.class, Void.class)));
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ System.err.println("Usage: java -cp gwt-servlet.jar:your-code.jar "
+ + RequestFactoryInterfaceValidator.class.getCanonicalName()
+ + " com.example.MyRequestFactory");
+ System.exit(1);
+ }
+ RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
+ Logger.getLogger(RequestFactoryInterfaceValidator.class.getName()),
+ new ClassLoaderLoader(Thread.currentThread().getContextClassLoader()));
+ validator.validateRequestFactory(args[0]);
+ System.exit(validator.isPoisoned() ? 1 : 0);
+ }
+
+ private static String print(Method method) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(print(method.getReturnType())).append(" ").append(
+ method.getName()).append("(");
+ for (Type t : method.getArgumentTypes()) {
+ sb.append(print(t)).append(" ");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ private static String print(Type type) {
+ return SourceOrBinaryName.toSourceName(type.getClassName());
+ }
+
+ /**
+ * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
+ */
+ private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
+ /**
+ * The type {@link EntityProxy}.
+ */
+ private final Type entityProxyIntf = Type.getType(EntityProxy.class);
+ /**
+ * A placeholder type for client types that could not be resolved to a domain
+ * type.
+ */
+ private final Type errorType = Type.getType(MissingDomainType.class);
+ /**
+ * The type {@link InstanceRequest}.
+ */
+ private final Type instanceRequestIntf = Type.getType(InstanceRequest.class);
+ private final Loader loader;
+ /**
+ * A cache of all methods defined in a type hierarchy.
+ */
+ private final Map<Type, Set<RFMethod>> methodsInHierarchy = new HashMap<Type, Set<RFMethod>>();
+ private final ErrorContext parentLogger;
+ private boolean poisoned;
+ /**
+ * The type {@link Request}.
+ */
+ private final Type requestIntf = Type.getType(Request.class);
+ /**
+ * The type {@link RequestContext}.
+ */
+ private final Type requestContextIntf = Type.getType(RequestContext.class);
+ /**
+ * A map of a type to all types that it could be assigned to.
+ */
+ private final Map<Type, List<Type>> supertypes = new HashMap<Type, List<Type>>();
+ /**
+ * A set to prevent re-validation of a type.
+ */
+ private final Set<String> validatedTypes = new HashSet<String>();
+
+ /**
+ * Contains vaue types (e.g. Integer).
+ */
+ private final Set<Type> valueTypes = new HashSet<Type>();
+
+ public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
+ this.parentLogger = new ErrorContext(logger);
+ this.loader = loader;
+ for (Class<?> clazz : VALUE_TYPES) {
+ valueTypes.add(Type.getType(clazz));
+ }
+ }
+
+ /**
+ * Returns true if validation failed.
+ */
+ public boolean isPoisoned() {
+ return poisoned;
+ }
+
+ /**
+ * This method checks an EntityProxy interface against its peer domain object
+ * to determine if the server code would be able to process a request using
+ * the methods defined in the EntityProxy interface. It does not perform any
+ * checks as to whether or not the EntityProxy could actually be generated by
+ * the Generator.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements EntityProxy</li>
+ * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+ * annotation</li>
+ * <li>The domain object has getId() and getVersion() methods</li>
+ * <li>All property methods in the EntityProxy can be mapped onto an
+ * equivalent domain method</li>
+ * <li>All referenced EntityProxy types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * EntityProxy subtype
+ */
+ public void validateEntityProxy(String binaryName) {
+ if (!Name.isBinaryName(binaryName)) {
+ parentLogger.poison("%s is not a binary name", binaryName);
+ return;
+ }
+
+ // Don't revalidate the same type
+ if (!validatedTypes.add(binaryName)) {
+ return;
+ }
+
+ Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ ErrorContext logger = parentLogger.setType(proxyType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, entityProxyIntf, proxyType)) {
+ parentLogger.poison("%s is not a %s", print(proxyType),
+ EntityProxy.class.getSimpleName());
+ return;
+ }
+
+ // Find the domain type
+ Type domainType = getDomainType(logger, proxyType);
+ if (domainType == errorType) {
+ logger.poison(
+ "The type %s must be annotated with a @%s or @%s annotation",
+ BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
+ ProxyForName.class.getSimpleName());
+ return;
+ }
+
+ // Check for getId() and getVersion() in domain
+ checkIdAndVersion(logger, domainType);
+
+ // Collect all methods in the client proxy type
+ Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
+ proxyType);
+
+ // Find the equivalent domain getter/setter method
+ for (RFMethod clientPropertyMethod : clientPropertyMethods) {
+ // Ignore stableId(). Can't use descriptor due to overrides
+ if ("stableId".equals(clientPropertyMethod.getName())
+ && clientPropertyMethod.getArgumentTypes().length == 0) {
+ continue;
+ }
+ checkPropertyMethod(logger, clientPropertyMethod, domainType);
+ maybeCheckReferredProxies(logger, clientPropertyMethod);
+ }
+ }
+
+ /**
+ * This method checks a RequestContext interface against its peer domain
+ * domain object to determine if the server code would be able to process a
+ * request using the the methods defined in the RequestContext interface. It
+ * does not perform any checks as to whether or not the RequestContext could
+ * actually be generated by the Generator.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements RequestContext</li>
+ * <li><code>binaryName</code> has a {@link Service} or {@link ServiceName}
+ * annotation</li>
+ * <li>All service methods in the RequestContext can be mapped onto an
+ * equivalent domain method</li>
+ * <li>All referenced EntityProxy types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * RequestContext subtype
+ * @see #validateEntityProxy(String)
+ */
+ public void validateRequestContext(String binaryName) {
+ if (!Name.isBinaryName(binaryName)) {
+ parentLogger.poison("%s is not a binary name", binaryName);
+ return;
+ }
+
+ // Don't revalidate the same type
+ if (!validatedTypes.add(binaryName)) {
+ return;
+ }
+
+ if (FindRequest.class.getName().equals(binaryName)) {
+ // Ignore FindRequest, it's a huge hack
+ return;
+ }
+
+ Type requestContextType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ final ErrorContext logger = parentLogger.setType(requestContextType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, requestContextIntf, requestContextType)) {
+ logger.poison("%s is not a %s", print(requestContextType),
+ RequestContext.class.getSimpleName());
+ return;
+ }
+
+ Type domainServiceType = getDomainType(logger, requestContextType);
+ if (domainServiceType == errorType) {
+ logger.poison(
+ "The type %s must be annotated with a @%s or @%s annotation",
+ BinaryName.toSourceName(binaryName), Service.class.getSimpleName(),
+ ServiceName.class.getSimpleName());
+ return;
+ }
+
+ for (RFMethod method : getMethodsInHierarchy(logger, requestContextType)) {
+ // Ignore methods in RequestContext itself
+ if (findCompatibleMethod(logger, requestContextIntf, method, false, true) != null) {
+ continue;
+ }
+
+ // Check the client method against the domain
+ checkClientMethodInDomain(logger, method, domainServiceType);
+ maybeCheckReferredProxies(logger, method);
+ }
+ }
+
+ /**
+ * This method checks a RequestFactory interface.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs. It does not perform any
+ * checks as to whether or not the RequestContext could actually be generated
+ * by the Generator.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements RequestFactory</li>
+ * <li>All referenced RequestContext types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * RequestContext subtype
+ * @see #validateRequestContext(String)
+ */
+ public void validateRequestFactory(String binaryName) {
+ if (!Name.isBinaryName(binaryName)) {
+ parentLogger.poison("%s is not a binary name", binaryName);
+ return;
+ }
+
+ // Don't revalidate the same type
+ if (!validatedTypes.add(binaryName)) {
+ return;
+ }
+
+ Type requestFactoryType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ ErrorContext logger = parentLogger.setType(requestFactoryType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, Type.getType(RequestFactory.class),
+ requestFactoryType)) {
+ logger.poison("%s is not a %s", print(requestFactoryType),
+ RequestFactory.class.getSimpleName());
+ return;
+ }
+
+ // Validate each RequestContext method in the RF
+ for (Method contextMethod : getMethodsInHierarchy(logger,
+ requestFactoryType)) {
+ Type returnType = contextMethod.getReturnType();
+ if (isAssignable(logger, requestContextIntf, returnType)) {
+ validateRequestContext(returnType.getClassName());
+ }
+ }
+ }
+
+ /**
+ * Check that a given method RequestContext method declaration can be mapped
+ * to the server's domain type.
+ */
+ private void checkClientMethodInDomain(ErrorContext logger, RFMethod method,
+ Type domainServiceType) {
+ logger = logger.setMethod(method);
+
+ // Create a "translated" method declaration to search for
+ // Request<BlahProxy> foo(int a, BarProxy bar) -> Blah foo(int a, Bar bar);
+ Type returnType = getReturnType(logger, method);
+ Method searchFor = createDomainMethod(logger, new Method(method.getName(),
+ returnType, method.getArgumentTypes()));
+
+ RFMethod found = findCompatibleMethod(logger, domainServiceType, searchFor);
+
+ if (found != null) {
+ boolean isInstance = isAssignable(logger, instanceRequestIntf,
+ method.getReturnType());
+ if (isInstance && found.isDeclaredStatic()) {
+ logger.poison("The method %s is declared to return %s, but the"
+ + " service method is static", method.getName(),
+ InstanceRequest.class.getCanonicalName());
+ } else if (!isInstance && !found.isDeclaredStatic) {
+ logger.poison("The method %s is declared to return %s, but the"
+ + " service method is not static", method.getName(),
+ Request.class.getCanonicalName());
+ }
+ }
+ }
+
+ /**
+ * Check that the domain object has <code>getId()</code> and
+ * <code>getVersion</code> methods.
+ */
+ private void checkIdAndVersion(ErrorContext logger, Type domainType) {
+ logger = logger.setType(domainType);
+ Method getIdString = new Method("getId", "()Ljava/lang/String;");
+ Method getIdLong = new Method("getId", "()Ljava/lang/Long;");
+ if (findCompatibleMethod(logger, domainType, getIdString, false, true) == null
+ && findCompatibleMethod(logger, domainType, getIdLong, false, true) == null) {
+ logger.poison("Did not find a getId() method that"
+ + " returns a String or a Long");
+ }
+
+ Method getVersion = new Method("getVersion", "()Ljava/lang/Integer;");
+ if (findCompatibleMethod(logger, domainType, getVersion) == null) {
+ logger.poison("Did not find a getVersion() method"
+ + " that returns an Integer");
+ }
+ }
+
+ /**
+ * Ensure that the given property method on an EntityProxy exists on the
+ * domain object.
+ */
+ private void checkPropertyMethod(ErrorContext logger,
+ Method clientPropertyMethod, Type domainType) {
+ logger = logger.setMethod(clientPropertyMethod);
+
+ findCompatibleMethod(logger, domainType,
+ createDomainMethod(logger, clientPropertyMethod));
+ }
+
+ /**
+ * Convert a method declaration using client types (e.g. FooProxy) to domain
+ * types (e.g. Foo).
+ */
+ private Method createDomainMethod(ErrorContext logger, Method clientMethod) {
+ Type[] args = clientMethod.getArgumentTypes();
+ for (int i = 0, j = args.length; i < j; i++) {
+ args[i] = getDomainType(logger, args[i]);
+ }
+ Type returnType = getDomainType(logger, clientMethod.getReturnType());
+ return new Method(clientMethod.getName(), returnType, args);
+ }
+
+ /**
+ * Finds a compatible method declaration in <code>domainType</code>'s
+ * hierarchy that is assignment-compatible with the given Method.
+ */
+ private RFMethod findCompatibleMethod(final ErrorContext logger,
+ Type domainType, Method searchFor) {
+ return findCompatibleMethod(logger, domainType, searchFor, true, false);
+ }
+
+ /**
+ * Finds a compatible method declaration in <code>domainType</code>'s
+ * hierarchy that is assignment-compatible with the given Method.
+ */
+ private RFMethod findCompatibleMethod(final ErrorContext logger,
+ Type domainType, Method searchFor, boolean mustFind,
+ boolean allowOverloads) {
+ String methodName = searchFor.getName();
+ Type[] clientArgs = searchFor.getArgumentTypes();
+ Type clientReturnType = searchFor.getReturnType();
+ // Pull all methods out of the domain type
+ Map<String, List<RFMethod>> domainLookup = new LinkedHashMap<String, List<RFMethod>>();
+ for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
+ List<RFMethod> list = domainLookup.get(method.getName());
+ if (list == null) {
+ list = new ArrayList<RFMethod>();
+ domainLookup.put(method.getName(), list);
+ }
+ list.add(method);
+ }
+
+ // Find the matching method in the domain object
+ List<RFMethod> methods = domainLookup.get(methodName);
+ if (methods == null) {
+ if (mustFind) {
+ logger.poison("Could not find any methods named %s in %s", methodName,
+ print(domainType));
+ }
+ return null;
+ }
+ if (methods.size() > 1 && !allowOverloads) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("Method overloads found in type %s named %s:\n",
+ print(domainType), methodName));
+ for (RFMethod method : methods) {
+ sb.append(" ").append(print(method)).append("\n");
+ }
+ logger.poison(sb.toString());
+ return null;
+ }
+
+ // Check each overloaded name
+ for (RFMethod domainMethod : methods) {
+ // Box any primitive types to simplify compotibility check
+ Type[] domainArgs = domainMethod.getArgumentTypes();
+ for (int i = 0, j = domainArgs.length; i < j; i++) {
+ domainArgs[i] = maybeBoxType(domainArgs[i]);
+ }
+ Type domainReturnType = maybeBoxType(domainMethod.getReturnType());
+
+ /*
+ * Make sure the client args can be passed into the domain args and the
+ * domain return type into the client return type.
+ */
+ if (isAssignable(logger, domainArgs, clientArgs)
+ && isAssignable(logger, clientReturnType, domainReturnType)) {
+
+ logger.spam("Mapped client method " + print(searchFor) + " to "
+ + print(domainMethod));
+ return domainMethod;
+ }
+ }
+ if (mustFind) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(
+ "Could not find matching method in %s:\nPossible matches:\n",
+ print(domainType)));
+ for (RFMethod domainMethod : methods) {
+ sb.append(" ").append(print(domainMethod)).append("\n");
+ }
+ logger.poison(sb.toString());
+ }
+ return null;
+ }
+
+ /**
+ * This looks like it should be a utility method somewhere else, but I can't
+ * find it.
+ */
+ private Type getBoxedType(Type primitive) {
+ switch (primitive.getSort()) {
+ case Type.BOOLEAN:
+ return Type.getType(Boolean.class);
+ case Type.BYTE:
+ return Type.getType(Byte.class);
+ case Type.CHAR:
+ return Type.getType(Character.class);
+ case Type.DOUBLE:
+ return Type.getType(Double.class);
+ case Type.FLOAT:
+ return Type.getType(Float.class);
+ case Type.INT:
+ return Type.getType(Integer.class);
+ case Type.LONG:
+ return Type.getType(Long.class);
+ case Type.SHORT:
+ return Type.getType(Short.class);
+ case Type.VOID:
+ return Type.getType(Void.class);
+ }
+ throw new RuntimeException(primitive.getDescriptor()
+ + " is not a primitive type");
+ }
+
+ /**
+ * Convert the type used in a client-side EntityProxy or RequestContext
+ * declaration to the equivalent domain type. Value types and supported
+ * collections are a pass-through. EntityProxy types will be resolved to their
+ * domain object type. RequestContext types will be resolved to their service
+ * object.
+ */
+ private Type getDomainType(ErrorContext logger, Type clientType) {
+ Type toReturn = clientToDomainType.get(clientType);
+ if (toReturn != null) {
+ return toReturn;
+ }
+ if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
+ toReturn = clientType;
+ } else {
+ logger = logger.setType(clientType);
+ DomainMapper pv = new DomainMapper(logger);
+ visit(logger, clientType.getInternalName(), pv);
+ if (pv.getDomainInternalName() == null) {
+ logger.poison("%s has no mapping to a domain type (e.g. @%s or @%s)",
+ print(clientType), ProxyFor.class.getSimpleName(),
+ Service.class.getSimpleName());
+ toReturn = errorType;
+ } else {
+ toReturn = Type.getObjectType(pv.getDomainInternalName());
+ }
+ }
+ clientToDomainType.put(clientType, toReturn);
+ return toReturn;
+ }
+
+ /**
+ * Collect all of the methods defined within a type hierarchy.
+ */
+ private Set<RFMethod> getMethodsInHierarchy(ErrorContext logger,
+ Type domainType) {
+ Set<RFMethod> toReturn = methodsInHierarchy.get(domainType);
+ if (toReturn == null) {
+ logger = logger.setType(domainType);
+ toReturn = new MethodsInHierarchyCollector(logger).exec(domainType.getInternalName());
+ methodsInHierarchy.put(domainType, Collections.unmodifiableSet(toReturn));
+ }
+ return toReturn;
+ }
+
+ /**
+ * Examines a generic RequestContext method declaration and determines the
+ * expected domain return type. This implementation is limited in that it will
+ * not attempt to resolve type bounds since that would essentially require
+ * implementing TypeOracle. In the case where the type bound cannot be
+ * resolved, this method will return Object's type.
+ */
+ private Type getReturnType(ErrorContext logger, RFMethod method) {
+ logger = logger.setMethod(method);
+ final String[] returnType = {"java/lang/Object"};
+ String signature = method.getSignature();
+
+ final int expectedCount;
+ if (method.getReturnType().equals(instanceRequestIntf)) {
+ expectedCount = 2;
+ } else if (method.getReturnType().equals(requestIntf)) {
+ expectedCount = 1;
+ } else {
+ logger.spam("Punting on " + signature);
+ return Type.getObjectType(returnType[0]);
+ }
+
+ // TODO(bobv): If a class-based TypeOracle is built, use that instead
+ new SignatureReader(signature).accept(new SignatureAdapter() {
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return new SignatureAdapter() {
+ int count;
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ if (++count == expectedCount) {
+ return new SignatureAdapter() {
+ @Override
+ public void visitClassType(String name) {
+ returnType[0] = name;
+ }
+ };
+ }
+ return super.visitTypeArgument(wildcard);
+ }
+ };
+ }
+ });
+
+ logger.spam("Extracted " + returnType[0]);
+ return Type.getObjectType(returnType[0]);
+ }
+
+ private List<Type> getSupertypes(ErrorContext logger, Type type) {
+ if (type.getSort() != Type.OBJECT) {
+ return Collections.emptyList();
+ }
+ List<Type> toReturn = supertypes.get(type);
+ if (toReturn != null) {
+ return toReturn;
+ }
+ logger = logger.setType(type);
+ toReturn = new SupertypeCollector(logger).exec(type);
+ supertypes.put(type, Collections.unmodifiableList(toReturn));
+ return toReturn;
+ }
+
+ private boolean isAssignable(ErrorContext logger, Type possibleSupertype,
+ Type possibleSubtype) {
+ // Fast-path for same type
+ if (possibleSupertype.equals(possibleSubtype)) {
+ return true;
+ }
+
+ // Box primitive types to make this simple
+ if (possibleSupertype.getSort() != Type.OBJECT) {
+ possibleSupertype = getBoxedType(possibleSupertype);
+ }
+ if (possibleSubtype.getSort() != Type.OBJECT) {
+ possibleSubtype = getBoxedType(possibleSubtype);
+ }
+
+ // Supertype calculation is cached
+ List<Type> allSupertypes = getSupertypes(logger, possibleSubtype);
+ return allSupertypes.contains(possibleSupertype);
+ }
+
+ private boolean isAssignable(ErrorContext logger, Type[] possibleSupertypes,
+ Type[] possibleSubtypes) {
+ // Check the same number of types
+ if (possibleSupertypes.length != possibleSubtypes.length) {
+ return false;
+ }
+ for (int i = 0, j = possibleSupertypes.length; i < j; i++) {
+ if (!isAssignable(logger, possibleSupertypes[i], possibleSubtypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isCollectionType(ErrorContext logger, Type type) {
+ return "java/util/List".equals(type.getInternalName())
+ || "java/util/Set".equals(type.getInternalName());
+ }
+
+ private boolean isValueType(ErrorContext logger, Type type) {
+ if (type.getSort() != Type.OBJECT) {
+ return true;
+ }
+ if (valueTypes.contains(type)) {
+ return true;
+ }
+ logger = logger.setType(type);
+ List<Type> types = getSupertypes(logger, type);
+ for (Type t : types) {
+ if (valueTypes.contains(t)) {
+ valueTypes.add(type);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Type maybeBoxType(Type maybePrimitive) {
+ if (maybePrimitive.getSort() == Type.OBJECT) {
+ return maybePrimitive;
+ }
+ return getBoxedType(maybePrimitive);
+ }
+
+ /**
+ * Examine an array of Types and call {@link #validateEntityProxy(String)} if
+ * the type is an EntityProxy.
+ */
+ private void maybeCheckEntityProxyType(ErrorContext logger, Type... types) {
+ for (Type type : types) {
+ if (isAssignable(logger, entityProxyIntf, type)) {
+ validateEntityProxy(type.getClassName());
+ }
+ }
+ }
+
+ /**
+ * Examine the arguments ond return value of a method and check any
+ * EntityProxies referred.
+ */
+ private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
+ Type[] argTypes = method.getArgumentTypes();
+ Type returnType = getReturnType(logger, method);
+
+ // Check EntityProxy args ond return types against the domain
+ maybeCheckEntityProxyType(logger, argTypes);
+ maybeCheckEntityProxyType(logger, returnType);
+ }
+
+ /**
+ * Load the classfile for the given binary name and apply the provided
+ * visitor.
+ *
+ * @return <code>true</code> if the visitor was successfully visited
+ */
+ private boolean visit(ErrorContext logger, String internalName,
+ ClassVisitor visitor) {
+ assert Name.isInternalName(internalName) : "internalName";
+ logger.spam("Visiting " + internalName);
+ InputStream inputStream = null;
+ try {
+ inputStream = loader.getResourceAsStream(internalName + ".class");
+ if (inputStream == null) {
+ logger.poison("Could not find class file for " + internalName);
+ return false;
+ }
+ ClassReader reader = new ClassReader(inputStream);
+ reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
+ | ClassReader.SKIP_FRAMES);
+ return true;
+ } catch (IOException e) {
+ logger.poison("Unable to open " + internalName, e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
index 91485a2..16e81f3 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
@@ -20,7 +20,7 @@
* an unexpected exception is caught. Includes an appropriate
* response of T to send to the client.
*/
-public class RequestProcessingException extends Exception {
+class RequestProcessingException extends Exception {
private final Object response;
/**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
index 93dd64e..ed72beb 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
@@ -21,7 +21,7 @@
* requests, and a serialized return value of the same type is returned.
* @param <T> the type of encoding used to serialize the request (e.g. String)
*/
-public interface RequestProcessor<T> {
+interface RequestProcessor<T> {
/**
* Decodes request, invokes methods, and re-encoded resulting return values.
*/
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
index b1aeb0c..31a948c 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
@@ -23,7 +23,7 @@
/**
* Represents one piece in a property reference sequence.
*/
-public class RequestProperty implements Iterable<RequestProperty> {
+class RequestProperty implements Iterable<RequestProperty> {
/**
* Merge two property chains.
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
index 9a404af..439f997 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
@@ -19,7 +19,7 @@
* Enforces security policy for operations and classes, as well as permitting
* request obfuscation.
*/
-public interface RequestSecurityProvider {
+interface RequestSecurityProvider {
/**
* Throws exception if argument is not accessible via remote requests.
diff --git a/user/src/com/google/gwt/requestfactory/server/SampleDataPopulator.java b/user/src/com/google/gwt/requestfactory/server/SampleDataPopulator.java
deleted file mode 100644
index 2ffd72e..0000000
--- a/user/src/com/google/gwt/requestfactory/server/SampleDataPopulator.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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;
-
-import com.google.gwt.requestfactory.client.DefaultRequestTransport;
-import com.google.gwt.requestfactory.shared.impl.Constants;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Class to populate the datastore with sample data in a JSON file.
- */
-public class SampleDataPopulator {
-
- public static void main(String args[]) {
- // TODO: cleanup argument processing and error reporting.
- if (args.length < 2) {
- printHelp();
- System.exit(-1);
- }
- try {
- if (!args[0].endsWith(DefaultRequestTransport.URL)) {
- System.err.println("Please check your URL string " + args[0]
- + ", it should end with " + DefaultRequestTransport.URL + ", exiting");
- System.exit(-1);
- }
- SampleDataPopulator populator = new SampleDataPopulator(args[0], args[1]);
- populator.populate();
- } catch (Exception ex) {
- ex.printStackTrace();
- printHelp();
- }
- }
-
- private static void printHelp() {
- StringBuffer sb = new StringBuffer();
- sb.append("\n");
- sb.append("Requires two arguments: the URL to post the JSON data and the path to the JSON data file.");
- System.err.println(sb.toString());
- }
-
- private final String url;
-
- private final String filePathName;
-
- SampleDataPopulator(String url, String filePathName) {
- this.url = url;
- this.filePathName = filePathName;
- }
-
- public void populate() throws JSONException, IOException {
- JSONObject jsonObject = readAsJsonObject(readFileAsString(filePathName));
- postJsonFile(jsonObject);
- }
-
- private void postJsonFile(JSONObject contentData) throws IOException,
- JSONException {
- HttpPost post = new HttpPost(url);
- JSONObject request = new JSONObject();
- request.put(Constants.OPERATION_TOKEN, "DOESNT_WORK");
- request.put(Constants.CONTENT_TOKEN, contentData);
- StringEntity reqEntity = new StringEntity(request.toString());
- post.setEntity(reqEntity);
- HttpClient client = new DefaultHttpClient();
- HttpResponse response = client.execute(post);
- HttpEntity resEntity = response.getEntity();
- if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
- System.out.println("SUCCESS: Put " + resEntity.getContentLength()
- + " records in the datastore!");
- return;
- }
- System.err.println("POST failed: Status line " + response.getStatusLine()
- + ", please check your URL");
- }
-
- private JSONObject readAsJsonObject(String string) throws JSONException {
- JSONObject jsonObject = new JSONObject(string);
- return jsonObject;
- }
-
- // ugly method, refactor later when cleaning up this class.
- private byte[] readFileAsBytes(String filePathName) {
- File file = new File(filePathName);
- FileInputStream fileInputStream = null;
- byte bytes[] = null;
- try {
- fileInputStream = new FileInputStream(file);
- int byteLength = (int) file.length();
- bytes = new byte[byteLength];
- int byteOffset = 0;
- while (byteOffset < byteLength) {
- int bytesReadCount = fileInputStream.read(bytes, byteOffset, byteLength
- - byteOffset);
- if (bytesReadCount == -1) {
- return null;
- }
- byteOffset += bytesReadCount;
- }
- } catch (IOException e) {
- // Ignored.
- } finally {
- try {
- if (fileInputStream != null) {
- fileInputStream.close();
- }
- } catch (IOException e) {
- // ignored
- }
- }
- return bytes;
- }
-
- private String readFileAsString(String filePathName) {
- byte bytes[] = readFileAsBytes(filePathName);
- if (bytes != null) {
- try {
- return new String(bytes, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Ignored.
- }
- return null;
- }
- return null;
- }
-
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
new file mode 100644
index 0000000..ebd6299
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import com.google.gwt.dev.asm.signature.SignatureVisitor;
+
+/**
+ * An empty implementation of SignatureVisitor, used by
+ * {@link RequestFactoryInterfaceValidator}. This is a copy of the dev package's
+ * EmptySignatureVisitor.
+ */
+class SignatureAdapter implements SignatureVisitor {
+
+ private static final SignatureAdapter ignore = new SignatureAdapter();
+
+ public SignatureVisitor visitArrayType() {
+ return ignore;
+ }
+
+ public void visitBaseType(char descriptor) {
+ }
+
+ public SignatureVisitor visitClassBound() {
+ return ignore;
+ }
+
+ public void visitClassType(String name) {
+ }
+
+ public void visitEnd() {
+ }
+
+ public SignatureVisitor visitExceptionType() {
+ return ignore;
+ }
+
+ public void visitFormalTypeParameter(String name) {
+ }
+
+ public void visitInnerClassType(String name) {
+ }
+
+ public SignatureVisitor visitInterface() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitInterfaceBound() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitParameterType() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitReturnType() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitSuperclass() {
+ return ignore;
+ }
+
+ public void visitTypeArgument() {
+ }
+
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return ignore;
+ }
+
+ public void visitTypeVariable(String name) {
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
index 7a8c747..4da1afe 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
@@ -21,12 +21,13 @@
import java.lang.annotation.Target;
/**
- * Annotation on Record classes specifying 'type'. 'type'
- * represents the server-side counterpart of the Record.
+ * Annotation on EntityProxy classes specifying the domain (server-side) object
+ * type.
+ *
+ * @see ProxyForName
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProxyFor {
-
Class<?> value();
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
new file mode 100644
index 0000000..fa3f5bd
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
@@ -0,0 +1,32 @@
+/*
+ * 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.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on EntityProxy classes specifying the domain (server-side) object
+ * type. This annotation can be used in place of {@link ProxyFor} if the domain
+ * object is not available to the GWT compiler or DevMode runtime.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ProxyForName {
+ String value();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Service.java b/user/src/com/google/gwt/requestfactory/shared/Service.java
index 59f2cf5..29d8829 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Service.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Service.java
@@ -23,10 +23,11 @@
/**
* Annotation on Request classes specifying the server side implementations that
* back them.
+ *
+ * @see ServiceName
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
-
Class<?> value();
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
new file mode 100644
index 0000000..beb72b6
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
@@ -0,0 +1,32 @@
+/*
+ * 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.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on Request classes specifying the server side implementations that
+ * back them.This annotation can be used in place of {@link Service} if the
+ * service type is not available to the GWT compiler or DevMode runtime.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ServiceName {
+ String value();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
index 69ca68b..7c2ff23 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
@@ -18,11 +18,6 @@
import java.util.Collection;
/**
- * <p> <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span> </p> Defines a property of a {@link com.google.gwt.requestfactory.shared.EntityProxy} that contains a
- * one-to-many set of related values.
- *
* @param <C> the type of the Container, must be List or Set
* @param <E> the type of the element the container contains
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
index 7a0e743..842d745 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
@@ -16,11 +16,6 @@
package com.google.gwt.requestfactory.shared.impl;
/**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
* Defines a property of a {@link com.google.gwt.requestfactory.shared.EntityProxy}.
*
* @param <V> the type of the property's value
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java b/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
index 06bd8ba..15e8b84 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
@@ -16,11 +16,6 @@
package com.google.gwt.requestfactory.shared.impl;
/**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
* Defines a property of an {@link EntityProxy}.
*
* @param <V> the type of the property's value
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
index 6431b51..7234528 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
@@ -25,9 +25,6 @@
import java.util.Set;
/**
- * <p> <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span> </p>
* Utility methods for querying, encoding, and decoding typed
* payload data.
*/
diff --git a/user/src/com/google/gwt/user/tools/WebAppCreator.java b/user/src/com/google/gwt/user/tools/WebAppCreator.java
index 8c5f373..c73c69c 100644
--- a/user/src/com/google/gwt/user/tools/WebAppCreator.java
+++ b/user/src/com/google/gwt/user/tools/WebAppCreator.java
@@ -371,6 +371,15 @@
replacements.put("@testFolder", testFolder);
replacements.put("@warFolder", warFolder);
+ // Add command to copy gwt-servlet-deps.jar into libs, unless this is a
+ // maven project. Maven projects should include libs as maven dependencies.
+ String copyServletDeps = "";
+ if (!maven) {
+ copyServletDeps = "<copy todir=\"" + warFolder + "/WEB-INF/lib\" "
+ + "file=\"${gwt.sdk}/gwt-servlet-deps.jar\" />";
+ }
+ replacements.put("@copyServletDeps", copyServletDeps);
+
// Collect the list of server libs to include on the eclipse classpath.
StringBuilder serverLibs = new StringBuilder();
if (libDir.exists()) {
diff --git a/user/src/com/google/gwt/user/tools/project.ant.xmlsrc b/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
index a1dd654..dacddf7 100644
--- a/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
+++ b/user/src/com/google/gwt/user/tools/project.ant.xmlsrc
@@ -17,6 +17,7 @@
<target name="libs" description="Copy libs to WEB-INF/lib">
<mkdir dir="@warFolder/WEB-INF/lib" />
<copy todir="@warFolder/WEB-INF/lib" file="${gwt.sdk}/gwt-servlet.jar" />
+ @copyServletDeps
<!-- Add any additional server libs that need to be copied -->
</target>
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
index a6ad74a..3aa7f1b 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -19,6 +19,7 @@
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.RequestFactoryInterfaceValidatorTest;
import com.google.gwt.requestfactory.server.RequestPropertyTest;
import junit.framework.Test;
@@ -34,6 +35,7 @@
suite.addTestSuite(SimpleEntityProxyIdTest.class);
suite.addTestSuite(JsonRequestProcessorTest.class);
suite.addTestSuite(ReflectionBasedOperationRegistryTest.class);
+ suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
suite.addTestSuite(RequestFactoryModelTest.class);
suite.addTestSuite(RequestPropertyTest.class);
return suite;
diff --git a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
index 42a821b..74d268a 100644
--- a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
+++ b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
@@ -26,8 +26,6 @@
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;
@@ -49,8 +47,9 @@
import java.util.SortedSet;
/**
- * Test case for {@link com.google.gwt.requestfactory.rebind.model.RequestFactoryModel}
- * that uses mock CompilationStates.
+ * Test case for
+ * {@link com.google.gwt.requestfactory.rebind.model.RequestFactoryModel} that
+ * uses mock CompilationStates.
*/
public class RequestFactoryModelTest extends TestCase {
@@ -64,8 +63,7 @@
public EmptyMockJavaResource(Class<?> clazz) {
super(clazz.getName());
- code.append("package ").append(clazz.getPackage().getName())
- .append(";\n");
+ code.append("package ").append(clazz.getPackage().getName()).append(";\n");
code.append("public interface ").append(clazz.getSimpleName());
int numParams = clazz.getTypeParameters().length;
@@ -103,15 +101,15 @@
@Override
protected CharSequence getContent() {
String resourceName = getTypeName().replace('.', '/') + ".java";
- InputStream stream = Thread.currentThread().getContextClassLoader()
- .getResourceAsStream(resourceName);
+ InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(
+ resourceName);
return Util.readStreamAsString(stream);
}
}
private static TreeLogger createCompileLogger() {
- PrintWriterTreeLogger logger = new PrintWriterTreeLogger(
- new PrintWriter(System.err, true));
+ PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+ System.err, true));
logger.setMaxDetail(TreeLogger.ERROR);
return logger;
}
@@ -125,7 +123,8 @@
}
public void testBadCollectionType() {
- testModelWithMethodDecl("Request<SortedSet<Integer>> badReturnType();",
+ testModelWithMethodDecl(
+ "Request<SortedSet<Integer>> badReturnType();",
"Requests that return collections may be declared with java.util.List or java.util.Set only");
}
@@ -139,55 +138,10 @@
"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");
+ "The t.TestProxy type does not have a @ProxyFor or @ProxyForName annotation");
}
public void testMissingService() {
@@ -196,13 +150,6 @@
"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,
@@ -250,8 +197,8 @@
}
});
- CompilationState state = CompilationStateBuilder
- .buildFrom(logger, javaResources);
+ CompilationState state = CompilationStateBuilder.buildFrom(logger,
+ javaResources);
UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
builder.setLowestLogLevel(TreeLogger.ERROR);
@@ -261,19 +208,14 @@
builder.expectError(RequestFactoryModel.poisonedMessage(), null);
UnitTestTreeLogger testLogger = builder.createLogger();
try {
- new RequestFactoryModel(testLogger,
- state.getTypeOracle().findType("t.TestRequestFactory"));
+ 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
@@ -290,7 +232,7 @@
return code;
}
}, new MockJavaResource("java.util.List") {
- // Tests a Driver interface that extends more than RFED
+ // Tests a Driver interface that extends more than RFED
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -300,7 +242,7 @@
return code;
}
}, new MockJavaResource("java.util.Set") {
- // Tests a Driver interface that extends more than RFED
+ // Tests a Driver interface that extends more than RFED
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -310,7 +252,7 @@
return code;
}
}, new MockJavaResource("java.util.SortedSet") {
- // Tests a Driver interface that extends more than RFED
+ // Tests a Driver interface that extends more than RFED
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -323,18 +265,18 @@
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),
+ 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),}));
+ new RealJavaResource(Request.class),
+ new RealJavaResource(Service.class),
+ new RealJavaResource(ProxyFor.class),
+ new EmptyMockJavaResource(RequestContext.class),}));
toReturn.addAll(Arrays.asList(JavaResourceBase.getStandardResources()));
return toReturn;
}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
new file mode 100644
index 0000000..0b1bad7
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+
+import com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+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.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
+
+import junit.framework.TestCase;
+
+import java.util.logging.Logger;
+
+/**
+ * JRE tests for {@link RequestFactoryInterfaceValidator}.
+ */
+public class RequestFactoryInterfaceValidatorTest extends TestCase {
+ static class Domain {
+ static int fooStatic(int a) {
+ return 0;
+ }
+
+ int foo(int a) {
+ return 0;
+ }
+ }
+
+ @ProxyFor(Domain.class)
+ interface DomainProxy extends EntityProxy {
+ }
+
+ interface DomainProxyMissingAnnotation extends EntityProxy {
+ }
+
+ static class DomainWithOverloads {
+ void foo() {
+ }
+
+ void foo(int a) {
+ }
+
+ String getId() {
+ return null;
+ }
+
+ int getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(DomainWithOverloads.class)
+ interface DomainWithOverloadsProxy extends EntityProxy {
+ void foo();
+ }
+
+ class Foo {
+ }
+
+ interface RequestContextMissingAnnotation extends RequestContext {
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedArity extends RequestContext {
+ InstanceRequest<DomainProxy, Integer> foo(int a, int b);
+
+ Request<Integer> fooStatic(int a, int b);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedParam extends RequestContext {
+ Request<Integer> foo(long a);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedReturn extends RequestContext {
+ Request<Long> foo(int a);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedStatic extends RequestContext {
+ Request<Integer> foo(int a);
+
+ InstanceRequest<DomainProxy, Integer> fooStatic(int a);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMissingMethod extends RequestContext {
+ Request<Integer> doesNotExist(int a);
+ }
+
+ RequestFactoryInterfaceValidator v;
+
+ public void testMismatchedArity() {
+ v.validateRequestContext(ServiceRequestMismatchedArity.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMismatchedParamType() {
+ v.validateRequestContext(ServiceRequestMismatchedParam.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMismatchedReturnType() {
+ v.validateRequestContext(ServiceRequestMismatchedReturn.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMismatchedStatic() {
+ v.validateRequestContext(ServiceRequestMismatchedStatic.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingDomainAnnotations() {
+ v.validateEntityProxy(DomainProxyMissingAnnotation.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingServiceAnnotations() {
+ v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingIdAndVersion() {
+ v.validateEntityProxy(DomainProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingMethod() {
+ v.validateRequestContext(ServiceRequestMissingMethod.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testOverloadedMethod() {
+ v.validateEntityProxy(DomainWithOverloadsProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ /**
+ * Perform a full test of the RequestFactory used for most tests.
+ */
+ public void testTestCodeFactories() {
+ v.validateRequestFactory(SimpleRequestFactory.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ Logger logger = Logger.getLogger("");
+ v = new RequestFactoryInterfaceValidator(logger, new ClassLoaderLoader(
+ Thread.currentThread().getContextClassLoader()));
+ }
+}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
index e08a18d..6c8c1ae 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
@@ -15,13 +15,11 @@
*/
package com.google.gwt.requestfactory.shared;
-import com.google.gwt.requestfactory.server.SimpleBar;
-
/**
* A simple entity used for testing. Has an int field and date field. Add other
* data types as their support gets built in.
*/
-@ProxyFor(SimpleBar.class)
+@ProxyForName("com.google.gwt.requestfactory.server.SimpleBar")
public interface SimpleBarProxy extends EntityProxy {
Boolean getFindFails();
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java
index 0a8e7f2..f0196ad 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleBarRequest.java
@@ -21,7 +21,7 @@
/**
* Do nothing test interface.
*/
-@Service(com.google.gwt.requestfactory.server.SimpleBar.class)
+@ServiceName("com.google.gwt.requestfactory.server.SimpleBar")
public interface SimpleBarRequest extends RequestContext {
Request<Long> countSimpleBar();