Add Locator API to allow arbitrary domain types to be used with RequestFactory.
Refactor ServiceLayer API to allow extension through decoration.
Miscellaneous javadoc cleanups.
Issue 5111.
Patch by: bobv
Review by: rchandia,rjrjr


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


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