Add Locator API to allow arbitrary domain types to be used with RequestFactory.
Refactor ServiceLayer API to allow extension through decoration.
Miscellaneous javadoc cleanups.
Issue 5111.
Patch by: bobv
Review by: rchandia,rjrjr
Review at http://gwt-code-reviews.appspot.com/1130801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9266 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt21_22userApi.conf b/tools/api-checker/config/gwt21_22userApi.conf
index 8daae36..8735494 100644
--- a/tools/api-checker/config/gwt21_22userApi.conf
+++ b/tools/api-checker/config/gwt21_22userApi.conf
@@ -39,6 +39,7 @@
:com/google/gwt/resources/css/**\
:com/google/gwt/resources/ext/**\
:com/google/gwt/resources/rg/**\
+:com/google/gwt/requestfactory/client/impl/FindRequest.java\
:com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
:com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:com/google/gwt/rpc/client/impl/EscapeUtil.java\
diff --git a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
index 7fc9f00..c18d943 100644
--- a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
+++ b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
@@ -28,16 +28,17 @@
* Generates JVM-compatible implementations of AutoBeanFactory and AutoBean
* types.
* <p>
- * NB: This implementation is excessively dynamic, however, the inability to
- * create a TypeOracle fram a ClassLoader prevents re-using the existing model
- * code. If the model code could be reused, it would be straightforward to
- * simply generate implementations of the various interfaces.
- * <p>
* This implementation is written assuming that the AutoBeanFactory and
* associated declarations will validate if compiled and used with the
* AutoBeanFactoyModel.
*/
public class AutoBeanFactoryMagic {
+ /*
+ * NB: This implementation is excessively dynamic, however the inability to
+ * create a TypeOracle fram a ClassLoader prevents re-using the existing model
+ * code. If the model code could be reused, it would be straightforward to
+ * simply generate implementations of the various interfaces.
+ */
private static final AutoBeanFactory EMPTY = create(AutoBeanFactory.class);
/**
diff --git a/user/src/com/google/gwt/autobean/server/Configuration.java b/user/src/com/google/gwt/autobean/server/Configuration.java
index 31942e9..8e96c36 100644
--- a/user/src/com/google/gwt/autobean/server/Configuration.java
+++ b/user/src/com/google/gwt/autobean/server/Configuration.java
@@ -25,7 +25,9 @@
import java.util.Set;
/**
- * Used by {@link AutoBeanFactoryMagic#createBean()}.
+ * Used by {@link AutoBeanFactoryMagic#createBean(Class, Configuration)}. This
+ * type replicates the annotations that may be applied to an AutoBeanFactory
+ * declaration.
*/
public class Configuration {
/**
@@ -44,12 +46,29 @@
}
}
+ /**
+ * Equivalent to applying a
+ * {@link com.google.gwt.autobean.shared.AutoBeanFactory.Category Category}
+ * annotation to an AutoBeanFactory declaration.
+ *
+ * @param categories the category types that should be searched for static
+ * implementations of non-property methods
+ * @return the Builder
+ */
public Builder setCategories(Class<?>... categories) {
toReturn.categories = Collections.unmodifiableList(new ArrayList<Class<?>>(
Arrays.asList(categories)));
return this;
}
+ /**
+ * Equivalent to applying a
+ * {@link com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap NoWrap}
+ * annotation to an AutoBeanFactory declaration.
+ *
+ * @param noWrap the types that should be excluded from wrapping
+ * @return the Builder
+ */
public Builder setNoWrap(Class<?>... noWrap) {
toReturn.noWrap.addAll(Arrays.asList(noWrap));
return this;
diff --git a/user/src/com/google/gwt/requestfactory/server/FindService.java b/user/src/com/google/gwt/autobean/server/package-info.java
similarity index 60%
copy from user/src/com/google/gwt/requestfactory/server/FindService.java
copy to user/src/com/google/gwt/autobean/server/package-info.java
index d5b5353..88e1b04 100644
--- a/user/src/com/google/gwt/requestfactory/server/FindService.java
+++ b/user/src/com/google/gwt/autobean/server/package-info.java
@@ -13,20 +13,16 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.gwt.requestfactory.server;
/**
- * Server side service to support a generic find method.
+ * Contains JVM-compatible implementations of the AutoBean framework.
+ *
+ * @see <a
+ * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ * wiki page</a>
+ * @see com.google.gwt.autobean.shared.AutoBeanFactory
+ * @see com.google.gwt.autobean.server.AutoBeanFactoryMagic
*/
-public class FindService {
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.autobean.server;
- /**
- * For now, a simple implementation of find will work.
- *
- * @param entityInstance an entity instance
- * @return the passed-in entity instance
- */
- public static <T> T find(T entityInstance) {
- return entityInstance;
- }
-}
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBean.java b/user/src/com/google/gwt/autobean/shared/AutoBean.java
index 5ad9089..a51c22b 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBean.java
@@ -22,7 +22,8 @@
import java.lang.annotation.Target;
/**
- * A controller for an implementation of a bean interface.
+ * A controller for an implementation of a bean interface. Instances of
+ * AutoBeans are obtained from an {@link AutoBeanFactory}.
*
* @param <T> the type of interface that will be wrapped.
*/
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
index d5832e7..5f1a8f9 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
@@ -37,6 +37,10 @@
* AutoBean<ArbitraryInterface> wrapper(ArbitraryInterface delegate);
* }
* </pre>
+ *
+ * @see <a
+ * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ * wiki page</a>
*/
public interface AutoBeanFactory {
/**
@@ -78,8 +82,8 @@
}
/**
- * Methods annotated with this annotation will not have their return values
- * automatically wrapped by the factory.
+ * The types specified by this annotation will not be wrapped by an AutoBean
+ * when returned from an AutoBean-controlled method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
diff --git a/user/src/com/google/gwt/autobean/shared/package-info.java b/user/src/com/google/gwt/autobean/shared/package-info.java
new file mode 100644
index 0000000..5c13167
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * The AutoBean framework provides automatically-generated implementations of
+ * bean-like interfaces and a low-level serialization mechanism for those
+ * interfaces. AutoBeans can be used in both client and server code to improve
+ * code re-use.
+ *
+ * @see <a
+ * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ * wiki page</a>
+ * @see com.google.gwt.autobean.shared.AutoBeanFactory
+ * @see com.google.gwt.autobean.server.AutoBeanFactoryMagic
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.autobean.shared;
+
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
index 924b70e..068fa77 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
@@ -23,7 +23,7 @@
*/
public class DefaultExceptionHandler implements ExceptionHandler {
public ServerFailure createServerFailure(Throwable throwable) {
- return new ServerFailure("Server Error: " + throwable.getMessage(), null,
- null);
+ return new ServerFailure("Server Error: "
+ + (throwable == null ? null : throwable.getMessage()), null, null);
}
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
new file mode 100644
index 0000000..a2922e4
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
@@ -0,0 +1,165 @@
+/*
+ * 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.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.Locator;
+import com.google.gwt.requestfactory.shared.LocatorFor;
+import com.google.gwt.requestfactory.shared.LocatorForName;
+
+/**
+ * Adds support to the ServiceLayer chain for using {@link Locator} helper
+ * objects.
+ */
+final class LocatorServiceLayer extends ServiceLayerDecorator {
+
+ @Override
+ public <T> T createDomainObject(Class<T> clazz) {
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.createDomainObject(clazz);
+ }
+ return l.create(clazz);
+ }
+
+ @Override
+ public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+ Throwable ex;
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ }
+ return this.<T> die(ex,
+ "Could not instantiate Locator %s. It is default-instantiable?",
+ clazz.getCanonicalName());
+ }
+
+ @Override
+ public Object getId(Object domainObject) {
+ return doGetId(domainObject);
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ Locator<?, ?> l = getLocator(domainType);
+ if (l == null) {
+ return super.getIdType(domainType);
+ }
+ return l.getIdType();
+ }
+
+ @Override
+ public Object getVersion(Object domainObject) {
+ return doGetVersion(domainObject);
+ }
+
+ @Override
+ public boolean isLive(Object domainObject) {
+ return doIsLive(domainObject);
+ }
+
+ @Override
+ public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
+ return doLoadDomainObject(clazz, domainId);
+ }
+
+ @Override
+ public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+ // Find the matching BaseProxy
+ Class<?> proxyType = getTop().resolveClientType(domainType,
+ BaseProxy.class, false);
+ if (proxyType == null) {
+ return null;
+ }
+
+ // Check it for annotations
+ Class<? extends Locator<?, ?>> locatorType;
+ LocatorFor l = proxyType.getAnnotation(LocatorFor.class);
+ LocatorForName ln = proxyType.getAnnotation(LocatorForName.class);
+ if (l != null) {
+ locatorType = l.value();
+ } else if (ln != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) Class.forName(
+ ln.value(), false, domainType.getClassLoader()).asSubclass(
+ Locator.class);
+ locatorType = found;
+ } catch (ClassNotFoundException e) {
+ return die(e,
+ "Could not find the type specified in the @%s annotation %s",
+ LocatorForName.class.getCanonicalName(), ln.value());
+ }
+ } else {
+ // No locator annotation
+ locatorType = null;
+ }
+ return locatorType;
+ }
+
+ private <T> Object doGetId(T domainObject) {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.getId(domainObject);
+ }
+ return l.getId(domainObject);
+ }
+
+ private <T> Object doGetVersion(T domainObject) {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.getVersion(domainObject);
+ }
+ return l.getVersion(domainObject);
+ }
+
+ private <T> boolean doIsLive(T domainObject) {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.isLive(domainObject);
+ }
+ return l.isLive(domainObject);
+ }
+
+ private <T, I> T doLoadDomainObject(Class<T> clazz, Object domainId) {
+ @SuppressWarnings("unchecked")
+ Locator<T, I> l = (Locator<T, I>) getLocator(clazz);
+ if (l == null) {
+ return super.loadDomainObject(clazz, domainId);
+ }
+ I id = l.getIdType().cast(domainId);
+ return l.find(clazz, id);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T, I> Locator<T, I> getLocator(Class<T> domainType) {
+ Class<? extends Locator<?, ?>> locatorType = getTop().resolveLocator(
+ domainType);
+ if (locatorType == null) {
+ return null;
+ }
+ return (Locator<T, I>) getTop().createLocator(locatorType);
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index b17acb4..2c2da00 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -17,19 +17,9 @@
import com.google.gwt.autobean.server.impl.TypeUtils;
import com.google.gwt.autobean.shared.ValueCodex;
-import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.shared.BaseProxy;
-import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
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.Service;
-import com.google.gwt.requestfactory.shared.ServiceName;
-import com.google.gwt.requestfactory.shared.ValueProxy;
-import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -37,7 +27,6 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collections;
-import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -49,22 +38,17 @@
import javax.validation.ValidatorFactory;
/**
- * A reflection-based implementation of ServiceLayer.
+ * Implements all methods that interact with domain objects.
*/
-public class ReflectiveServiceLayer implements ServiceLayer {
+final class ReflectiveServiceLayer extends ServiceLayerDecorator {
+ /*
+ * NB: All calls that ReflectiveServiceLayer makes to public APIs inherited
+ * from ServiceLayer should be made to use the instance returned from
+ * getTop().
+ */
private static final Validator jsr303Validator;
-
- private static final Logger log = Logger.getLogger(ReflectiveServiceLayer.class.getName());
-
- /**
- * All instances of the service layer that are loaded by the same classloader
- * can use a shared validator. The use of the validator should be
- * synchronized, since it is stateful.
- */
- private static final RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
- log, new RequestFactoryInterfaceValidator.ClassLoaderLoader(
- ReflectiveServiceLayer.class.getClassLoader()));
+ private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
static {
Validator found;
@@ -83,18 +67,19 @@
+ (name.length() >= 1 ? name.substring(1) : "");
}
- public Object createDomainObject(Class<?> clazz) {
+ @Override
+ public <T> T createDomainObject(Class<T> clazz) {
Throwable ex;
try {
- Constructor<?> c = clazz.getConstructor();
+ Constructor<T> c = clazz.getConstructor();
c.setAccessible(true);
return c.newInstance();
} catch (InstantiationException e) {
- return report("Could not create a new instance of the requested type");
+ return this.<T> report("Could not create a new instance of the requested type");
} catch (NoSuchMethodException e) {
- return report("The requested type is not default-instantiable");
+ return this.<T> report("The requested type is not default-instantiable");
} catch (InvocationTargetException e) {
- return report(e);
+ return this.<T> report(e);
} catch (IllegalAccessException e) {
ex = e;
} catch (SecurityException e) {
@@ -102,60 +87,22 @@
} catch (IllegalArgumentException e) {
ex = e;
}
- return die(ex, "Could not create a new instance of domain type %s",
+ return this.<T> die(ex,
+ "Could not create a new instance of domain type %s",
clazz.getCanonicalName());
}
- public Class<?> getClientType(Class<?> domainClass, Class<?> clientClass) {
- String name;
- synchronized (validator) {
- name = validator.getEntityProxyTypeName(domainClass.getName(),
- clientClass.getName());
- }
- if (name != null) {
- return forName(name).asSubclass(BaseProxy.class);
- }
- if (List.class.isAssignableFrom(domainClass)) {
- return List.class;
- }
- if (Set.class.isAssignableFrom(domainClass)) {
- return Set.class;
- }
- if (TypeUtils.isValueType(domainClass)) {
- return domainClass;
- }
- return die(null, "The domain type %s cannot be sent to the client",
- domainClass.getCanonicalName());
- }
-
- public Class<?> getDomainClass(Class<?> clazz) {
- if (List.class.equals(clazz)) {
- return List.class;
- } else if (Set.class.equals(clazz)) {
- return Set.class;
- } else if (BaseProxy.class.isAssignableFrom(clazz)) {
- ProxyFor pf = clazz.getAnnotation(ProxyFor.class);
- if (pf != null) {
- return pf.value();
- }
- ProxyForName pfn = clazz.getAnnotation(ProxyForName.class);
- if (pfn != null) {
- Class<?> toReturn = forName(pfn.value());
- return toReturn;
- }
- }
- return die(null, "Could not resolve a domain type for client type %s",
- clazz.getCanonicalName());
- }
-
+ @Override
public Object getId(Object domainObject) {
- return getProperty(domainObject, "id");
+ return getTop().getProperty(domainObject, "id");
}
+ @Override
public Class<?> getIdType(Class<?> domainType) {
return getFind(domainType).getParameterTypes()[0];
}
+ @Override
public Object getProperty(Object domainObject, String property) {
Throwable toReport;
try {
@@ -178,6 +125,7 @@
return die(toReport, "Could not retrieve property %s", property);
}
+ @Override
public Type getRequestReturnType(Method contextMethod) {
Class<?> returnClass = contextMethod.getReturnType();
if (InstanceRequest.class.isAssignableFrom(returnClass)) {
@@ -190,19 +138,17 @@
contextMethod.getGenericReturnType());
return param;
} else {
- throw new IllegalArgumentException("Unknown RequestContext return type "
- + returnClass.getCanonicalName());
+ return die(null, "Unknown RequestContext return type %s",
+ returnClass.getCanonicalName());
}
}
- public String getTypeToken(Class<?> clazz) {
- return clazz.getName();
- }
-
+ @Override
public Object getVersion(Object domainObject) {
- return getProperty(domainObject, "version");
+ return getTop().getProperty(domainObject, "version");
}
+ @Override
public Object invoke(Method domainMethod, Object... args) {
Throwable ex;
try {
@@ -227,101 +173,21 @@
/**
* This implementation attempts to re-load the object from the backing store.
*/
- public boolean isLive(EntitySource source, Object domainObject) {
- Object id = getId(domainObject);
- return invoke(getFind(domainObject.getClass()), id) != null;
+ @Override
+ public boolean isLive(Object domainObject) {
+ Object id = getTop().getId(domainObject);
+ return getTop().invoke(getFind(domainObject.getClass()), id) != null;
}
- public Object loadDomainObject(EntitySource source, Class<?> clazz, Object id) {
+ @Override
+ public <T> T loadDomainObject(Class<T> clazz, Object id) {
if (id == null) {
die(null, "Cannot invoke find method with a null id");
}
- return invoke(getFind(clazz), id);
+ return clazz.cast(getTop().invoke(getFind(clazz), id));
}
- public Class<? extends BaseProxy> resolveClass(String typeToken) {
- Class<?> found = forName(typeToken);
- if (!EntityProxy.class.isAssignableFrom(found)
- && !ValueProxy.class.isAssignableFrom(found)) {
- die(null, "The requested type %s is not assignable to %s or %s",
- typeToken, EntityProxy.class.getCanonicalName(),
- ValueProxy.class.getCanonicalName());
- }
- synchronized (validator) {
- validator.antidote();
- validator.validateProxy(found.getName());
- if (validator.isPoisoned()) {
- die(null, "The type %s did not pass RequestFactory validation",
- found.getCanonicalName());
- }
- }
- return found.asSubclass(BaseProxy.class);
- }
-
- public Method resolveDomainMethod(Method requestContextMethod) {
- Class<?> enclosing = requestContextMethod.getDeclaringClass();
-
- Class<?> searchIn = null;
- Service s = enclosing.getAnnotation(Service.class);
- if (s != null) {
- searchIn = s.value();
- }
- ServiceName sn = enclosing.getAnnotation(ServiceName.class);
- if (sn != null) {
- searchIn = forName(sn.value());
- }
- if (searchIn == null) {
- die(null, "The %s type %s did not specify a service type",
- RequestContext.class.getSimpleName(), enclosing.getCanonicalName());
- }
-
- Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
- Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
- for (int i = 0, j = domainArgs.length; i < j; i++) {
- if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
- domainArgs[i] = getDomainClass(parameterTypes[i].asSubclass(BaseProxy.class));
- } else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
- domainArgs[i] = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
- EntityProxyId.class,
- requestContextMethod.getGenericParameterTypes()[i]));
- } else {
- domainArgs[i] = parameterTypes[i];
- }
- }
-
- Throwable ex;
- try {
- return searchIn.getMethod(requestContextMethod.getName(), domainArgs);
- } catch (SecurityException e) {
- ex = e;
- } catch (NoSuchMethodException e) {
- return report("Could not locate domain method %s",
- requestContextMethod.getName());
- }
- return die(ex, "Could not get domain method %s in type %s",
- requestContextMethod.getName(), searchIn.getCanonicalName());
- }
-
- public Method resolveRequestContextMethod(String requestContextClass,
- String methodName) {
- synchronized (validator) {
- validator.antidote();
- validator.validateRequestContext(requestContextClass);
- if (validator.isPoisoned()) {
- die(null, "The RequestContext type %s did not pass validation",
- requestContextClass);
- }
- }
- Class<?> searchIn = forName(requestContextClass);
- for (Method method : searchIn.getMethods()) {
- if (method.getName().equals(methodName)) {
- return method;
- }
- }
- return report("Could not locate %s method %s::%s",
- RequestContext.class.getSimpleName(), requestContextClass, methodName);
- }
-
+ @Override
public void setProperty(Object domainObject, String property,
Class<?> expectedType, Object value) {
Method setter;
@@ -348,6 +214,7 @@
domainObject.getClass().getCanonicalName());
}
+ @Override
public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
if (jsr303Validator != null) {
return jsr303Validator.validate(domainObject);
@@ -355,32 +222,6 @@
return Collections.emptySet();
}
- /**
- * Throw a fatal error up into the top-level processing code. This method
- * should be used to provide diagnostic information that will help the
- * end-developer track down problems when that data would expose
- * implementation details of the server to the client.
- */
- private <T> T die(Throwable e, String message, Object... args)
- throws UnexpectedException {
- String msg = String.format(message, args);
- log.log(Level.SEVERE, msg, e);
- throw new UnexpectedException(msg, e);
- }
-
- /**
- * Call {@link Class#forName(String)} and report any errors through
- * {@link #die()}.
- */
- private Class<?> forName(String name) {
- try {
- return Class.forName(name, false,
- Thread.currentThread().getContextClassLoader());
- } catch (ClassNotFoundException e) {
- return die(e, "Could not locate class %s", name);
- }
- }
-
private Method getFind(Class<?> clazz) {
if (clazz == null) {
return die(null, "Could not find static method with a single"
@@ -414,26 +255,7 @@
return true;
}
- return BaseProxy.class.isAssignableFrom(getClientType(domainClass,
- BaseProxy.class));
- }
-
- /**
- * Report an exception thrown by code that is under the control of the
- * end-developer.
- */
- private <T> T report(InvocationTargetException userGeneratedException)
- throws ReportableException {
- throw new ReportableException(userGeneratedException.getCause());
- }
-
- /**
- * Return a message to the client. This method should not include any data
- * that was not sent to the server by the client to avoid leaking data.
- *
- * @see #die()
- */
- private <T> T report(String msg, Object... args) throws ReportableException {
- throw new ReportableException(String.format(msg, args));
+ return BaseProxy.class.isAssignableFrom(getTop().resolveClientType(
+ domainClass, BaseProxy.class, true));
}
}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index e65603a..95f26a8 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -31,6 +31,8 @@
import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.LocatorFor;
+import com.google.gwt.requestfactory.shared.LocatorForName;
import com.google.gwt.requestfactory.shared.ProxyFor;
import com.google.gwt.requestfactory.shared.ProxyForName;
import com.google.gwt.requestfactory.shared.Request;
@@ -69,7 +71,7 @@
* public void testRequestFactory() {
* Logger logger = Logger.getLogger("");
* RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
- * logger, new ClassLoaderLoader(Thread.currentThread().getContextClassLoader()));
+ * logger, new ClassLoaderLoader(MyRequestContext.class.getClassLoader()));
* v.validateRequestContext(MyRequestContext.class.getName());
* assertFalse(v.isPoisoned());
* }
@@ -140,6 +142,7 @@
private class DomainMapper extends EmptyVisitor {
private final ErrorContext logger;
private String domainInternalName;
+ private String locatorInternalName;
public DomainMapper(ErrorContext logger) {
this.logger = logger;
@@ -150,6 +153,10 @@
return domainInternalName;
}
+ public String getLocatorInternalName() {
+ return locatorInternalName;
+ }
+
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
@@ -160,24 +167,30 @@
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ final boolean foundLocator = desc.equals(Type.getDescriptor(LocatorFor.class));
+ final boolean foundLocatorName = desc.equals(Type.getDescriptor(LocatorForName.class));
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) {
+ if (foundLocator || foundProxy || foundService) {
return new EmptyVisitor() {
-
@Override
public void visit(String name, Object value) {
if ("value".equals(name)) {
- domainInternalName = ((Type) value).getInternalName();
+ String found = ((Type) value).getInternalName();
+ if (foundLocator) {
+ locatorInternalName = found;
+ } else {
+ domainInternalName = found;
+ }
}
}
};
}
- if (foundProxyName || foundServiceName) {
+ if (foundLocatorName || foundProxyName || foundServiceName) {
return new EmptyVisitor() {
@Override
public void visit(String name, Object value) {
@@ -199,7 +212,11 @@
desc.setCharAt(idx, '$');
}
- domainInternalName = desc.toString();
+ if (foundLocatorName) {
+ locatorInternalName = desc.toString();
+ } else {
+ domainInternalName = desc.toString();
+ }
logger.spam(domainInternalName);
}
}
@@ -494,7 +511,6 @@
* A set of binary type names that are known to be bad.
*/
private final Set<String> badTypes = new HashSet<String>();
-
/**
* The type {@link BaseProxy}.
*/
@@ -504,11 +520,15 @@
*/
private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
/**
+ * Maps client types (e.g. FooProxy) to their locator types (e.g. FooLocator).
+ */
+ private final Map<Type, Type> clientToLocatorMap = new HashMap<Type, Type>();
+
+ /**
* Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
* FooBProxy).
*/
private final Map<Type, List<Type>> domainToClientType = new HashMap<Type, List<Type>>();
-
/**
* The type {@link EntityProxy}.
*/
@@ -549,6 +569,7 @@
* The type {@link ValueProxy}.
*/
private final Type valueProxyIntf = Type.getType(ValueProxy.class);
+
/**
* A set to prevent re-validation of a type.
*/
@@ -787,7 +808,7 @@
/*
* If nothing was found look for proxyable supertypes the domain object can
- * be upcast to.
+ * be upcast to.
*/
if (found == null || found.isEmpty()) {
List<Type> types = getSupertypes(parentLogger, key);
@@ -802,13 +823,13 @@
}
}
}
-
+
if (found == null || found.isEmpty()) {
return null;
}
-
+
Type typeToReturn = null;
-
+
// Common case
if (found.size() == 1) {
typeToReturn = found.get(0);
@@ -822,7 +843,7 @@
}
}
}
-
+
return typeToReturn == null ? null : typeToReturn.getClassName();
}
@@ -1141,6 +1162,10 @@
} else {
domainType = Type.getObjectType(pv.getDomainInternalName());
}
+ if (pv.getLocatorInternalName() != null) {
+ Type locatorType = Type.getObjectType(pv.getLocatorInternalName());
+ clientToLocatorMap.put(clientType, locatorType);
+ }
}
addToDomainMap(logger, domainType, clientType);
maybeCheckProxyType(logger, clientType);
@@ -1170,7 +1195,7 @@
*/
private Type getReturnType(ErrorContext logger, RFMethod method) {
logger = logger.setMethod(method);
- final String[] returnType = { objectType.getInternalName() };
+ final String[] returnType = {objectType.getInternalName()};
String signature = method.getSignature();
final int expectedCount;
@@ -1218,9 +1243,9 @@
if (toReturn != null) {
return toReturn;
}
-
+
logger = logger.setType(type);
-
+
toReturn = new SupertypeCollector(logger).exec(type);
supertypes.put(type, Collections.unmodifiableList(toReturn));
return toReturn;
@@ -1370,9 +1395,12 @@
return;
}
- // Check for getId() and getVersion() in domain
+ // Check for getId() and getVersion() in domain if no locator is specified
if (requireId) {
- checkIdAndVersion(logger, domainType);
+ Type locatorType = clientToLocatorMap.get(proxyType);
+ if (locatorType == null) {
+ checkIdAndVersion(logger, domainType);
+ }
}
// Collect all methods in the client proxy type
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index f977c5c..c0e1418 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -96,9 +96,14 @@
* {@link ExceptionHandler}.
*
* @param exceptionHandler an {@link ExceptionHandler} instance
+ * @param serviceDecorators an array of ServiceLayerDecorators that change how
+ * the RequestFactory request processor interact with the domain
+ * objects
*/
- public RequestFactoryServlet(ExceptionHandler exceptionHandler) {
- processor = new SimpleRequestProcessor(new ReflectiveServiceLayer());
+ public RequestFactoryServlet(ExceptionHandler exceptionHandler,
+ ServiceLayerDecorator... serviceDecorators) {
+ processor = new SimpleRequestProcessor(
+ ServiceLayer.create(serviceDecorators));
processor.setExceptionHandler(exceptionHandler);
}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
index 5cb6c6c..3682f1e 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestState.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -22,15 +22,14 @@
import com.google.gwt.autobean.shared.ValueCodex;
import com.google.gwt.autobean.shared.impl.StringQuoter;
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
-import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.EntityCodex;
import com.google.gwt.requestfactory.shared.impl.IdFactory;
import com.google.gwt.requestfactory.shared.impl.MessageFactoryHolder;
import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
-import com.google.gwt.requestfactory.shared.messages.EntityCodex;
import com.google.gwt.requestfactory.shared.messages.IdMessage;
import com.google.gwt.requestfactory.shared.messages.IdMessage.Strength;
@@ -76,8 +75,8 @@
}
@Override
- protected String getTypeToken(Class<?> clazz) {
- return service.getTypeToken(clazz);
+ protected String getTypeToken(Class<? extends BaseProxy> clazz) {
+ return service.resolveTypeToken(clazz);
}
};
domainObjectsToId = new IdentityHashMap<Object, SimpleProxyId<?>>();
@@ -96,7 +95,7 @@
public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(IdMessage idMessage) {
SimpleProxyId<Q> id;
- if (idMessage.getSyntheticId() > 0) {
+ if (Strength.SYNTHETIC.equals(idMessage.getStrength())) {
@SuppressWarnings("unchecked")
Class<Q> clazz = (Class<Q>) service.resolveClass(idMessage.getTypeToken());
id = idFactory.allocateSyntheticId(clazz, idMessage.getSyntheticId());
@@ -147,7 +146,7 @@
public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
IdMessage ref = bean.as();
- ref.setTypeToken(service.getTypeToken(stableId.getProxyClass()));
+ ref.setTypeToken(service.resolveTypeToken(stableId.getProxyClass()));
if (stableId.isSynthetic()) {
ref.setStrength(Strength.SYNTHETIC);
ref.setSyntheticId(stableId.getSyntheticId());
@@ -155,7 +154,6 @@
ref.setStrength(Strength.EPHEMERAL);
ref.setClientId(stableId.getClientId());
} else {
- ref.setStrength(Strength.PERSISTED);
ref.setServerId(SimpleRequestProcessor.toBase64(stableId.getServerId()));
}
return AutoBeanCodex.encode(bean);
@@ -210,10 +208,14 @@
AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
if (toReturn == null) {
// Resolve the domain object
- Class<?> domainClass = service.getDomainClass(id.getProxyClass());
+ Class<?> domainClass = service.resolveDomainClass(id.getProxyClass());
Object domain;
if (id.isEphemeral() || id.isSynthetic()) {
domain = service.createDomainObject(domainClass);
+ if (domain == null) {
+ throw new UnexpectedException("Could not create instance of "
+ + domainClass.getCanonicalName(), null);
+ }
} else {
Splittable address = StringQuoter.split(id.getServerId());
Class<?> param = service.getIdType(domainClass);
@@ -224,7 +226,7 @@
domainParam = new SimpleRequestProcessor(service).decodeOobMessage(
param, address).get(0);
}
- domain = service.loadDomainObject(this, domainClass, domainParam);
+ domain = service.loadDomainObject(domainClass, domainParam);
}
domainObjectsToId.put(domain, id);
toReturn = createEntityProxyBean(id, domain);
diff --git a/user/src/com/google/gwt/requestfactory/server/Resolver.java b/user/src/com/google/gwt/requestfactory/server/Resolver.java
index 24a7fed..5762f28 100644
--- a/user/src/com/google/gwt/requestfactory/server/Resolver.java
+++ b/user/src/com/google/gwt/requestfactory/server/Resolver.java
@@ -21,7 +21,6 @@
import com.google.gwt.autobean.shared.AutoBeanVisitor;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
-import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
@@ -399,8 +398,8 @@
return assignableTo.cast(previous);
}
- Class<?> returnClass = service.getClientType(domainValue.getClass(),
- assignableTo);
+ Class<?> returnClass = service.resolveClientType(domainValue.getClass(),
+ assignableTo, true);
if (anyType) {
assignableTo = returnClass;
diff --git a/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
new file mode 100644
index 0000000..aafbe52
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
@@ -0,0 +1,202 @@
+/*
+ * 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.autobean.server.impl.TypeUtils;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+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.RequestContext;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Implements all of the resolution methods in ServiceLayer.
+ */
+final class ResolverServiceLayer extends ServiceLayerDecorator {
+
+ private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+ /**
+ * All instances of the service layer that are loaded by the same classloader
+ * can use a shared validator. The use of the validator should be
+ * synchronized, since it is stateful.
+ */
+ private static final RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
+ log, new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+ ServiceLayer.class.getClassLoader()));
+
+ @Override
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
+ Class<?> found = forName(typeToken);
+ if (!EntityProxy.class.isAssignableFrom(found)
+ && !ValueProxy.class.isAssignableFrom(found)) {
+ die(null, "The requested type %s is not assignable to %s or %s",
+ typeToken, EntityProxy.class.getCanonicalName(),
+ ValueProxy.class.getCanonicalName());
+ }
+ synchronized (validator) {
+ validator.antidote();
+ validator.validateProxy(found.getName());
+ if (validator.isPoisoned()) {
+ die(null, "The type %s did not pass RequestFactory validation",
+ found.getCanonicalName());
+ }
+ }
+ return found.asSubclass(BaseProxy.class);
+ }
+
+ @Override
+ public <T> Class<? extends T> resolveClientType(Class<?> domainClass,
+ Class<T> clientClass, boolean required) {
+ String name;
+ synchronized (validator) {
+ name = validator.getEntityProxyTypeName(domainClass.getName(),
+ clientClass.getName());
+ }
+ if (name != null) {
+ return forName(name).asSubclass(clientClass);
+ }
+ if (List.class.isAssignableFrom(domainClass)) {
+ return List.class.asSubclass(clientClass);
+ }
+ if (Set.class.isAssignableFrom(domainClass)) {
+ return Set.class.asSubclass(clientClass);
+ }
+ if (TypeUtils.isValueType(domainClass)) {
+ return domainClass.asSubclass(clientClass);
+ }
+ if (required) {
+ die(null, "The domain type %s cannot be sent to the client",
+ domainClass.getCanonicalName());
+ }
+ return null;
+ }
+
+ @Override
+ public Class<?> resolveDomainClass(Class<?> clazz) {
+ if (List.class.equals(clazz)) {
+ return List.class;
+ } else if (Set.class.equals(clazz)) {
+ return Set.class;
+ } else if (BaseProxy.class.isAssignableFrom(clazz)) {
+ ProxyFor pf = clazz.getAnnotation(ProxyFor.class);
+ if (pf != null) {
+ return pf.value();
+ }
+ ProxyForName pfn = clazz.getAnnotation(ProxyForName.class);
+ if (pfn != null) {
+ Class<?> toReturn = forName(pfn.value());
+ return toReturn;
+ }
+ }
+ return die(null, "Could not resolve a domain type for client type %s",
+ clazz.getCanonicalName());
+ }
+
+ @Override
+ public Method resolveDomainMethod(Method requestContextMethod) {
+ Class<?> enclosing = requestContextMethod.getDeclaringClass();
+
+ Class<?> searchIn = null;
+ Service s = enclosing.getAnnotation(Service.class);
+ if (s != null) {
+ searchIn = s.value();
+ }
+ ServiceName sn = enclosing.getAnnotation(ServiceName.class);
+ if (sn != null) {
+ searchIn = forName(sn.value());
+ }
+ if (searchIn == null) {
+ die(null, "The %s type %s did not specify a service type",
+ RequestContext.class.getSimpleName(), enclosing.getCanonicalName());
+ }
+
+ Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
+ Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
+ for (int i = 0, j = domainArgs.length; i < j; i++) {
+ if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
+ domainArgs[i] = getTop().resolveDomainClass(
+ parameterTypes[i].asSubclass(BaseProxy.class));
+ } else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
+ domainArgs[i] = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+ EntityProxyId.class,
+ requestContextMethod.getGenericParameterTypes()[i]));
+ } else {
+ domainArgs[i] = parameterTypes[i];
+ }
+ }
+
+ Throwable ex;
+ try {
+ return searchIn.getMethod(requestContextMethod.getName(), domainArgs);
+ } catch (SecurityException e) {
+ ex = e;
+ } catch (NoSuchMethodException e) {
+ return report("Could not locate domain method %s",
+ requestContextMethod.getName());
+ }
+ return die(ex, "Could not get domain method %s in type %s",
+ requestContextMethod.getName(), searchIn.getCanonicalName());
+ }
+
+ @Override
+ public Method resolveRequestContextMethod(String requestContextClass,
+ String methodName) {
+ synchronized (validator) {
+ validator.antidote();
+ validator.validateRequestContext(requestContextClass);
+ if (validator.isPoisoned()) {
+ die(null, "The RequestContext type %s did not pass validation",
+ requestContextClass);
+ }
+ }
+ Class<?> searchIn = forName(requestContextClass);
+ for (Method method : searchIn.getMethods()) {
+ if (method.getName().equals(methodName)) {
+ return method;
+ }
+ }
+ return report("Could not locate %s method %s::%s",
+ RequestContext.class.getSimpleName(), requestContextClass, methodName);
+ }
+
+ @Override
+ public String resolveTypeToken(Class<? extends BaseProxy> clazz) {
+ return clazz.getName();
+ }
+
+ /**
+ * Call {@link Class#forName(String)} and report any errors through
+ * {@link #die()}.
+ */
+ private Class<?> forName(String name) {
+ try {
+ return Class.forName(name, false,
+ Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ return die(e, "Could not locate class %s", name);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
new file mode 100644
index 0000000..9a0ba62
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
@@ -0,0 +1,292 @@
+/*
+ * 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.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.Locator;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * The ServiceLayer mediates all interactions between the
+ * {@link SimpleRequestProcessor} and the domain environment. The core service
+ * logic can be decorated by extending an {@link ServiceLayerDecorator}.
+ * <p>
+ * This API is subject to change in future releases.
+ */
+public abstract class ServiceLayer {
+ /*
+ * NB: This type cannot be directly extended by the user since it has a
+ * package-protected constructor. This means that any API-compatibility work
+ * that needs to happen can be done in ServiceLayerDecorator in order to keep
+ * this interface as clean as possible.
+ */
+
+ /**
+ * Create a RequestFactory ServiceLayer that is optionally modified by the
+ * given decorators.
+ *
+ * @param decorators the decorators that will modify the behavior of the core
+ * service layer implementation
+ * @return a ServiceLayer instance
+ */
+ public static ServiceLayer create(ServiceLayerDecorator... decorators) {
+ List<ServiceLayerDecorator> list = new ArrayList<ServiceLayerDecorator>();
+ // Always hit the cache first
+ ServiceLayerCache cache = new ServiceLayerCache();
+ list.add(cache);
+ // The the user-provided decorators
+ if (decorators != null) {
+ list.addAll(Arrays.asList(decorators));
+ }
+ // Support for Locator objects
+ list.add(new LocatorServiceLayer());
+ // Interact with domain objects
+ list.add(new ReflectiveServiceLayer());
+ // Locate domain objects
+ list.add(new ResolverServiceLayer());
+
+ // Make the last layer point to the cache
+ list.get(list.size() - 1).top = cache;
+
+ // Point each entry at the next
+ for (int i = list.size() - 2; i >= 0; i--) {
+ ServiceLayerDecorator layer = list.get(i);
+ layer.next = list.get(i + 1);
+ layer.top = cache;
+ }
+
+ return cache;
+ }
+
+ /**
+ * A pointer to the top-most ServiceLayer instance.
+ */
+ ServiceLayer top;
+
+ /**
+ * Not generally-extensible.
+ */
+ ServiceLayer() {
+ }
+
+ /**
+ * Create an instance of the requested domain type.
+ *
+ * @param <T> the requested domain type
+ * @param clazz the requested domain type
+ * @return an instance of the requested domain type
+ */
+ public abstract <T> T createDomainObject(Class<T> clazz);
+
+ /**
+ * Create an instance of the requested {@link Locator} type.
+ *
+ * @param <T> the requested Locator type
+ * @param clazz the requested Locator type
+ * @return an instance of the requested Locator type
+ */
+ public abstract <T extends Locator<?, ?>> T createLocator(Class<T> clazz);
+
+ /**
+ * Return the persistent id for a domain object. May return {@code null} to
+ * indicate that the domain object has not been persisted. The value returned
+ * from this method must be a simple type (e.g. Integer, String) or a domain
+ * type for which a mapping to an EntityProxy or Value proxy exists.
+ * <p>
+ * The values returned from this method may be passed to
+ * {@link #loadDomainObject(Class, Object)} in the future.
+ *
+ * @param domainObject a domain object
+ * @return the persistent id of the domain object or {@code null} if the
+ * object is not persistent
+ */
+ public abstract Object getId(Object domainObject);
+
+ /**
+ * Returns the type of object the domain type's {@code findFoo()} or
+ * {@link com.google.gwt.requestfactory.shared.Locator#getId(Object)
+ * Locator.getId()} expects to receive.
+ *
+ * @param domainType a domain entity type
+ * @return the type of the persistent id value used to represent the domain
+ * type
+ */
+ public abstract Class<?> getIdType(Class<?> domainType);
+
+ /**
+ * Retrieve the named property from the domain object.
+ *
+ * @param domainObject the domain object being examined
+ * @param property the property name
+ * @return the value of the property
+ */
+ public abstract Object getProperty(Object domainObject, String property);
+
+ /**
+ * Compute the return type for a method declared in a RequestContext by
+ * analyzing the generic method declaration.
+ */
+ public abstract Type getRequestReturnType(Method contextMethod);
+
+ /**
+ * May return {@code null} to indicate that the domain object has not been
+ * persisted. The value returned from this method must be a simple type (e.g.
+ * Integer, String) or a domain type for which a mapping to an EntityProxy or
+ * Value proxy exists.
+ *
+ * @param domainObject a domain object
+ * @return the version of the domain object or {@code null} if the object is
+ * not persistent
+ */
+ public abstract Object getVersion(Object domainObject);
+
+ /**
+ * Invoke a domain service method. The underlying eventually calls
+ * {@link Method#invoke(Object, Object...)}.
+ *
+ * @param domainMethod the method to invoke
+ * @param args the arguments to pass to the method
+ * @return the value returned from the method invocation
+ */
+ public abstract Object invoke(Method domainMethod, Object... args);
+
+ /**
+ * Returns {@code true} if the given domain object is still live (i.e. not
+ * deleted) in the backing store.
+ *
+ * @param domainObject a domain entity
+ * @return {@code true} if {@code domainObject} could be retrieved at a later
+ * point in time
+ */
+ public abstract boolean isLive(Object domainObject);
+
+ /**
+ * Load an object from the backing store. This method may return {@code null}
+ * to indicate that the requested object is no longer available.
+ *
+ * @param <T> the type of object to load
+ * @param clazz the type of object to load
+ * @param domainId an id previously returned from {@link #getId(Object)}
+ * @return the requested object or {@code null} if it is irretrievable
+ */
+ public abstract <T> T loadDomainObject(Class<T> clazz, Object domainId);
+
+ /**
+ * Given a type token previously returned from
+ * {@link #resolveTypeToken(Class)}, return the Class literal associated with
+ * the token.
+ *
+ * @param typeToken a string token
+ * @return the type represented by the token
+ */
+ public abstract Class<? extends BaseProxy> resolveClass(String typeToken);
+
+ /**
+ * Determine the type used by the client code to represent a given domain
+ * type. If multiple proxy types have been mapped to the same domain type, the
+ * {@code clientType} parameter is used to ensure assignability.
+ *
+ * @param domainClass the server-side type to be transported to the client
+ * @param clientType the type to which the returned type must be assignable
+ * @param required if {@code true} and no mapping is available, throw an
+ * {@link UnexpectedException}, othrewise the method will return
+ * {@code null}
+ * @return a class that represents {@code domainClass} on the client which is
+ * assignable to {@code clientType}
+ */
+ public abstract <T> Class<? extends T> resolveClientType(
+ Class<?> domainClass, Class<T> clientType, boolean required);
+
+ /**
+ * Determine the domain (server-side) type that the given client type is
+ * mapped to.
+ *
+ * @param clientType a client-side type
+ * @return the domain type that {@code clientType} represents
+ */
+ public abstract Class<?> resolveDomainClass(Class<?> clientType);
+
+ /**
+ * Return the domain service method associated with a RequestContext method
+ * declaration. The {@code requestContextMethod} will have been previously
+ * resolved by {@link #resolveRequestContextMethod(String, String)}.
+ *
+ * @param requestContextMethod a RequestContext method declaration.
+ * @return the domain service method that should be invoked
+ */
+ public abstract Method resolveDomainMethod(Method requestContextMethod);
+
+ /**
+ * Return the type of {@link Locator} that should be used to access the given
+ * domain type.
+ *
+ * @param domainType a domain (server-side) type
+ * @return the type of Locator to use, or {@code null} if the type conforms to
+ * the RequestFactory entity protocol
+ */
+ public abstract Class<? extends Locator<?, ?>> resolveLocator(
+ Class<?> domainType);
+
+ /**
+ * Find a RequestContext method declaration by name.
+ *
+ * @param requestContextClass the fully-qualified binary name of the
+ * RequestContext
+ * @param methodName the name of the service method declared within the
+ * RequestContext
+ * @return the method declaration, or {@code null} if the method does not
+ * exist
+ */
+ public abstract Method resolveRequestContextMethod(
+ String requestContextClass, String methodName);
+
+ /**
+ * Return a string used to represent the given type in the wire protocol.
+ *
+ * @param proxyType a client-side EntityProxy or ValueProxy type
+ * @return the type token used to represent the proxy type
+ */
+ public abstract String resolveTypeToken(Class<? extends BaseProxy> proxyType);
+
+ /**
+ * Sets a property on a domain object.
+ *
+ * @param domainObject the domain object to operate on
+ * @param property the name of the property to set
+ * @param expectedType the type of the property
+ * @param value the new value
+ */
+ public abstract void setProperty(Object domainObject, String property,
+ Class<?> expectedType, Object value);
+
+ /**
+ * Invoke a JSR 303 validator on the given domain object. If no validator is
+ * available, this method is a no-op.
+ *
+ * @param <T> the type of data being validated
+ * @param domainObject the domain objcet to validate
+ * @return the violations associated with the domain object
+ */
+ public abstract <T> Set<ConstraintViolation<T>> validate(T domainObject);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
new file mode 100644
index 0000000..887d49c
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
@@ -0,0 +1,192 @@
+/*
+ * 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.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.Locator;
+import com.google.gwt.rpc.server.Pair;
+
+import java.lang.ref.SoftReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A cache for idempotent methods in {@link ServiceLayer}. The caching is
+ * separate from {@link ReflectiveServiceLayer} so that the cache can be applied
+ * to any decorators injected by the user.
+ */
+class ServiceLayerCache extends ServiceLayerDecorator {
+
+ /**
+ * ConcurrentHashMaps don't allow null keys or values, but sometimes we want
+ * to cache a null value.
+ */
+ private static final Object NULL_MARKER = new Object();
+
+ private static SoftReference<Map<Method, Map<Object, Object>>> methodCache;
+
+ private static final Method createLocator;
+ private static final Method getIdType;
+ private static final Method getRequestReturnType;
+ private static final Method resolveClass;
+ private static final Method resolveClientType;
+ private static final Method resolveDomainClass;
+ private static final Method resolveDomainMethod;
+ private static final Method resolveLocator;
+ private static final Method resolveRequestContextMethod;
+ private static final Method resolveTypeToken;
+
+ static {
+ createLocator = getMethod("createLocator", Class.class);
+ getIdType = getMethod("getIdType", Class.class);
+ getRequestReturnType = getMethod("getRequestReturnType", Method.class);
+ resolveClass = getMethod("resolveClass", String.class);
+ resolveClientType = getMethod("resolveClientType", Class.class,
+ Class.class, boolean.class);
+ resolveDomainClass = getMethod("resolveDomainClass", Class.class);
+ resolveDomainMethod = getMethod("resolveDomainMethod", Method.class);
+ resolveLocator = getMethod("resolveLocator", Class.class);
+ resolveRequestContextMethod = getMethod("resolveRequestContextMethod",
+ String.class, String.class);
+ resolveTypeToken = getMethod("resolveTypeToken", Class.class);
+ }
+
+ private static Map<Method, Map<Object, Object>> getCache() {
+ Map<Method, Map<Object, Object>> toReturn = methodCache == null ? null
+ : methodCache.get();
+ if (toReturn == null) {
+ toReturn = new ConcurrentHashMap<Method, Map<Object, Object>>();
+ methodCache = new SoftReference<Map<Method, Map<Object, Object>>>(
+ toReturn);
+ }
+ return toReturn;
+ }
+
+ private static Method getMethod(String name, Class<?>... argTypes) {
+ try {
+ return ServiceLayer.class.getMethod(name, argTypes);
+ } catch (SecurityException e) {
+ throw new RuntimeException("Could not set up ServiceLayerCache Methods",
+ e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Could not set up ServiceLayerCache Methods",
+ e);
+ }
+ }
+
+ private final Map<Method, Map<Object, Object>> methodMap = getCache();
+
+ @Override
+ public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+ return getOrCache(createLocator, clazz, clazz, clazz);
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ return getOrCache(getIdType, domainType, Class.class, domainType);
+ }
+
+ @Override
+ public Type getRequestReturnType(Method contextMethod) {
+ return getOrCache(getRequestReturnType, contextMethod, Type.class,
+ contextMethod);
+ }
+
+ @Override
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
+ Class<?> found = getOrCache(resolveClass, typeToken, Class.class, typeToken);
+ return found.asSubclass(BaseProxy.class);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> Class<? extends T> resolveClientType(Class<?> domainClass,
+ Class<T> clientType, boolean required) {
+ return getOrCache(resolveClientType, new Pair<Class<?>, Class<?>>(
+ domainClass, clientType), Class.class, domainClass, clientType,
+ required);
+ }
+
+ @Override
+ public Class<?> resolveDomainClass(Class<?> clazz) {
+ return getOrCache(resolveDomainClass, clazz, Class.class, clazz);
+ }
+
+ @Override
+ public Method resolveDomainMethod(Method requestContextMethod) {
+ return getOrCache(resolveDomainMethod, requestContextMethod, Method.class,
+ requestContextMethod);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+ return getOrCache(resolveLocator, domainType, Class.class, domainType);
+ }
+
+ @Override
+ public Method resolveRequestContextMethod(String requestContextClass,
+ String methodName) {
+ return getOrCache(resolveRequestContextMethod, new Pair<String, String>(
+ requestContextClass, methodName), Method.class, requestContextClass,
+ methodName);
+ }
+
+ @Override
+ public String resolveTypeToken(Class<? extends BaseProxy> domainClass) {
+ return getOrCache(resolveTypeToken, domainClass, String.class, domainClass);
+ }
+
+ private <K, T> T getOrCache(Method method, K key, Class<T> valueType,
+ Object... args) {
+ Map<Object, Object> map = methodMap.get(method);
+ if (map == null) {
+ map = new ConcurrentHashMap<Object, Object>();
+ methodMap.put(method, map);
+ }
+ Object raw = map.get(key);
+ if (raw == NULL_MARKER) {
+ return null;
+ }
+ T toReturn = valueType.cast(raw);
+ if (toReturn == null) {
+ Throwable ex = null;
+ try {
+ toReturn = valueType.cast(method.invoke(getNext(), args));
+ map.put(key, toReturn == null ? NULL_MARKER : toReturn);
+ } catch (InvocationTargetException e) {
+ // The next layer threw an exception
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ // Re-throw RuntimeExceptions, which likely originate from die()
+ throw ((RuntimeException) cause);
+ }
+ die(cause, "Unexpected checked exception");
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ }
+ if (ex != null) {
+ die(ex, "Bad method invocation");
+ }
+ }
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
new file mode 100644
index 0000000..5acd2bd
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
@@ -0,0 +1,214 @@
+/*
+ * 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.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.Locator;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Users that intend to alter how RequestFactory interacts with the domain
+ * environment can extend this type and provide it to
+ * {@link ServiceLayer#create(ServiceLayerDecorator...)}. The methods defined in
+ * this type will automatically delegate to the next decorator or the root
+ * service object after being processed by{@code create()}.
+ */
+public class ServiceLayerDecorator extends ServiceLayer {
+ private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+ /**
+ * A pointer to the next deepest layer.
+ */
+ ServiceLayer next;
+
+ @Override
+ public <T> T createDomainObject(Class<T> clazz) {
+ return getNext().createDomainObject(clazz);
+ }
+
+ @Override
+ public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+ return getNext().createLocator(clazz);
+ }
+
+ @Override
+ public Object getId(Object domainObject) {
+ return getNext().getId(domainObject);
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ return getNext().getIdType(domainType);
+ }
+
+ @Override
+ public Object getProperty(Object domainObject, String property) {
+ return getNext().getProperty(domainObject, property);
+ }
+
+ @Override
+ public Type getRequestReturnType(Method contextMethod) {
+ return getNext().getRequestReturnType(contextMethod);
+ }
+
+ @Override
+ public Object getVersion(Object domainObject) {
+ return getNext().getVersion(domainObject);
+ }
+
+ @Override
+ public Object invoke(Method domainMethod, Object... args) {
+ return getNext().invoke(domainMethod, args);
+ }
+
+ @Override
+ public boolean isLive(Object domainObject) {
+ return getNext().isLive(domainObject);
+ }
+
+ @Override
+ public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
+ return getNext().loadDomainObject(clazz, domainId);
+ }
+
+ @Override
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
+ return getNext().resolveClass(typeToken);
+ }
+
+ @Override
+ public <T> Class<? extends T> resolveClientType(Class<?> domainClass,
+ Class<T> clientType, boolean required) {
+ return getNext().resolveClientType(domainClass, clientType, required);
+ }
+
+ @Override
+ public Class<?> resolveDomainClass(Class<?> clazz) {
+ return getNext().resolveDomainClass(clazz);
+ }
+
+ @Override
+ public Method resolveDomainMethod(Method requestContextMethod) {
+ return getNext().resolveDomainMethod(requestContextMethod);
+ }
+
+ @Override
+ public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+ return getNext().resolveLocator(domainType);
+ }
+
+ @Override
+ public Method resolveRequestContextMethod(String requestContextClass,
+ String methodName) {
+ return getNext().resolveRequestContextMethod(requestContextClass,
+ methodName);
+ }
+
+ @Override
+ public String resolveTypeToken(Class<? extends BaseProxy> proxyType) {
+ return getNext().resolveTypeToken(proxyType);
+ }
+
+ @Override
+ public void setProperty(Object domainObject, String property,
+ Class<?> expectedType, Object value) {
+ getNext().setProperty(domainObject, property, expectedType, value);
+ }
+
+ @Override
+ public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
+ return getNext().validate(domainObject);
+ }
+
+ /**
+ * Throw a fatal error up into the top-level processing code. This method
+ * should be used to provide diagnostic information that will help the
+ * end-developer track down problems when that data would expose
+ * implementation details of the server to the client.
+ *
+ * @param e a throwable with more data, may be {@code null}
+ * @param message a printf-style format string
+ * @param args arguments for the message
+ * @throws UnexpectedException this method never returns normally
+ * @see #report(String, Object...)
+ */
+ protected final <T> T die(Throwable e, String message, Object... args)
+ throws UnexpectedException {
+ String msg = String.format(message, args);
+ log.log(Level.SEVERE, msg, e);
+ throw new UnexpectedException(msg, e);
+ }
+
+ /**
+ * Returns the top-most service layer. General-purpose ServiceLayer decorators
+ * should use the instance provided by {@code getTop()} when calling public
+ * methods on the ServiceLayer API to allow higher-level decorators to
+ * override behaviors built into lower-level decorators.
+ *
+ * @return the ServiceLayer returned by
+ * {@link #create(ServiceLayerDecorator...)}
+ */
+ protected ServiceLayer getTop() {
+ return top;
+ }
+
+ /**
+ * Report an exception thrown by code that is under the control of the
+ * end-developer.
+ *
+ * @param an {@link InvocationTargetException} thrown by an invocation of
+ * user-provided code
+ * @throws ReportableException this method never returns normally
+ */
+ protected final <T> T report(InvocationTargetException userGeneratedException)
+ throws ReportableException {
+ throw new ReportableException(userGeneratedException.getCause());
+ }
+
+ /**
+ * Return a message to the client. This method should not include any data
+ * that was not sent to the server by the client to avoid leaking data.
+ *
+ * @param msg a printf-style format string
+ * @param args arguments for the message
+ * @throws ReportableException this method never returns normally
+ * @see #die(Throwable, String, Object...)
+ */
+ protected final <T> T report(String msg, Object... args)
+ throws ReportableException {
+ throw new ReportableException(String.format(msg, args));
+ }
+
+ /**
+ * Retrieves the next service layer. Used only by the server-package code and
+ * accessed by used code via {@code super.doSomething()}.
+ */
+ final ServiceLayer getNext() {
+ if (next == null) {
+ // Unexpected, all methods should be implemented by some layer
+ throw new UnsupportedOperationException();
+ }
+ return next;
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index 03c830a..f54e62e 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -32,11 +32,11 @@
import com.google.gwt.requestfactory.shared.WriteOperation;
import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.EntityCodex;
import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
-import com.google.gwt.requestfactory.shared.messages.EntityCodex;
-import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
+import com.google.gwt.requestfactory.shared.messages.IdMessage.Strength;
import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
import com.google.gwt.requestfactory.shared.messages.MessageFactory;
import com.google.gwt.requestfactory.shared.messages.OperationMessage;
@@ -67,70 +67,6 @@
*/
public class SimpleRequestProcessor {
/**
- * Abstracts all reflection operations from the request processor.
- */
- public interface ServiceLayer {
- Object createDomainObject(Class<?> clazz);
-
- /**
- * The way to introduce polymorphism.
- */
- Class<?> getClientType(Class<?> domainClass, Class<?> clientType);
-
- Class<?> getDomainClass(Class<?> clazz);
-
- /**
- * May return {@code null} to indicate that the domain object has not been
- * persisted.
- */
- Object getId(Object domainObject);
-
- /**
- * Returns the type of object the domain type's {@code findFoo()} expects to
- * receive.
- */
- Class<?> getIdType(Class<?> domainType);
-
- Object getProperty(Object domainObject, String property);
-
- /**
- * Compute the return type for a method declared in a RequestContext by
- * analyzing the generic method declaration.
- */
- Type getRequestReturnType(Method contextMethod);
-
- String getTypeToken(Class<?> domainClass);
-
- /**
- * May return {@code null} to indicate that the domain object has not been
- * persisted.
- */
- Object getVersion(Object domainObject);
-
- Object invoke(Method domainMethod, Object... args);
-
- /**
- * Returns {@code true} if the given domain object is still live (i.e. not
- * deleted) in the backing store.
- */
- boolean isLive(EntitySource source, Object domainObject);
-
- Object loadDomainObject(EntitySource source, Class<?> clazz, Object domainId);
-
- Class<? extends BaseProxy> resolveClass(String typeToken);
-
- Method resolveDomainMethod(Method requestContextMethod);
-
- Method resolveRequestContextMethod(String requestContextClass,
- String methodName);
-
- void setProperty(Object domainObject, String property,
- Class<?> expectedType, Object value);
-
- <T> Set<ConstraintViolation<T>> validate(T domainObject);
- }
-
- /**
* This parameterization is so long, it improves readability to have a
* specific type.
*/
@@ -175,6 +111,12 @@
this.service = serviceLayer;
}
+ /**
+ * Process a payload sent by a RequestFactory client.
+ *
+ * @param payload the payload sent by the client
+ * @return a payload to return to the client
+ */
public String process(String payload) {
RequestMessage req = AutoBeanCodex.decode(FACTORY, RequestMessage.class,
payload).as();
@@ -209,8 +151,8 @@
if (domainValue == null) {
clientValue = null;
} else {
- Class<?> clientType = service.getClientType(domainValue.getClass(),
- BaseProxy.class);
+ Class<?> clientType = service.resolveClientType(domainValue.getClass(),
+ BaseProxy.class, true);
clientValue = state.getResolver().resolveClientValue(domainValue,
clientType, Collections.<String> emptySet());
}
@@ -236,7 +178,8 @@
* Decode an out-of-band message.
*/
<T> List<T> decodeOobMessage(Class<T> domainClass, Splittable payload) {
- Class<?> proxyType = service.getClientType(domainClass, BaseProxy.class);
+ Class<?> proxyType = service.resolveClientType(domainClass,
+ BaseProxy.class, true);
RequestState state = new RequestState(service);
RequestMessage message = AutoBeanCodex.decode(FACTORY,
RequestMessage.class, payload).as();
@@ -318,7 +261,7 @@
if (id.isEphemeral() || id.isSynthetic() || domainObject == null) {
// If the object isn't persistent, there's no reason to send an update
writeOperation = null;
- } else if (!service.isLive(returnState, domainObject)) {
+ } else if (!service.isLive(domainObject)) {
writeOperation = WriteOperation.DELETE;
} else if (id.wasEphemeral()) {
writeOperation = WriteOperation.PERSIST;
@@ -381,8 +324,14 @@
op.setServerId(toBase64(id.getServerId()));
}
- op.setSyntheticId(id.getSyntheticId());
- op.setTypeToken(service.getTypeToken(id.getProxyClass()));
+ if (id.isSynthetic()) {
+ op.setStrength(Strength.SYNTHETIC);
+ op.setSyntheticId(id.getSyntheticId());
+ } else if (id.isEphemeral()) {
+ op.setStrength(Strength.EPHEMERAL);
+ }
+
+ op.setTypeToken(service.resolveTypeToken(id.getProxyClass()));
if (version != null) {
op.setVersion(toBase64(version.getPayload()));
}
@@ -459,7 +408,15 @@
String[] operation = invocation.getOperation().split("::");
Method contextMethod = service.resolveRequestContextMethod(
operation[0], operation[1]);
+ if (contextMethod == null) {
+ throw new UnexpectedException("Cannot resolve operation "
+ + invocation.getOperation(), null);
+ }
Method domainMethod = service.resolveDomainMethod(contextMethod);
+ if (domainMethod == null) {
+ throw new UnexpectedException("Cannot resolve domain method "
+ + invocation.getOperation(), null);
+ }
// Invoke it
List<Object> args = decodeInvocationArguments(state, invocation,
@@ -515,7 +472,7 @@
Object resolved = state.getResolver().resolveDomainValue(
newValue, false);
service.setProperty(domain, propertyName,
- service.getDomainClass(ctx.getType()), resolved);
+ service.resolveDomainClass(ctx.getType()), resolved);
}
return false;
}
@@ -560,11 +517,11 @@
message.setPath(error.getPropertyPath().toString());
if (id.isEphemeral()) {
message.setClientId(id.getClientId());
- }
- if (id.getServerId() != null) {
+ message.setStrength(Strength.EPHEMERAL);
+ } else {
message.setServerId(toBase64(id.getServerId()));
}
- message.setTypeToken(service.getTypeToken(id.getProxyClass()));
+ message.setTypeToken(service.resolveTypeToken(id.getProxyClass()));
errorMessages.add(message);
}
}
diff --git a/user/src/com/google/gwt/requestfactory/server/FindService.java b/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
similarity index 94%
rename from user/src/com/google/gwt/requestfactory/server/FindService.java
rename to user/src/com/google/gwt/requestfactory/server/impl/FindService.java
index d5b5353..8f467e1 100644
--- a/user/src/com/google/gwt/requestfactory/server/FindService.java
+++ b/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.gwt.requestfactory.server;
+package com.google.gwt.requestfactory.server.impl;
/**
* Server side service to support a generic find method.
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
index 74247af..af61538 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
@@ -38,7 +38,7 @@
import java.lang.reflect.Proxy;
/**
- * An JRE-compatible implementation of RequestFactory.
+ * A JRE-compatible implementation of RequestFactory.
*/
class InProcessRequestFactory extends AbstractRequestFactory {
@Category(value = {
@@ -105,7 +105,7 @@
}
@Override
- protected String getTypeToken(Class<?> clazz) {
+ protected String getTypeToken(Class<? extends BaseProxy> clazz) {
return isEntityType(clazz) || isValueType(clazz) ? clazz.getName() : null;
}
}
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
index fa32efb..a10adb2 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
@@ -22,6 +22,20 @@
* A RequesTransports that calls a {@link SimpleRequestProcessor}. This test can
* be used for end-to-end tests of RequestFactory service methods in non-GWT
* test suites.
+ *
+ * <pre>
+ * ServiceLayer serviceLayer = ServiceLayer.create();
+ * SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
+ * EventBus eventBus = new SimpleEventBus();
+ * MyRequestFactory f = RequestFactoryMagic.create(MyRequestFactory.class);
+ * f.initialize(eventBus, new InProcessRequestTransport(processor));
+ * </pre>
+ *
+ * @see RequestFactoryMagic
+ * @see com.google.gwt.requestfactory.server.ServiceLayer#create(com.google.gwt.requestfactory.server.ServiceLayerDecorator...)
+ * ServiceLayer.create()
+ * @see com.google.gwt.event.shared.SimpleEventBus SimpleEventBus
+ * @see SimpleRequestProcessor
*/
public class InProcessRequestTransport implements RequestTransport {
private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
index 0b84c27..3d1286d 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
@@ -34,7 +34,6 @@
*
* @param <T> the RequestFactory type
* @param requestFactory the RequestFactory type
- * @param processor
* @return an instance of the RequestFactory type
* @see InProcessRequestTransport
*/
@@ -42,7 +41,7 @@
RequestFactoryHandler handler = new InProcessRequestFactory().new RequestFactoryHandler();
return requestFactory.cast(Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
- new Class<?>[]{requestFactory}, handler));
+ new Class<?>[] {requestFactory}, handler));
}
private RequestFactoryMagic() {
diff --git a/user/src/com/google/gwt/requestfactory/shared/Locator.java b/user/src/com/google/gwt/requestfactory/shared/Locator.java
new file mode 100644
index 0000000..a88032b
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/Locator.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+/**
+ * A Locator allows entity types that do not conform to the RequestFactory
+ * entity protocol to be used. Instead of attempting to use a {@code findFoo()},
+ * {@code getId()}, and {@code getVersion()} declared in the domain entity type,
+ * an instance of a Locator will be created to provide implementations of these
+ * methods.
+ * <p>
+ * Locator subtypes must be default instantiable (i.e. public static types with
+ * a no-arg constructor). Instances of Locators may be retained and reused by
+ * the RequestFactory service layer.
+ *
+ * @param <T> the type of domain object the Locator will operate on
+ * @param <I> the type of object the Locator expects to use as an id for the
+ * domain object
+ * @see LocatorFor
+ */
+public abstract class Locator<T, I> {
+ /**
+ * Create a new instance of the requested type.
+ *
+ * @param clazz the type of object to create
+ * @return the new instance of the domain type
+ */
+ public abstract T create(Class<? extends T> clazz);
+
+ /**
+ * Retrieve an object. May return {@code null} to indicate that the requested
+ * object could not be found.
+ *
+ * @param clazz the type of object to retrieve
+ * @param id an id previously returned from {@link #getId(Object)}
+ * @return the requested object or {@code null} if it could not be found
+ */
+ public abstract T find(Class<? extends T> clazz, I id);
+
+ /**
+ * Returns the {@code T} type.
+ */
+ public abstract Class<T> getDomainType();
+
+ /**
+ * Returns a domain object to be used as the id for the given object. This
+ * method may return {@code null} if the object has not been persisted or
+ * should be treated as irretrievable.
+ *
+ * @param domainObject the object to obtain an id for
+ * @return the object's id or {@code null}
+ */
+ public abstract I getId(T domainObject);
+
+ /**
+ * Returns the {@code I} type.
+ */
+ public abstract Class<I> getIdType();
+
+ /**
+ * Returns a domain object to be used as the version for the given object.
+ * This method may return {@code null} if the object has not been persisted or
+ * should be treated as irretrievable.
+ *
+ * @param domainObject the object to obtain an id for
+ * @return the object's version or {@code null}
+ */
+ public abstract Object getVersion(T domainObject);
+
+ /**
+ * Returns a value indicating if the domain object should no longer be
+ * considered accessible. This method might return false if the record
+ * underlying the domain object had been deleted as a side-effect of
+ * processing a request.
+ * <p>
+ * The default implementation of this method uses {@link #getId(Object)} and
+ * {@link #find(Class, Object)} to determine if an object can be retrieved.
+ */
+ public boolean isLive(T domainObject) {
+ // Can't us Class.asSubclass() in web-mode code
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ return find(clazz, getId(domainObject)) != null;
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/LocatorFor.java b/user/src/com/google/gwt/requestfactory/shared/LocatorFor.java
new file mode 100644
index 0000000..2c237fd
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/LocatorFor.java
@@ -0,0 +1,35 @@
+/*
+ * 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 BaseProxy classes specifying the domain (server-side) object
+ * {@link Locator}. Specifying an explicit {@code Locator} removes the
+ * requirement to have a {@code findFoo()}, {@code getId()}, and
+ * {@code getVersion()} method in the domain object type.
+ *
+ * @see LocatorForName
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface LocatorFor {
+ Class<? extends Locator<?, ?>> value();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/LocatorForName.java b/user/src/com/google/gwt/requestfactory/shared/LocatorForName.java
new file mode 100644
index 0000000..f537cf4
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/LocatorForName.java
@@ -0,0 +1,35 @@
+/*
+ * 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 BaseProxy classes specifying the domain (server-side) object
+ * {@link Locator}. This annotation can be used in place of {@link LocatorFor}
+ * if the domain Locator is not available to the GWT compiler or DevMode
+ * runtime.
+ *
+ * @see LocatorFor
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface LocatorForName {
+ String value();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 48d1233..8e5778c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -39,6 +39,8 @@
* effect, treating it as an instance of the supertype. Returning abstract
* supertypes of value types is not supported (e.g. Object, Enum, Number).
* </p>
+ *
+ * @see com.google.gwt.requestfactory.server.testing.InProcessRequestTransport
*/
public interface RequestFactory {
/**
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
index f8b7ff1..aceb3a1 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
@@ -23,7 +23,6 @@
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.ServerFailure;
import com.google.gwt.requestfactory.shared.Violation;
-import com.google.gwt.requestfactory.shared.messages.EntityCodex;
import java.util.Arrays;
import java.util.Collections;
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 8d90add..43c766e 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -36,7 +36,6 @@
import com.google.gwt.requestfactory.shared.Violation;
import com.google.gwt.requestfactory.shared.WriteOperation;
import com.google.gwt.requestfactory.shared.impl.posers.DatePoser;
-import com.google.gwt.requestfactory.shared.messages.EntityCodex;
import com.google.gwt.requestfactory.shared.messages.IdMessage;
import com.google.gwt.requestfactory.shared.messages.IdMessage.Strength;
import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
@@ -246,8 +245,6 @@
} else if (stableId.isEphemeral()) {
ref.setStrength(Strength.EPHEMERAL);
ref.setClientId(stableId.getClientId());
- } else {
- ref.setStrength(Strength.PERSISTED);
}
return AutoBeanCodex.encode(bean);
}
@@ -521,7 +518,7 @@
* Resolves an IdMessage into an SimpleProxyId.
*/
private SimpleProxyId<BaseProxy> getId(IdMessage op) {
- if (op.getSyntheticId() > 0) {
+ if (Strength.SYNTHETIC.equals(op.getStrength())) {
return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
}
return requestFactory.getId(op.getTypeToken(), op.getServerId(),
@@ -590,6 +587,7 @@
operation.setOperation(WriteOperation.PERSIST);
// The ephemeral id is passed to the server
operation.setClientId(stableId.getClientId());
+ operation.setStrength(Strength.EPHEMERAL);
} else {
parent = currentView.getTag(PARENT_OBJECT);
// Requests involving existing objects use the persisted id
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
similarity index 95%
rename from user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
rename to user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
index 82de771..4304885 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.gwt.requestfactory.shared.messages;
+package com.google.gwt.requestfactory.shared.impl;
import com.google.gwt.autobean.shared.AutoBean;
import com.google.gwt.autobean.shared.AutoBeanUtils;
@@ -23,9 +23,6 @@
import com.google.gwt.autobean.shared.impl.StringQuoter;
import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
-import com.google.gwt.requestfactory.shared.impl.Poser;
-import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
index b314775..7888fb1 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
@@ -16,7 +16,7 @@
package com.google.gwt.requestfactory.shared.impl;
-import com.google.gwt.requestfactory.server.FindService;
+import com.google.gwt.requestfactory.server.impl.FindService;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.Request;
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
index eaae080..0c16cae 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -206,7 +206,7 @@
protected abstract <P extends BaseProxy> Class<P> getTypeFromToken(
String typeToken);
- protected abstract String getTypeToken(Class<?> clazz);
+ protected abstract String getTypeToken(Class<? extends BaseProxy> clazz);
private <P> Class<P> checkTypeToken(String token) {
@SuppressWarnings("unchecked")
diff --git a/user/src/com/google/gwt/rpc/server/Pair.java b/user/src/com/google/gwt/rpc/server/Pair.java
index c533a4e..082cab2 100644
--- a/user/src/com/google/gwt/rpc/server/Pair.java
+++ b/user/src/com/google/gwt/rpc/server/Pair.java
@@ -25,11 +25,19 @@
private final A a;
private final B b;
- Pair(A a, B b) {
+ public Pair(A a, B b) {
this.a = a;
this.b = b;
}
+ public boolean equals(Object o) {
+ if (!(o instanceof Pair)) {
+ return false;
+ }
+ Pair<?, ?> other = (Pair<?, ?>) o;
+ return a.equals(other.a) && b.equals(other.b);
+ }
+
public A getA() {
return a;
}
@@ -37,4 +45,8 @@
public B getB() {
return b;
}
+
+ public int hashCode() {
+ return a.hashCode() * 13 + b.hashCode() * 7;
+ }
}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
index 342aaa3..c158123 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -18,6 +18,7 @@
import com.google.gwt.requestfactory.rebind.model.RequestFactoryModelTest;
import com.google.gwt.requestfactory.server.ComplexKeysJreTest;
import com.google.gwt.requestfactory.server.FindServiceJreTest;
+import com.google.gwt.requestfactory.server.LocatorJreTest;
import com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest;
import com.google.gwt.requestfactory.server.RequestFactoryJreTest;
import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyIdTest;
@@ -34,6 +35,7 @@
"requestfactory package tests that require the JRE");
suite.addTestSuite(ComplexKeysJreTest.class);
suite.addTestSuite(FindServiceJreTest.class);
+ suite.addTestSuite(LocatorJreTest.class);
suite.addTestSuite(RequestFactoryJreTest.class);
suite.addTestSuite(SimpleEntityProxyIdTest.class);
suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
index 9eff4c3..cbb5558 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
@@ -22,6 +22,7 @@
import com.google.gwt.requestfactory.client.RequestFactoryTest;
import com.google.gwt.requestfactory.client.ui.EditorTest;
import com.google.gwt.requestfactory.shared.ComplexKeysTest;
+import com.google.gwt.requestfactory.shared.LocatorTest;
import junit.framework.Test;
@@ -35,6 +36,7 @@
suite.addTestSuite(ComplexKeysTest.class);
suite.addTestSuite(EditorTest.class);
suite.addTestSuite(FindServiceTest.class);
+ suite.addTestSuite(LocatorTest.class);
suite.addTestSuite(RequestFactoryTest.class);
suite.addTestSuite(RequestFactoryExceptionHandlerTest.class);
suite.addTestSuite(RequestFactoryPolymorphicTest.class);
diff --git a/user/src/com/google/gwt/requestfactory/server/FindService.java b/user/test/com/google/gwt/requestfactory/server/LocatorJreTest.java
similarity index 66%
copy from user/src/com/google/gwt/requestfactory/server/FindService.java
copy to user/test/com/google/gwt/requestfactory/server/LocatorJreTest.java
index d5b5353..fdaa741 100644
--- a/user/src/com/google/gwt/requestfactory/server/FindService.java
+++ b/user/test/com/google/gwt/requestfactory/server/LocatorJreTest.java
@@ -15,18 +15,19 @@
*/
package com.google.gwt.requestfactory.server;
-/**
- * Server side service to support a generic find method.
- */
-public class FindService {
+import com.google.gwt.requestfactory.shared.LocatorTest;
- /**
- * For now, a simple implementation of find will work.
- *
- * @param entityInstance an entity instance
- * @return the passed-in entity instance
- */
- public static <T> T find(T entityInstance) {
- return entityInstance;
+/**
+ * A JRE version of {@link LocatorTest}.
+ */
+public class LocatorJreTest extends LocatorTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
}
}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index d8eca03..eb87261 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -18,6 +18,8 @@
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.Locator;
+import com.google.gwt.requestfactory.shared.LocatorFor;
import com.google.gwt.requestfactory.shared.ProxyFor;
import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.RequestContext;
@@ -140,6 +142,49 @@
}
}
+ /**
+ * An entity type without the usual boilerplate.
+ */
+ class LocatorEntity {
+ }
+
+ class LocatorEntityLocator extends Locator<LocatorEntity, String> {
+ @Override
+ public LocatorEntity create(Class<? extends LocatorEntity> clazz) {
+ return null;
+ }
+
+ @Override
+ public LocatorEntity find(Class<? extends LocatorEntity> clazz, String id) {
+ return null;
+ }
+
+ @Override
+ public Class<LocatorEntity> getDomainType() {
+ return null;
+ }
+
+ @Override
+ public String getId(LocatorEntity domainObject) {
+ return null;
+ }
+
+ @Override
+ public Class<String> getIdType() {
+ return null;
+ }
+
+ @Override
+ public Object getVersion(LocatorEntity domainObject) {
+ return null;
+ }
+ }
+
+ @ProxyFor(LocatorEntity.class)
+ @LocatorFor(LocatorEntityLocator.class)
+ interface LocatorEntityProxy extends EntityProxy {
+ }
+
@ProxyFor(value = Value.class)
interface MyValueProxy extends ValueProxy {
}
@@ -246,6 +291,11 @@
assertFalse(v.isPoisoned());
}
+ public void testLocatorProxy() {
+ v.validateEntityProxy(LocatorEntityProxy.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
public void testMismatchedArity() {
v.validateRequestContext(ServiceRequestMismatchedArity.class.getName());
assertTrue(v.isPoisoned());
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
index 0e33457..3dff0cc 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
@@ -18,7 +18,6 @@
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.requestfactory.client.RequestFactoryTest;
-import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.server.testing.InProcessRequestTransport;
import com.google.gwt.requestfactory.server.testing.RequestFactoryMagic;
import com.google.gwt.requestfactory.shared.RequestFactory;
@@ -32,7 +31,7 @@
public static <T extends RequestFactory> T createInProcess(Class<T> clazz) {
EventBus eventBus = new SimpleEventBus();
T req = RequestFactoryMagic.create(clazz);
- ServiceLayer serviceLayer = new ReflectiveServiceLayer();
+ ServiceLayer serviceLayer = ServiceLayer.create();
SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
req.initialize(eventBus, new InProcessRequestTransport(processor));
return req;
diff --git a/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java b/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java
new file mode 100644
index 0000000..fc550e8
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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 com.google.gwt.core.client.GWT;
+import com.google.gwt.event.shared.SimpleEventBus;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests the use of Locator objects.
+ */
+public class LocatorTest extends GWTTestCase {
+ /**
+ * The locator being tested.
+ */
+ public static class DomainLocator extends Locator<Domain, String> {
+ @Override
+ public Domain create(Class<? extends Domain> clazz) {
+ assertEquals(Domain.class, clazz);
+ return new Domain();
+ }
+
+ @Override
+ public Domain find(Class<? extends Domain> clazz, String id) {
+ assertEquals(ID, id);
+ return Domain.INSTANCE;
+ }
+
+ @Override
+ public Class<Domain> getDomainType() {
+ return Domain.class;
+ }
+
+ @Override
+ public String getId(Domain domainObject) {
+ return ID;
+ }
+
+ @Override
+ public Class<String> getIdType() {
+ return String.class;
+ }
+
+ @Override
+ public Object getVersion(Domain domainObject) {
+ return 0;
+ }
+ }
+
+ /**
+ * The factory under test.
+ */
+ protected interface Factory extends RequestFactory {
+ Context context();
+ }
+
+ @Service(ContextImpl.class)
+ interface Context extends RequestContext {
+ Request<DomainProxy> getDomain();
+ }
+
+ static class ContextImpl {
+ public static Domain getDomain() {
+ return Domain.INSTANCE;
+ }
+ }
+
+ static class Domain {
+ static final Domain INSTANCE = new Domain();
+ }
+
+ @ProxyFor(Domain.class)
+ @LocatorFor(DomainLocator.class)
+ interface DomainProxy extends EntityProxy {
+ EntityProxyId<DomainProxy> stableId();
+ };
+
+ private static final String ID = "DomainId";
+ private static final int TEST_DELAY = 500;
+
+ private Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.requestfactory.RequestFactorySuite";
+ }
+
+ public void testLocator() {
+ delayTestFinish(TEST_DELAY);
+ context().getDomain().fire(new Receiver<DomainProxy>() {
+ @Override
+ public void onSuccess(final DomainProxy response) {
+ factory.find(response.stableId()).fire(new Receiver<DomainProxy>() {
+ @Override
+ public void onSuccess(DomainProxy found) {
+ assertEquals(response.stableId(), found.stableId());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory toReturn = GWT.create(Factory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ factory = createFactory();
+ }
+
+ private Context context() {
+ return factory.context();
+ }
+}