Add ServiceLocator API to allow service methods to be invoked on non-static methods.
http://code.google.com/p/google-web-toolkit/wiki/RequestFactory_2_1_1
Issue 5680.
Patch by: bobv
Review by: rchandia,rjrjr

Review at http://gwt-code-reviews.appspot.com/1162801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9318 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();