Merge r9318 into GWT 2.1 branch. Add ServiceLocator API. git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.1@9322 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java index a2922e4..d5efc04 100644 --- a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java +++ b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
@@ -17,12 +17,19 @@ 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; +import com.google.gwt.requestfactory.shared.ProxyFor; +import com.google.gwt.requestfactory.shared.ProxyForName; +import com.google.gwt.requestfactory.shared.Request; +import com.google.gwt.requestfactory.shared.Service; +import com.google.gwt.requestfactory.shared.ServiceLocator; +import com.google.gwt.requestfactory.shared.ServiceName; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; /** - * Adds support to the ServiceLayer chain for using {@link Locator} helper - * objects. + * Adds support to the ServiceLayer chain for using {@link Locator} and + * {@link ServiceLocator} helper objects. */ final class LocatorServiceLayer extends ServiceLayerDecorator { @@ -37,17 +44,15 @@ @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()); + return newInstance(clazz, Locator.class); + } + + @Override + public Object createServiceInstance(Method contextMethod, Method domainMethod) { + Class<? extends ServiceLocator> locatorType = getTop().resolveServiceLocator( + contextMethod, domainMethod); + ServiceLocator locator = newInstance(locatorType, ServiceLocator.class); + return locator.getInstance(domainMethod.getDeclaringClass()); } @Override @@ -79,6 +84,17 @@ return doLoadDomainObject(clazz, domainId); } + /** + * Returns true if the context method returns a {@link Request} and the domain + * method is non-static. + */ + @Override + public boolean requiresServiceLocator(Method contextMethod, + Method domainMethod) { + return Request.class.isAssignableFrom(contextMethod.getReturnType()) + && !Modifier.isStatic(domainMethod.getModifiers()); + } + @Override public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) { // Find the matching BaseProxy @@ -90,21 +106,24 @@ // 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) { + ProxyFor l = proxyType.getAnnotation(ProxyFor.class); + ProxyForName ln = proxyType.getAnnotation(ProxyForName.class); + if (l != null && !Locator.class.equals(l.locator())) { + @SuppressWarnings("unchecked") + Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) l.locator(); + locatorType = found; + } else if (ln != null && ln.locator().length() > 0) { try { @SuppressWarnings("unchecked") Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) Class.forName( - ln.value(), false, domainType.getClassLoader()).asSubclass( + ln.locator(), 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()); + return die( + e, + "Could not find the locator type specified in the @%s annotation %s", + ProxyForName.class.getCanonicalName(), ln.value()); } } else { // No locator annotation @@ -113,6 +132,34 @@ return locatorType; } + @Override + public Class<? extends ServiceLocator> resolveServiceLocator( + Method contextMethod, Method domainMethod) { + Class<? extends ServiceLocator> locatorType; + + // Look at the RequestContext + Class<?> requestContextClass = contextMethod.getDeclaringClass(); + Service l = requestContextClass.getAnnotation(Service.class); + ServiceName ln = requestContextClass.getAnnotation(ServiceName.class); + if (l != null && !ServiceLocator.class.equals(l.locator())) { + locatorType = l.locator(); + } else if (ln != null && ln.locator().length() > 0) { + try { + locatorType = Class.forName(ln.locator(), false, + requestContextClass.getClassLoader()).asSubclass( + ServiceLocator.class); + } catch (ClassNotFoundException e) { + return die( + e, + "Could not find the locator type specified in the @%s annotation %s", + ServiceName.class.getCanonicalName(), ln.value()); + } + } else { + locatorType = null; + } + return locatorType; + } + private <T> Object doGetId(T domainObject) { @SuppressWarnings("unchecked") Class<T> clazz = (Class<T>) domainObject.getClass(); @@ -162,4 +209,18 @@ } return (Locator<T, I>) getTop().createLocator(locatorType); } + + private <T> T newInstance(Class<T> clazz, Class<? super T> base) { + Throwable ex; + try { + return clazz.newInstance(); + } catch (InstantiationException e) { + ex = e; + } catch (IllegalAccessException e) { + ex = e; + } + return this.<T> die(ex, + "Could not instantiate %s %s. Is it default-instantiable?", + base.getSimpleName(), clazz.getCanonicalName()); + } }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java index 5dafce4..2c8f005 100644 --- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java +++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -31,8 +31,6 @@ 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; @@ -44,6 +42,7 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -142,6 +141,7 @@ private class DomainMapper extends EmptyVisitor { private final ErrorContext logger; private String domainInternalName; + private List<Class<? extends Annotation>> found = new ArrayList<Class<? extends Annotation>>(); private String locatorInternalName; public DomainMapper(ErrorContext logger) { @@ -165,65 +165,93 @@ } } + /** + * This method examines one annotation at a time. + */ @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)); + // Set to true if the annotation should have class literal values + boolean expectClasses = false; + // Set to true if the annonation has string values + boolean expectNames = false; - if (foundLocator || foundProxy || foundService) { + if (desc.equals(Type.getDescriptor(ProxyFor.class))) { + expectClasses = true; + found.add(ProxyFor.class); + } else if (desc.equals(Type.getDescriptor(ProxyForName.class))) { + expectNames = true; + found.add(ProxyForName.class); + } else if (desc.equals(Type.getDescriptor(Service.class))) { + expectClasses = true; + found.add(Service.class); + } else if (desc.equals(Type.getDescriptor(ServiceName.class))) { + expectNames = true; + found.add(ServiceName.class); + } + + if (expectClasses) { return new EmptyVisitor() { @Override public void visit(String name, Object value) { if ("value".equals(name)) { - String found = ((Type) value).getInternalName(); - if (foundLocator) { - locatorInternalName = found; - } else { - domainInternalName = found; - } + domainInternalName = ((Type) value).getInternalName(); + } else if ("locator".equals(name)) { + locatorInternalName = ((Type) value).getInternalName(); } } }; } - if (foundLocatorName || foundProxyName || foundServiceName) { + if (expectNames) { return new EmptyVisitor() { @Override public void visit(String name, Object value) { - if ("value".equals(name)) { - String sourceName = (String) value; - - /* - * The input is a source name, so we need to convert it to an - * internal name. We'll do this by substituting dollar signs for - * the last slash in the name until there are no more slashes. - */ - StringBuffer desc = new StringBuffer(sourceName.replace('.', '/')); - while (!loader.exists(desc.toString() + ".class")) { - logger.spam("Did not find " + desc.toString()); - int idx = desc.lastIndexOf("/"); - if (idx == -1) { - return; - } - desc.setCharAt(idx, '$'); - } - - if (foundLocatorName) { - locatorInternalName = desc.toString(); - } else { - domainInternalName = desc.toString(); - } - logger.spam(domainInternalName); + String sourceName; + if ("value".equals(name) || "locator".equals(name)) { + sourceName = (String) value; + } else { + return; } + + /* + * The input is a source name, so we need to convert it to an + * internal name. We'll do this by substituting dollar signs for the + * last slash in the name until there are no more slashes. + */ + StringBuffer desc = new StringBuffer(sourceName.replace('.', '/')); + while (!loader.exists(desc.toString() + ".class")) { + logger.spam("Did not find " + desc.toString()); + int idx = desc.lastIndexOf("/"); + if (idx == -1) { + return; + } + desc.setCharAt(idx, '$'); + } + + if ("locator".equals(name)) { + locatorInternalName = desc.toString(); + } else { + domainInternalName = desc.toString(); + } + logger.spam(domainInternalName); } }; } return null; } + + @Override + public void visitEnd() { + // Only allow one annotation + if (found.size() > 1) { + StringBuilder sb = new StringBuilder(); + for (Class<?> clazz : found) { + sb.append(" @").append(clazz.getSimpleName()); + } + logger.poison("Redundant domain mapping annotations present:%s", + sb.toString()); + } + } } /** @@ -536,10 +564,10 @@ */ private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>(); /** - * Maps client types (e.g. FooProxy) to their locator types (e.g. FooLocator). + * Maps client types (e.g. FooProxy or FooContext) to their locator types + * (e.g. FooLocator or FooServiceLocator). */ private final Map<Type, Type> clientToLocatorMap = new HashMap<Type, Type>(); - /** * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy, * FooBProxy). @@ -731,7 +759,8 @@ } // Check the client method against the domain - checkClientMethodInDomain(logger, method, domainServiceType); + checkClientMethodInDomain(logger, method, domainServiceType, + !clientToLocatorMap.containsKey(requestContextType)); maybeCheckReferredProxies(logger, method); } @@ -887,7 +916,7 @@ * to the server's domain type. */ private void checkClientMethodInDomain(ErrorContext logger, RFMethod method, - Type domainServiceType) { + Type domainServiceType, boolean requireStaticMethodsForRequestType) { logger = logger.setMethod(method); // Create a "translated" method declaration to search for @@ -905,7 +934,8 @@ logger.poison("The method %s is declared to return %s, but the" + " service method is static", method.getName(), InstanceRequest.class.getCanonicalName()); - } else if (!isInstance && !found.isDeclaredStatic) { + } else if (requireStaticMethodsForRequestType && !isInstance + && !found.isDeclaredStatic()) { logger.poison("The method %s is declared to return %s, but the" + " service method is not static", method.getName(), Request.class.getCanonicalName());
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java index a906e9f..8cd93a4 100644 --- a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java +++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
@@ -17,6 +17,7 @@ import com.google.gwt.requestfactory.shared.BaseProxy; import com.google.gwt.requestfactory.shared.Locator; +import com.google.gwt.requestfactory.shared.ServiceLocator; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -109,6 +110,17 @@ public abstract <T extends Locator<?, ?>> T createLocator(Class<T> clazz); /** + * Create an instance of a service object that can be used as the target for + * the given method invocation. + * + * @param contextMethod a method defined in a RequestContext + * @param domainMethod the method that the service object must implement + * @return an instance of the requested service object + */ + public abstract Object createServiceInstance(Method contextMethod, + Method domainMethod); + + /** * 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 @@ -209,6 +221,18 @@ List<Object> domainIds); /** + * Determines if the invocation of a domain method requires a + * {@link ServiceLocator} as the 0th parameter when passed into + * {@link #invoke(Method, Object...)}. + * + * @param contextMethod a method defined in a RequestContext + * @param domainMethod a domain method + * @return {@code true} if a ServiceLocator is required + */ + public abstract boolean requiresServiceLocator(Method contextMethod, + Method domainMethod); + + /** * Given a type token previously returned from * {@link #resolveTypeToken(Class)}, return the Class literal associated with * the token. @@ -278,6 +302,19 @@ String requestContextClass, String methodName); /** + * Given a RequestContext method declaration, resolve the + * {@link ServiceLocator} that should be used when invoking the domain method. + * This method will only be called if {@link #requiresServiceLocator(Method)} + * returned {@code true} for the associated domain method. + * + * @param contextMethod a RequestContext method declaration + * @param domainMethod the domain method that will be invoked + * @return the type of ServiceLocator to use + */ + public abstract Class<? extends ServiceLocator> resolveServiceLocator( + Method contextMethod, Method domainMethod); + + /** * Return a string used to represent the given type in the wire protocol. * * @param proxyType a client-side EntityProxy or ValueProxy type
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java index 887d49c..0c89f75 100644 --- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java +++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
@@ -17,6 +17,7 @@ import com.google.gwt.requestfactory.shared.BaseProxy; import com.google.gwt.requestfactory.shared.Locator; +import com.google.gwt.requestfactory.shared.ServiceLocator; import com.google.gwt.rpc.server.Pair; import java.lang.ref.SoftReference; @@ -42,20 +43,27 @@ private static SoftReference<Map<Method, Map<Object, Object>>> methodCache; private static final Method createLocator; + private static final Method createServiceInstance; private static final Method getIdType; private static final Method getRequestReturnType; + private static final Method requiresServiceLocator; 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 resolveServiceLocator; private static final Method resolveTypeToken; static { createLocator = getMethod("createLocator", Class.class); + createServiceInstance = getMethod("createServiceInstance", Method.class, + Method.class); getIdType = getMethod("getIdType", Class.class); getRequestReturnType = getMethod("getRequestReturnType", Method.class); + requiresServiceLocator = getMethod("requiresServiceLocator", Method.class, + Method.class); resolveClass = getMethod("resolveClass", String.class); resolveClientType = getMethod("resolveClientType", Class.class, Class.class, boolean.class); @@ -64,6 +72,8 @@ resolveLocator = getMethod("resolveLocator", Class.class); resolveRequestContextMethod = getMethod("resolveRequestContextMethod", String.class, String.class); + resolveServiceLocator = getMethod("resolveServiceLocator", Method.class, + Method.class); resolveTypeToken = getMethod("resolveTypeToken", Class.class); } @@ -98,6 +108,12 @@ } @Override + public Object createServiceInstance(Method contextMethod, Method domainMethod) { + return getOrCache(createServiceInstance, new Pair<Method, Method>( + contextMethod, domainMethod), Object.class, contextMethod, domainMethod); + } + + @Override public Class<?> getIdType(Class<?> domainType) { return getOrCache(getIdType, domainType, Class.class, domainType); } @@ -109,18 +125,26 @@ } @Override + public boolean requiresServiceLocator(Method contextMethod, + Method domainMethod) { + return getOrCache(requiresServiceLocator, new Pair<Method, Method>( + contextMethod, domainMethod), Boolean.class, contextMethod, + domainMethod); + } + + @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); + Class<?> clazz = getOrCache(resolveClientType, + new Pair<Class<?>, Class<?>>(domainClass, clientType), Class.class, + domainClass, clientType, required); + return clazz == null ? null : clazz.asSubclass(clientType); } @Override @@ -149,6 +173,15 @@ } @Override + public Class<? extends ServiceLocator> resolveServiceLocator( + Method contextMethod, Method domainMethod) { + Class<?> clazz = getOrCache(resolveServiceLocator, + new Pair<Method, Method>(contextMethod, domainMethod), Class.class, + contextMethod, domainMethod); + return clazz == null ? null : clazz.asSubclass(ServiceLocator.class); + } + + @Override public String resolveTypeToken(Class<? extends BaseProxy> domainClass) { return getOrCache(resolveTypeToken, domainClass, String.class, domainClass); }
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java index b9a2248..c642d94 100644 --- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java +++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
@@ -17,6 +17,7 @@ import com.google.gwt.requestfactory.shared.BaseProxy; import com.google.gwt.requestfactory.shared.Locator; +import com.google.gwt.requestfactory.shared.ServiceLocator; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -54,6 +55,11 @@ } @Override + public Object createServiceInstance(Method contextMethod, Method domainMethod) { + return getNext().createServiceInstance(contextMethod, domainMethod); + } + + @Override public Object getId(Object domainObject) { return getNext().getId(domainObject); } @@ -100,6 +106,12 @@ } @Override + public boolean requiresServiceLocator(Method contextMethod, + Method domainMethod) { + return getNext().requiresServiceLocator(contextMethod, domainMethod); + } + + @Override public Class<? extends BaseProxy> resolveClass(String typeToken) { return getNext().resolveClass(typeToken); } @@ -133,6 +145,12 @@ } @Override + public Class<? extends ServiceLocator> resolveServiceLocator( + Method contextMethod, Method domainMethod) { + return getNext().resolveServiceLocator(contextMethod, domainMethod); + } + + @Override public String resolveTypeToken(Class<? extends BaseProxy> proxyType) { return getNext().resolveTypeToken(proxyType); }
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java index 4be0769..07fe1f5 100644 --- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java +++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -27,6 +27,7 @@ import com.google.gwt.requestfactory.shared.BaseProxy; import com.google.gwt.requestfactory.shared.EntityProxyId; import com.google.gwt.requestfactory.shared.InstanceRequest; +import com.google.gwt.requestfactory.shared.Request; import com.google.gwt.requestfactory.shared.ServerFailure; import com.google.gwt.requestfactory.shared.WriteOperation; import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory; @@ -47,7 +48,6 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; @@ -346,7 +346,7 @@ */ private List<Object> decodeInvocationArguments(RequestState source, InvocationMessage invocation, Method contextMethod, Method domainMethod) { - boolean isStatic = Modifier.isStatic(domainMethod.getModifiers()); + boolean isStatic = Request.class.isAssignableFrom(contextMethod.getReturnType()); int baseLength = contextMethod.getParameterTypes().length; int length = baseLength + (isStatic ? 0 : 1); int offset = isStatic ? 0 : 1; @@ -418,9 +418,16 @@ + invocation.getOperation(), null); } - // Invoke it + // Compute the arguments List<Object> args = decodeInvocationArguments(state, invocation, contextMethod, domainMethod); + // Possibly use a ServiceLocator + if (service.requiresServiceLocator(contextMethod, domainMethod)) { + Object serviceInstance = service.createServiceInstance(contextMethod, + domainMethod); + args.add(0, serviceInstance); + } + // Invoke it Object returnValue = service.invoke(domainMethod, args.toArray()); // Convert domain object to client object
diff --git a/user/src/com/google/gwt/requestfactory/shared/Locator.java b/user/src/com/google/gwt/requestfactory/shared/Locator.java index a88032b..3516984 100644 --- a/user/src/com/google/gwt/requestfactory/shared/Locator.java +++ b/user/src/com/google/gwt/requestfactory/shared/Locator.java
@@ -29,7 +29,7 @@ * @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 + * @see ProxyFor#locator() */ public abstract class Locator<T, I> { /**
diff --git a/user/src/com/google/gwt/requestfactory/shared/LocatorFor.java b/user/src/com/google/gwt/requestfactory/shared/LocatorFor.java deleted file mode 100644 index 2c237fd..0000000 --- a/user/src/com/google/gwt/requestfactory/shared/LocatorFor.java +++ /dev/null
@@ -1,35 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.gwt.requestfactory.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 deleted file mode 100644 index f537cf4..0000000 --- a/user/src/com/google/gwt/requestfactory/shared/LocatorForName.java +++ /dev/null
@@ -1,35 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.gwt.requestfactory.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/ProxyFor.java b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java index 4da1afe..38f6df6 100644 --- a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java +++ b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
@@ -21,13 +21,22 @@ import java.lang.annotation.Target; /** - * Annotation on EntityProxy classes specifying the domain (server-side) object - * type. + * Annotation on EntityProxy and ValueProxy classes specifying the domain + * (server-side) object type. * * @see ProxyForName */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ProxyFor { + /** + * The domain type that the proxy is mapped to. + */ Class<?> value(); + + /** + * An optional {@link Locator} that provides instances of the domain objects. + */ + @SuppressWarnings("rawtypes") + Class<? extends Locator> locator() default Locator.class; }
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java index fa3f5bd..5eb5594 100644 --- a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java +++ b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
@@ -28,5 +28,14 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ProxyForName { + /** + * The name of the domain type that the proxy is mapped to. + */ String value(); + + /** + * An optional name of a {@link Locator} that provides instances of the domain + * objects. + */ + String locator() default ""; }
diff --git a/user/src/com/google/gwt/requestfactory/shared/Service.java b/user/src/com/google/gwt/requestfactory/shared/Service.java index 29d8829..de14983 100644 --- a/user/src/com/google/gwt/requestfactory/shared/Service.java +++ b/user/src/com/google/gwt/requestfactory/shared/Service.java
@@ -29,5 +29,16 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Service { + /** + * The domain type that provides the implementations for the methods defined + * in the RequestContext. + */ Class<?> value(); + + /** + * An optional {@link ServiceLocator} that provides instances of service + * objects used when invoking instance methods on the type returned by + * {@link #value()}. + */ + Class<? extends ServiceLocator> locator() default ServiceLocator.class; }
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java b/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java new file mode 100644 index 0000000..dfa841d --- /dev/null +++ b/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
@@ -0,0 +1,37 @@ +/* + * 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 ServiceLocator provides instances of a type specified by a {@link Service} + * when {@link Request} methods declared in a {@link RequestContext}are mapped + * onto instance (non-static) methods. + * <p> + * ServiceLocator subtypes must be default instantiable (i.e. public static + * types with a no-arg constructor). Instances of ServiceLocators may be + * retained and reused by the RequestFactory service layer. + * + * @see Service#locator() + */ +public interface ServiceLocator { + /** + * Returns an instance of the service object. + * + * @param clazz the requested type of service object + * @return an instance of the service object + */ + Object getInstance(Class<?> clazz); +}
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java index beb72b6..464c032 100644 --- a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java +++ b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
@@ -28,5 +28,16 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ServiceName { + /** + * The binary name of the domain type that provides the implementations for + * the methods defined in the RequestContext. + */ String value(); + + /** + * An optional binary name of a {@link ServiceLocator} that provides instances + * of service objects used when invoking instance methods on the type returned + * by {@link #value()}. + */ + String locator() default ""; }
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java index ace0f2f..c3fe25e 100644 --- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java +++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -761,6 +761,17 @@ }); } + public void testInstanceServiceRequest() { + delayTestFinish(DELAY_TEST_FINISH); + req.instanceServiceRequest().add(5).fire(new Receiver<Integer>() { + @Override + public void onSuccess(Integer response) { + assertEquals(10, (int) response); + finishTestAndReset(); + } + }); + } + /** * Make sure our stock RF logging service keeps receiving. */ @@ -1157,6 +1168,11 @@ }); } + /* + * TODO: all these tests should check the final values. It will be easy when + * we have better persistence than the singleton pattern. + */ + /** * Ensure that a relationship can be set up between two newly-created objects. */ @@ -1183,11 +1199,6 @@ } /* - * TODO: all these tests should check the final values. It will be easy when - * we have better persistence than the singleton pattern. - */ - - /* * Find Entity2 Create Entity, Persist Entity Relate Entity2 to Entity Persist * Entity */ @@ -1419,6 +1430,11 @@ }); } + /* + * TODO: all these tests should check the final values. It will be easy when + * we have better persistence than the singleton pattern. + */ + public void testPersistValueList() { delayTestFinish(DELAY_TEST_FINISH); simpleFooRequest().findSimpleFooById(999L).fire( @@ -1482,11 +1498,6 @@ * TODO: all these tests should check the final values. It will be easy when * we have better persistence than the singleton pattern. */ - - /* - * TODO: all these tests should check the final values. It will be easy when - * we have better persistence than the singleton pattern. - */ public void testPersistValueListRemove() { delayTestFinish(DELAY_TEST_FINISH); simpleFooRequest().findSimpleFooById(999L).fire( @@ -2071,28 +2082,6 @@ } /** - * We provide a simple UserInformation class to give GAE developers a hand, - * and other developers a hint. Make sure RF doesn't break it (it relies on - * server side upcasting, and a somewhat sleazey reflective lookup mechanism - * in a static method on UserInformation). - */ - public void testUserInfo() { - delayTestFinish(DELAY_TEST_FINISH); - req.userInformationRequest().getCurrentUserInformation("").fire( - new Receiver<UserInformationProxy>() { - @Override - public void onSuccess(UserInformationProxy response) { - response = checkSerialization(response); - assertEquals("Dummy Email", response.getEmail()); - assertEquals("Dummy User", response.getName()); - assertEquals("", response.getLoginUrl()); - assertEquals("", response.getLogoutUrl()); - finishTestAndReset(); - } - }); - } - - /** * Check if a graph of unpersisted objects can be echoed. */ public void testUnpersistedEchoComplexGraph() { @@ -2232,6 +2221,28 @@ }); } + /** + * We provide a simple UserInformation class to give GAE developers a hand, + * and other developers a hint. Make sure RF doesn't break it (it relies on + * server side upcasting, and a somewhat sleazey reflective lookup mechanism + * in a static method on UserInformation). + */ + public void testUserInfo() { + delayTestFinish(DELAY_TEST_FINISH); + req.userInformationRequest().getCurrentUserInformation("").fire( + new Receiver<UserInformationProxy>() { + @Override + public void onSuccess(UserInformationProxy response) { + response = checkSerialization(response); + assertEquals("Dummy Email", response.getEmail()); + assertEquals("Dummy User", response.getName()); + assertEquals("", response.getLoginUrl()); + assertEquals("", response.getLogoutUrl()); + finishTestAndReset(); + } + }); + } + public void testValueObjectCreateSetRetrieveUpdate() { delayTestFinish(DELAY_TEST_FINISH); SimpleFooRequest req = simpleFooRequest(); @@ -2373,6 +2384,48 @@ checkEqualityAndHashcode(a, b); } + /** + * Since a ValueProxy cannot be passed into RequestContext edit, a proxy + * returned from a service method should be mutable by default. + */ + public void testValueObjectReturnedFromRequestIsImmutable() { + delayTestFinish(DELAY_TEST_FINISH); + simpleFooRequest().returnValueProxy().fire( + new Receiver<SimpleValueProxy>() { + @Override + public void onSuccess(SimpleValueProxy a) { + a = checkSerialization(a); + try { + a.setNumber(77); + fail(); + } catch (IllegalStateException expected) { + } + try { + // Ensure Dates comply with ValueProxy mutation behaviors + a.getDate().setTime(1); + fail(); + } catch (IllegalStateException expected) { + } + SimpleFooRequest ctx = simpleFooRequest(); + final SimpleValueProxy toCheck = ctx.edit(a); + toCheck.setNumber(77); + toCheck.getDate().setTime(1); + ctx.returnValueProxy().fire(new Receiver<SimpleValueProxy>() { + @Override + public void onSuccess(SimpleValueProxy b) { + b = checkSerialization(b); + b = simpleFooRequest().edit(b); + // Now check that same value is equal across contexts + b.setNumber(77); + b.setDate(new Date(1)); + checkEqualityAndHashcode(toCheck, b); + finishTestAndReset(); + } + }); + } + }); + } + public void testValueObjectViolationsOnCreate() { delayTestFinish(DELAY_TEST_FINISH); SimpleFooRequest req = simpleFooRequest(); @@ -2433,48 +2486,6 @@ }); } - /** - * Since a ValueProxy cannot be passed into RequestContext edit, a proxy - * returned from a service method should be mutable by default. - */ - public void testValueObjectReturnedFromRequestIsImmutable() { - delayTestFinish(DELAY_TEST_FINISH); - simpleFooRequest().returnValueProxy().fire( - new Receiver<SimpleValueProxy>() { - @Override - public void onSuccess(SimpleValueProxy a) { - a = checkSerialization(a); - try { - a.setNumber(77); - fail(); - } catch (IllegalStateException expected) { - } - try { - // Ensure Dates comply with ValueProxy mutation behaviors - a.getDate().setTime(1); - fail(); - } catch (IllegalStateException expected) { - } - SimpleFooRequest ctx = simpleFooRequest(); - final SimpleValueProxy toCheck = ctx.edit(a); - toCheck.setNumber(77); - toCheck.getDate().setTime(1); - ctx.returnValueProxy().fire(new Receiver<SimpleValueProxy>() { - @Override - public void onSuccess(SimpleValueProxy b) { - b = checkSerialization(b); - b = simpleFooRequest().edit(b); - // Now check that same value is equal across contexts - b.setNumber(77); - b.setDate(new Date(1)); - checkEqualityAndHashcode(toCheck, b); - finishTestAndReset(); - } - }); - } - }); - } - public void testViolationAbsent() { delayTestFinish(DELAY_TEST_FINISH);
diff --git a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java index 3e160e5..ce68390 100644 --- a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java +++ b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
@@ -28,12 +28,14 @@ import com.google.gwt.requestfactory.server.TestContextImpl; 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.ProxyFor; import com.google.gwt.requestfactory.shared.Receiver; import com.google.gwt.requestfactory.shared.Request; import com.google.gwt.requestfactory.shared.RequestContext; import com.google.gwt.requestfactory.shared.RequestFactory; import com.google.gwt.requestfactory.shared.Service; +import com.google.gwt.requestfactory.shared.ServiceLocator; import com.google.gwt.requestfactory.shared.ValueProxy; import junit.framework.TestCase; @@ -269,8 +271,10 @@ new EmptyMockJavaResource(Iterable.class), new EmptyMockJavaResource(EntityProxy.class), new EmptyMockJavaResource(InstanceRequest.class), + new EmptyMockJavaResource(Locator.class), new EmptyMockJavaResource(RequestFactory.class), new EmptyMockJavaResource(Receiver.class), + new EmptyMockJavaResource(ServiceLocator.class), new EmptyMockJavaResource(ValueProxy.class), new RealJavaResource(Request.class),
diff --git a/user/test/com/google/gwt/requestfactory/server/InstanceService.java b/user/test/com/google/gwt/requestfactory/server/InstanceService.java new file mode 100644 index 0000000..9d9c940 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/server/InstanceService.java
@@ -0,0 +1,24 @@ +/* + * 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; + +/** + * A service API that doesn't have static methods and that can't be + * default-instantiated. + */ +public interface InstanceService { + Integer add(int value); +}
diff --git a/user/test/com/google/gwt/requestfactory/server/InstanceServiceImpl.java b/user/test/com/google/gwt/requestfactory/server/InstanceServiceImpl.java new file mode 100644 index 0000000..e2dd226 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/server/InstanceServiceImpl.java
@@ -0,0 +1,32 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.server; + +/** + * A service API that doesn't have static methods and that can't be + * default-instantiated. + */ +public class InstanceServiceImpl implements InstanceService { + private final int base; + + public InstanceServiceImpl(int base) { + this.base = base; + } + + public Integer add(int value) { + return base + value; + } +}
diff --git a/user/test/com/google/gwt/requestfactory/server/InstanceServiceLocator.java b/user/test/com/google/gwt/requestfactory/server/InstanceServiceLocator.java new file mode 100644 index 0000000..e5a5b9e --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/server/InstanceServiceLocator.java
@@ -0,0 +1,29 @@ +/* + * 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.ServiceLocator; + +/** + * Demonstrates instance-based service objects. + */ +public class InstanceServiceLocator implements ServiceLocator { + + public Object getInstance(Class<?> clazz) { + assert InstanceService.class.equals(clazz); + return clazz.cast(new InstanceServiceImpl(5)); + } +}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java index eb87261..f6640da 100644 --- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java +++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -19,12 +19,13 @@ 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.ProxyForName; import com.google.gwt.requestfactory.shared.Request; import com.google.gwt.requestfactory.shared.RequestContext; import com.google.gwt.requestfactory.shared.RequestFactory; import com.google.gwt.requestfactory.shared.Service; +import com.google.gwt.requestfactory.shared.ServiceName; import com.google.gwt.requestfactory.shared.SimpleRequestFactory; import com.google.gwt.requestfactory.shared.ValueProxy; import com.google.gwt.requestfactory.shared.impl.FindRequest; @@ -180,8 +181,7 @@ } } - @ProxyFor(LocatorEntity.class) - @LocatorFor(LocatorEntityLocator.class) + @ProxyFor(value = LocatorEntity.class, locator = LocatorEntityLocator.class) interface LocatorEntityProxy extends EntityProxy { } @@ -228,6 +228,13 @@ Request<Integer> doesNotExist(int a); } + @ProxyFor(Domain.class) + @ProxyForName("Domain") + @Service(Domain.class) + @ServiceName("Domain") + interface TooManyAnnotations extends RequestContext { + } + static class UnexpectedIdAndVersionDomain { Random getId() { return null; @@ -349,6 +356,11 @@ assertFalse(v.isPoisoned()); } + public void testTooManyAnnotations() { + v.validateRequestContext(TooManyAnnotations.class.getName()); + assertTrue(v.isPoisoned()); + } + public void testUnexpectedIdAndVersion() { v.validateEntityProxy(UnexpectedIdAndVersionProxy.class.getName()); assertTrue(v.isPoisoned());
diff --git a/user/test/com/google/gwt/requestfactory/shared/InstanceServiceRequest.java b/user/test/com/google/gwt/requestfactory/shared/InstanceServiceRequest.java new file mode 100644 index 0000000..79fef75 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/shared/InstanceServiceRequest.java
@@ -0,0 +1,27 @@ +/* + * 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.requestfactory.server.InstanceService; +import com.google.gwt.requestfactory.server.InstanceServiceLocator; + +/** + * Used to test the ServiceLocator extension hook. + */ +@Service(value = InstanceService.class, locator = InstanceServiceLocator.class) +public interface InstanceServiceRequest extends RequestContext { + Request<Integer> add(int value); +}
diff --git a/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java b/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java index fc550e8..a321383 100644 --- a/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java +++ b/user/test/com/google/gwt/requestfactory/shared/LocatorTest.java
@@ -82,8 +82,7 @@ static final Domain INSTANCE = new Domain(); } - @ProxyFor(Domain.class) - @LocatorFor(DomainLocator.class) + @ProxyFor(value = Domain.class, locator = DomainLocator.class) interface DomainProxy extends EntityProxy { EntityProxyId<DomainProxy> stableId(); };
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java index c62b5a5..7def869 100644 --- a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java +++ b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
@@ -20,6 +20,8 @@ * UserInformation and Logging services. */ public interface SimpleRequestFactory extends BasicRequestFactory { + + InstanceServiceRequest instanceServiceRequest(); SimpleBarRequest simpleBarRequest();