Bye Bye Boilerplate patch. No static configuration required for servlet
beyond @Service annotations on request interfaces and
@DataTransferObject annotations on Record types.

TOKEN no longer required on Record types, as the class itself is used as
a token.

Introduced OperationRegistry and RequestSecurityProvider interfaces.

Removed the no longer used annotations: ServerType, ServerOperation. Deleted
ExpensesServerSideOperations.

Patch is updated version of http://gwt-code-reviews.appspot.com/661801

Patch by: cromwellian, amitmanjhi
Review by: amitmanjhi


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8309 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/app/place/AbstractRecordEditActivity.java b/bikeshed/src/com/google/gwt/app/place/AbstractRecordEditActivity.java
index 6da56eb..627b1b4 100644
--- a/bikeshed/src/com/google/gwt/app/place/AbstractRecordEditActivity.java
+++ b/bikeshed/src/com/google/gwt/app/place/AbstractRecordEditActivity.java
@@ -132,7 +132,7 @@
     if (creating) {
       // TODO shouldn't have to cast like this. Let's get something better than
       // a string token
-      R tempRecord = (R) deltas.create(getRecordToken());
+      R tempRecord = (R) deltas.create(getRecordClass());
       futureId = tempRecord.getId();
       doStart(display, tempRecord);
     } else {
@@ -169,7 +169,7 @@
    * Called to fetch the string token needed to get a new record via
    * {@link DeltaValueStore#create}.
    */
-  protected abstract String getRecordToken();
+  protected abstract Class getRecordClass();
 
   private void doStart(final Display display, R record) {
     view.setEnabled(true);
diff --git a/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index 309ffd5..4dfab30 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -35,9 +35,9 @@
 import com.google.gwt.requestfactory.client.impl.AbstractLongRequest;
 import com.google.gwt.requestfactory.client.impl.ClientRequestHelper;
 import com.google.gwt.requestfactory.client.impl.RequestFactoryJsonImpl;
+import com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistry;
 import com.google.gwt.requestfactory.shared.RecordListRequest;
 import com.google.gwt.requestfactory.shared.RecordRequest;
-import com.google.gwt.requestfactory.shared.ServerOperation;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -322,16 +322,11 @@
     SourceWriter sw = f.createSourceWriter(generatorContext, out);
     sw.println();
 
-    sw.println("public RecordSchema<? extends Record> getType(String token) {");
+    sw.println("public RecordSchema<? extends Record> getType(Class token) {");
     sw.indent();
     for (JClassType publicRecordType : generatedRecordTypes) {
-      if (publicRecordType.getField("TOKEN") == null) {
-        logger.log(TreeLogger.ERROR, "Record type "
-            + publicRecordType.getQualifiedSourceName()
-            + " should have a field TOKEN");
-        throw new UnableToCompleteException();
-      }
-      sw.println("if (token == " + publicRecordType.getName() + ".TOKEN) {");
+
+      sw.println("if (token == " + publicRecordType.getName() + ".class) {");
       sw.indent();
       sw.println("return " + publicRecordType.getName() + "Impl.SCHEMA;");
       sw.outdent();
@@ -385,12 +380,8 @@
       ensureRecordType(logger, generatorContext,
           returnType.getPackage().getName(), returnType);
 
-      ServerOperation annotation = method.getAnnotation(ServerOperation.class);
-      if (annotation == null) {
-        logger.log(TreeLogger.ERROR, "no annotation on the service method "
-            + method);
-        throw new UnableToCompleteException();
-      }
+      String operationName = interfaceType.getQualifiedSourceName()
+          + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR + method.getName();
 
       JClassType requestType = method.getReturnType().isClassOrInterface();
       String requestClassName = null;
@@ -420,7 +411,7 @@
       sw.indent();
       sw.println("return " + ClientRequestHelper.class.getSimpleName()
           + ".getRequestString(" + RequestDataManager.class.getSimpleName()
-          + ".getRequestMap(\"" + annotation.value() + "\", "
+          + ".getRequestMap(\"" + operationName + "\", "
           + getParametersAsString(method) + ", null));");
       sw.outdent();
       sw.println("}");
@@ -617,12 +608,10 @@
     sw.println("}");
 
     sw.println();
-    sw.println("public String getToken() {");
+    sw.println("public Class getToken() {");
     sw.indent();
-    String fieldName = "TOKEN";
-    validateTokenField(publicRecordType, fieldName, typeOracle, logger);
-    sw.println("return " + publicRecordType.getName() + "." + fieldName
-        + "; // special field");
+    sw.println("return " + publicRecordType.getQualifiedSourceName() + ".class;"
+        + " // special field");
     sw.outdent();
     sw.println("}");
 
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java b/bikeshed/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
new file mode 100644
index 0000000..4b92239
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.Service;
+
+/**
+ * A security provider that enforces {@link com.google.gwt.requestfactory.shared.Service}
+ * annotations.
+ */
+public class DefaultSecurityProvider implements RequestSecurityProvider {
+
+  public void checkClass(Class<?> clazz) throws SecurityException {
+    Service service = clazz.getAnnotation(Service.class);
+    if (service == null) {
+      throw new SecurityException(
+          "Class " + clazz.getName() + " does not have a @Service annotation.");
+    }
+    try {
+      Class.forName(service.value().getCanonicalName());
+    } catch (ClassNotFoundException e) {
+      throw new SecurityException(
+          "Class " + service.value() + " from @Service annotation on " + clazz
+              + " cannot be loaded.");
+    }
+  }
+
+  public String mapOperation(String operationName) throws SecurityException {
+    return operationName;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/ServerOperation.java b/bikeshed/src/com/google/gwt/requestfactory/server/OperationRegistry.java
similarity index 60%
rename from bikeshed/src/com/google/gwt/requestfactory/shared/ServerOperation.java
rename to bikeshed/src/com/google/gwt/requestfactory/server/OperationRegistry.java
index 5ea31a3..ef8a538 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/ServerOperation.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/OperationRegistry.java
@@ -13,19 +13,14 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.shared;
+package com.google.gwt.requestfactory.server;
+
+import com.google.gwt.requestfactory.shared.RequestFactory;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * Annotation to mark RPC methods that specifies details about the operation to
- * be invoked on the server.
+ * Maps operation name to {RequestDefinition}.
  */
-public @interface ServerOperation {
+public interface OperationRegistry {
 
-  String value();
-
+  RequestFactory.RequestDefinition getOperation(String operationName);
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java b/bikeshed/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
new file mode 100644
index 0000000..529e7c0
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
@@ -0,0 +1,190 @@
+/*
+ * 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.DataTransferObject;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * OperationRegistry which uses the operation name as a convention for
+ * reflection to a method on a class, and returns an appropriate {@link
+ * com.google.gwt.requestfactory.shared.RequestFactory.RequestDefinition}.
+ */
+public class ReflectionBasedOperationRegistry implements OperationRegistry {
+
+  class ReflectiveRequestDefinition
+      implements RequestFactory.RequestDefinition {
+
+    private Class<?> requestClass;
+
+    private Method requestMethod;
+
+    private Class<?> domainClass;
+
+    private Method domainMethod;
+
+    public ReflectiveRequestDefinition(Class<?> requestClass,
+        Method requestMethod, Class<?> domainClass, Method domainMethod) {
+      this.requestClass = requestClass;
+      this.requestMethod = requestMethod;
+      this.domainClass = domainClass;
+      this.domainMethod = domainMethod;
+    }
+
+    public String getDomainClassName() {
+      return domainClass.getCanonicalName();
+    }
+
+    public String getDomainMethodName() {
+      return domainMethod.getName();
+    }
+
+    public Class<?>[] getParameterTypes() {
+      return domainMethod.getParameterTypes();
+    }
+
+    public Class<?> getReturnType() {
+      Class<?> domainReturnType = getReturnTypeFromParameter(domainMethod,
+          domainMethod.getGenericReturnType());
+      Class<?> requestReturnType = getReturnTypeFromParameter(requestMethod,
+          requestMethod.getGenericReturnType());
+      if (Record.class.isAssignableFrom(requestReturnType)) {
+        DataTransferObject annotation =
+            requestReturnType.getAnnotation(DataTransferObject.class);
+        if (annotation != null) {
+          Class<?> dtoClass = annotation.value();
+          if (!dtoClass.equals(domainReturnType)) {
+            throw new IllegalArgumentException(
+                "Type mismatch between " + domainMethod + " return type, and "
+                    + requestReturnType + "'s DataTransferObject annotation "
+                    + dtoClass);
+          }
+        } else {
+          throw new IllegalArgumentException(
+              "Missing DataTransferObject " + "annotation on record type "
+                  + requestReturnType);
+        }
+        return requestReturnType;
+      }
+      // primitive ?
+      return requestReturnType;
+    }
+
+    public boolean isReturnTypeList() {
+      return List.class.isAssignableFrom(domainMethod.getReturnType());
+    }
+
+    public String name() {
+      return requestClass.getCanonicalName() + SCOPE_SEPARATOR
+          + getDomainMethodName();
+    }
+
+    private Class<?> getReturnTypeFromParameter(Method method, Type type) {
+      if (type instanceof ParameterizedType) {
+        ParameterizedType pType = (ParameterizedType) type;
+        Class<?> rawType = (Class<?>) pType.getRawType();
+        if (List.class.isAssignableFrom(rawType)
+            || RequestFactory.RequestObject.class.isAssignableFrom(rawType)) {
+          Class<?> rType = getTypeArgument(pType);
+          if (rType != null) {
+            if (List.class.isAssignableFrom(rType)) {
+              return getReturnTypeFromParameter(method, rType);
+            }
+            return rType;
+          }
+          throw new IllegalArgumentException(
+              "Bad or missing type arguments for "
+                  + "List return type on method " + method);
+        }
+
+      } else {
+        // Primitive?
+        return (Class<?>) type;
+      }
+      return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<?> getTypeArgument(ParameterizedType type) {
+      Type[] params = type.getActualTypeArguments();
+      if (params.length == 1) {
+        return (Class<Object>) params[0];
+      }
+
+      return null;
+    }
+  }
+
+  public static final String SCOPE_SEPARATOR = "::";
+
+  private RequestSecurityProvider securityProvider;
+
+  public ReflectionBasedOperationRegistry(
+      RequestSecurityProvider securityProvider) {
+    this.securityProvider = securityProvider;
+  }
+
+  /**
+   * Turns an operation in the form of package.requestClass::method into a
+   * RequestDefinition via reflection.
+   */
+  public RequestFactory.RequestDefinition getOperation(String operationName) {
+    String decodedOperationName = securityProvider.mapOperation(operationName);
+    String parts[] = decodedOperationName.split(SCOPE_SEPARATOR);
+    final String reqClassName = parts[0];
+    final String domainMethodName = parts[1];
+    try {
+      // Do not invoke static initializer before checking if this class can be
+      // legally invoked
+      Class<?> requestClass = Class.forName(reqClassName, false,
+          this.getClass().getClassLoader());
+      securityProvider.checkClass(requestClass);
+      Service domainClassAnnotation = requestClass.getAnnotation(Service.class);
+      if (domainClassAnnotation != null) {
+        Class<?> domainClass = domainClassAnnotation.value();
+        Method requestMethod = findMethod(requestClass, domainMethodName);
+        Method domainMethod = findMethod(domainClass, domainMethodName);
+        if (requestMethod != null && domainMethod != null) {
+          return new ReflectiveRequestDefinition(requestClass, requestMethod,
+              domainClass, domainMethod);
+        }
+      }
+      return null;
+    } catch (ClassNotFoundException e) {
+      throw new SecurityException(
+          "Access to non-existent class " + reqClassName);
+    }
+  }
+
+  private Method findMethod(Class<?> clazz, String methodName) {
+    for (Method method : clazz.getDeclaredMethods()) {
+      if ((method.getModifiers() & Modifier.PUBLIC) != 0) {
+        if (method.getName().equals(methodName)) {
+          return method;
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index 43f20a8..50a59d5 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -15,9 +15,8 @@
  */
 package com.google.gwt.requestfactory.server;
 
+import com.google.gwt.requestfactory.shared.DataTransferObject;
 import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.requestfactory.shared.ServerType;
-import com.google.gwt.requestfactory.shared.RequestFactory.Config;
 import com.google.gwt.requestfactory.shared.RequestFactory.RequestDefinition;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
 import com.google.gwt.valuestore.shared.Property;
@@ -92,8 +91,6 @@
 
   private static final Set<String> BLACK_LIST = initBlackList();
 
-  private static final String SERVER_OPERATION_CONTEXT_PARAM = "servlet.serverOperation";
-
   private static Set<String> initBlackList() {
     Set<String> blackList = new HashSet<String>();
     for (String str : new String[] {"password"}) {
@@ -102,9 +99,7 @@
     return Collections.unmodifiableSet(blackList);
   }
 
-  private Config config = null;
-
-  protected Map<String, EntityRecordPair> tokenToEntityRecord;
+  private OperationRegistry operationRegistry;
 
   @SuppressWarnings("unchecked")
   @Override
@@ -208,8 +203,9 @@
       JSONObject recordObject, WriteOperation writeOperation) {
 
     try {
-      Class<?> entity = tokenToEntityRecord.get(recordToken).entity;
-      Class<? extends Record> record = tokenToEntityRecord.get(recordToken).record;
+      Class<? extends Record> record = getRecordFromClassToken(recordToken);
+      Class<?> entity = getEntityFromRecordAnnotation(record);
+
       Map<String, Class<?>> propertiesInRecord = getPropertiesFromRecord(record);
       validateKeys(recordObject, propertiesInRecord.keySet());
       updatePropertyTypes(propertiesInRecord, entity);
@@ -277,70 +273,8 @@
 
   @SuppressWarnings("unchecked")
   private void ensureConfig() {
-    if (config == null) {
-      synchronized (this) {
-        if (config != null) {
-          return;
-        }
-        try {
-          final String serverOperation = getServletContext().getInitParameter(
-              SERVER_OPERATION_CONTEXT_PARAM);
-          if (null == serverOperation) {
-            failConfig();
-          }
-          Class<?> clazz = Class.forName(serverOperation);
-          if (Config.class.isAssignableFrom(clazz)) {
-            config = ((Class<? extends Config>) clazz).newInstance();
-
-            // initialize tokenToEntity map
-            tokenToEntityRecord = new HashMap<String, EntityRecordPair>();
-            for (Class<? extends Record> recordClass : config.recordTypes()) {
-              ServerType serverType = recordClass.getAnnotation(ServerType.class);
-              String token = (String) recordClass.getField("TOKEN").get(null);
-              if (token == null) {
-                throw new IllegalStateException("TOKEN field on "
-                    + recordClass.getName() + " can not be null");
-              }
-              EntityRecordPair previousValue = tokenToEntityRecord.get(token);
-              if (previousValue != null) {
-                throw new IllegalStateException(
-                    "TOKEN fields have to be unique. TOKEN fields for both "
-                        + recordClass.getName() + " and "
-                        + previousValue.record.getName()
-                        + " have the same value, value = " + token);
-              }
-              tokenToEntityRecord.put(token, new EntityRecordPair(
-                  serverType.type(), recordClass));
-            }
-          }
-
-        } catch (ClassNotFoundException e) {
-          failConfig(e);
-        } catch (InstantiationException e) {
-          failConfig(e);
-        } catch (IllegalAccessException e) {
-          failConfig(e);
-        } catch (SecurityException e) {
-          failConfig(e);
-        } catch (ClassCastException e) {
-          failConfig(e);
-        } catch (NoSuchFieldException e) {
-          failConfig(e);
-        }
-      }
-    }
-  }
-
-  private void failConfig() {
-    failConfig(null);
-  }
-
-  private void failConfig(Throwable e) {
-    final String message = String.format("Context parameter \"%s\" must name "
-        + "a default instantiable configuration class implementing %s",
-        SERVER_OPERATION_CONTEXT_PARAM, RequestFactory.Config.class.getName());
-
-    throw new IllegalStateException(message, e);
+    operationRegistry = new ReflectionBasedOperationRegistry(
+        new DefaultSecurityProvider());
   }
 
   private String getContent(HttpServletRequest request) throws IOException {
@@ -361,6 +295,16 @@
     }
   }
 
+  private Class<Object> getEntityFromRecordAnnotation(
+      Class<? extends Record> record) {
+    DataTransferObject dtoAnn = record.getAnnotation(DataTransferObject.class);
+    if (dtoAnn != null) {
+      return (Class<Object>) dtoAnn.value();
+    }
+    throw new IllegalArgumentException("Record class " + record.getName()
+        + " missing DataTransferObject annotation");
+  }
+
   private Object getEntityInstance(WriteOperation writeOperation,
       Class<?> entity, Object idValue, Class<?> idType)
       throws SecurityException, InstantiationException, IllegalAccessException,
@@ -432,7 +376,7 @@
 
   private RequestDefinition getOperation(String operationName) {
     RequestDefinition operation;
-    operation = config.requestDefinitions().get(operationName);
+    operation = operationRegistry.getOperation(operationName);
     if (null == operation) {
       throw new IllegalArgumentException("Unknown operation " + operationName);
     }
@@ -518,6 +462,21 @@
     return recordObject.get(key);
   }
 
+  private Class<Record> getRecordFromClassToken(String recordToken) {
+    try {
+      Class<?> clazz = Class.forName(recordToken, false,
+          getClass().getClassLoader());
+      if (Record.class.isAssignableFrom(clazz)) {
+        return (Class<Record>) clazz;
+      }
+      throw new SecurityException(
+          "Attempt to access non-record class " + recordToken);
+    } catch (ClassNotFoundException e) {
+      throw new IllegalArgumentException(
+          "Non-existent record class " + recordToken);
+    }
+  }
+
   private JSONObject getReturnRecord(WriteOperation writeOperation,
       Object entityInstance, JSONObject recordObject,
       Set<ConstraintViolation<Object>> violations) throws SecurityException,
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java b/bikeshed/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
new file mode 100644
index 0000000..e95ad31
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Enforces security policy for operations and classes, as well as permitting
+ * request obfuscation.
+ */
+public interface RequestSecurityProvider {
+
+  /**
+   * Throws exception if argument is not accessible via remote requests.
+   */
+  void checkClass(Class<?> clazz) throws SecurityException;
+
+  /**
+   * Optionally decodes a previously encoded operation. Throws exception if
+   * argument is not a legal operation.
+   */
+  String mapOperation(String operationName) throws SecurityException;
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java b/bikeshed/src/com/google/gwt/requestfactory/shared/DataTransferObject.java
similarity index 77%
rename from bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
rename to bikeshed/src/com/google/gwt/requestfactory/shared/DataTransferObject.java
index f0df3c7..21c505e 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/DataTransferObject.java
@@ -21,17 +21,14 @@
 import java.lang.annotation.Target;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
+ * <p> <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * Annotation on Record classes specifying 'type'. 'type' represents the
- * server-side counterpart of the Record.
+ * </span> </p> Annotation on Record classes specifying 'type'. 'type'
+ * represents the server-side counterpart of the Record.
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
-public @interface ServerType {
+public @interface DataTransferObject {
 
-  Class<?> type();
+  Class<?> value();
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 8b81041..518fe47 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -17,12 +17,8 @@
 
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.valuestore.shared.DeltaValueStore;
-import com.google.gwt.valuestore.shared.Record;
 import com.google.gwt.valuestore.shared.ValueStore;
 
-import java.util.Map;
-import java.util.Set;
-
 /**
  * <p>
  * <span style="color:red">Experimental API: This class is still under rapid
@@ -34,18 +30,6 @@
 public interface RequestFactory {
 
   // TODO all these inner interfaces are clutter, move them to their own files
-
-  /**
-   * Implemented by the configuration class used by
-   * {@link com.google.gwt.requestfactory.server.RequestFactoryServlet
-   * RequestFactoryServlet}.
-   */
-  interface Config {
-    Map<String, RequestDefinition> requestDefinitions();
-
-    Set<Class<? extends Record>> recordTypes();
-  }
-
   /**
    * Implemented by enums that define the mapping between request objects and
    * service methods.
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java b/bikeshed/src/com/google/gwt/requestfactory/shared/Service.java
similarity index 71%
copy from bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
copy to bikeshed/src/com/google/gwt/requestfactory/shared/Service.java
index f0df3c7..59f2cf5 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/Service.java
@@ -21,17 +21,12 @@
 import java.lang.annotation.Target;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
- * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * Annotation on Record classes specifying 'type'. 'type' represents the
- * server-side counterpart of the Record.
+ * Annotation on Request classes specifying the server side implementations that
+ * back them.
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
-public @interface ServerType {
+public @interface Service {
 
-  Class<?> type();
+  Class<?> value();
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
index 8a8734b..a7b1a4a 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
@@ -75,7 +75,7 @@
   public void create(String reportId) {
     deltas = requestFactory.getValueStore().spawnDeltaView();
 
-    expense = (ExpenseRecord) deltas.create(ExpenseRecord.TOKEN);
+    expense = (ExpenseRecord) deltas.create(ExpenseRecord.class);
     deltas.set(ExpenseRecord.reportId, expense, reportId);
     displayExpense();
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
index 0207b78..7e48a4f 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
@@ -85,7 +85,7 @@
   public void create(Long reporterId) {
     deltas = requestFactory.getValueStore().spawnDeltaView();
 
-    report = (ReportRecord) deltas.create(ReportRecord.TOKEN);
+    report = (ReportRecord) deltas.create(ReportRecord.class);
     deltas.set(ReportRecord.reporterKey, report, reporterId.toString());
     displayReport();
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
index 55cd630..54e0c17 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
-import com.google.gwt.requestfactory.shared.ServerType;
+import com.google.gwt.requestfactory.shared.DataTransferObject;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
@@ -25,15 +25,9 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
-@ServerType(type = com.google.gwt.sample.expenses.server.domain.Employee.class)
+@DataTransferObject(com.google.gwt.sample.expenses.server.domain.Employee.class)
 public interface EmployeeRecord extends Record {
 
-  /**
-   * Used as input to {@link com.google.gwt.valuestore.shared.DeltaValueStore#create(Record)
-   * DeltaValueStore#create()} and in the wire format during sync requests.
-   */
-  String TOKEN = "EmployeeRecord";
-
   Property<String> userName = new Property<String>("userName", "User Name", String.class);
   Property<String> displayName = new Property<String>("displayName", "Display Name",
       String.class);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
index 65a762a..8952ada 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
@@ -18,7 +18,8 @@
 import com.google.gwt.requestfactory.shared.RecordListRequest;
 import com.google.gwt.requestfactory.shared.RecordRequest;
 import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.requestfactory.shared.ServerOperation;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.sample.expenses.server.domain.Employee;
 import com.google.gwt.valuestore.shared.PropertyReference;
 
 /**
@@ -28,137 +29,39 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
+@Service(Employee.class)
 public interface EmployeeRequest {
 
   /**
-   * Defines the server operations that handle these requests.
-   */
-  public enum ServerOperations implements RequestFactory.RequestDefinition {
-    COUNT_EMPLOYEES {
-      public String getDomainMethodName() {
-        return "countEmployees";
-      }
-
-      public Class<?> getReturnType() {
-        return Long.class;
-      }
-
-      public boolean isReturnTypeList() {
-        return false;
-      }
-    },
-
-    COUNT_EMPLOYEES_BY_DEPARTMENT {
-      public String getDomainMethodName() {
-        return "countEmployeesByDepartment";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[]{java.lang.String.class};
-      }
-
-      public Class<?> getReturnType() {
-        return Long.class;
-      }
-
-      public boolean isReturnTypeList() {
-        return false;
-      }
-    },
-
-    FIND_ALL_EMPLOYEES {
-      public String getDomainMethodName() {
-        return "findAllEmployees";
-      }
-    },
-
-    FIND_EMPLOYEE {
-      public String getDomainMethodName() {
-        return "findEmployee";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {Long.class};
-      }
-
-      public boolean isReturnTypeList() {
-        return false;
-      }
-    },
-
-    FIND_EMPLOYEE_ENTRIES {
-      public String getDomainMethodName() {
-        return "findEmployeeEntries";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[]{int.class, int.class};
-      }
-    },
-
-    FIND_EMPLOYEE_ENTRIES_BY_DEPARTMENT {
-      public String getDomainMethodName() {
-        return "findEmployeeEntriesByDepartment";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[]{java.lang.String.class, int.class, int.class};
-      }
-    };
-
-    public String getDomainClassName() {
-      return "com.google.gwt.sample.expenses.server.domain.Employee";
-    }
-
-    public Class<?>[] getParameterTypes() {
-      return null;
-    }
-
-    public Class<?> getReturnType() {
-      return EmployeeRecord.class;
-    }
-
-    public boolean isReturnTypeList() {
-      return true;
-    }
-  }
-
-  /**
    * @return a request object
    */
-  @ServerOperation("COUNT_EMPLOYEES")
   RequestFactory.RequestObject<Long> countEmployees();
 
   /**
    * @return a request object
    */
-  @ServerOperation("COUNT_EMPLOYEES_BY_DEPARTMENT")
   RequestFactory.RequestObject<Long> countEmployeesByDepartment(
       String department);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_ALL_EMPLOYEES")
   RecordListRequest<EmployeeRecord> findAllEmployees();
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_EMPLOYEE")
   RecordRequest<EmployeeRecord> findEmployee(PropertyReference<String> id);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_EMPLOYEE_ENTRIES")
   RecordListRequest<EmployeeRecord> findEmployeeEntries(int firstResult,
       int maxResults);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_EMPLOYEE_ENTRIES_BY_DEPARTMENT")
   RecordListRequest<EmployeeRecord> findEmployeeEntriesByDepartment(
       String department, int firstResult, int maxResults);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
index 0c83865..e9c2b74 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
-import com.google.gwt.requestfactory.shared.ServerType;
+import com.google.gwt.requestfactory.shared.DataTransferObject;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
@@ -27,15 +27,9 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
-@ServerType(type = com.google.gwt.sample.expenses.server.domain.Expense.class)
+@DataTransferObject(com.google.gwt.sample.expenses.server.domain.Expense.class)
 public interface ExpenseRecord extends Record {
 
-  /**
-   * Used as input to {@link com.google.gwt.valuestore.shared.DeltaValueStore#create(Record)
-   * DeltaValueStore#create()} and in the wire format during sync requests.
-   */
-  String TOKEN = "ExpenseRecord";
-
   Property<Double> amount = new Property<Double>("amount", "Amount", Double.class);
   Property<String> approval = new Property<String>("approval", "Approval", String.class);
   Property<String> category = new Property<String>("category", "Category", String.class);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java
index 4cce650..62216f9 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java
@@ -16,10 +16,9 @@
 package com.google.gwt.sample.expenses.gwt.request;
 
 import com.google.gwt.requestfactory.shared.RecordListRequest;
-import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.requestfactory.shared.ServerOperation;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.sample.expenses.server.domain.Expense;
 import com.google.gwt.valuestore.shared.PropertyReference;
-import com.google.gwt.valuestore.shared.Record;
 
 /**
  * "API Generated" request selector interface implemented by objects that give
@@ -28,72 +27,23 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
+@Service(Expense.class)
 public interface ExpenseRequest {
 
   /**
-   * Defines the server operations that handle these requests.
-   */
-  public enum ServerOperations implements RequestFactory.RequestDefinition {
-    FIND_ALL_EXPENSES {
-      public String getDomainMethodName() {
-        return "findAllExpenses";
-      }
-    },
-
-    FIND_EXPENSE {
-      public String getDomainMethodName() {
-        return "findListOfOneExpense";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.Long.class};
-      }
-    },
-
-    FIND_EXPENSES_BY_REPORT {
-      public String getDomainMethodName() {
-        return "findExpensesByReport";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.Long.class};
-      }
-    };
-
-    public String getDomainClassName() {
-      return "com.google.gwt.sample.expenses.server.domain.Expense";
-    }
-
-    public Class<?>[] getParameterTypes() {
-      return null;
-    }
-
-    public Class<? extends Record> getReturnType() {
-      return ExpenseRecord.class;
-    }
-    
-    public boolean isReturnTypeList() {
-      return true;
-    }
-  }
-
-  /**
    * @return a request object
    */
-  @ServerOperation("FIND_ALL_EXPENSES")
   RecordListRequest<ExpenseRecord> findAllExpenses();
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_EXPENSE")
   RecordListRequest<ExpenseRecord> findExpense(
       PropertyReference<String> id);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_EXPENSES_BY_REPORT")
   RecordListRequest<ExpenseRecord> findExpensesByReport(
       PropertyReference<String> reportId);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
deleted file mode 100644
index ab85505..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.gwt.request;
-
-import com.google.gwt.requestfactory.shared.RequestFactory.Config;
-import com.google.gwt.requestfactory.shared.RequestFactory.RequestDefinition;
-import com.google.gwt.valuestore.shared.Record;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * "API Generated" configuration class for
- * {@link com.google.gwt.requestfactory.server.RequestFactoryServlet
- * RequestFactoryServlet}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
- */
-public class ExpensesServerSideOperations implements Config {
-
-  private static void putAll(RequestDefinition[] values,
-      Map<String, RequestDefinition> newMap) {
-    for (RequestDefinition def : values) {
-      newMap.put(def.name(), def);
-    }
-  }
-
-  private final Map<String, RequestDefinition> map;
-
-  public ExpensesServerSideOperations() {
-    Map<String, RequestDefinition> newMap = new HashMap<String, RequestDefinition>();
-    putAll(EmployeeRequest.ServerOperations.values(), newMap);
-    putAll(ReportRequest.ServerOperations.values(), newMap);
-    putAll(ExpenseRequest.ServerOperations.values(), newMap);
-    map = Collections.unmodifiableMap(newMap);
-  }
-
-  public Set<Class<? extends Record>> recordTypes() {
-    Set<Class<? extends Record>> records = new HashSet<Class<? extends Record>>();
-    records.add(EmployeeRecord.class);
-    records.add(ReportRecord.class);
-    records.add(ExpenseRecord.class);
-    return records;
-  }
-
-  public Map<String, RequestDefinition> requestDefinitions() {
-    return map;
-  }
-
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
index 7fce0cf..d93ed30 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
-import com.google.gwt.requestfactory.shared.ServerType;
+import com.google.gwt.requestfactory.shared.DataTransferObject;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
@@ -27,15 +27,9 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
-@ServerType(type = com.google.gwt.sample.expenses.server.domain.Report.class)
+@DataTransferObject(com.google.gwt.sample.expenses.server.domain.Report.class)
 public interface ReportRecord extends Record {
 
-  /**
-   * Used as input to {@link com.google.gwt.valuestore.shared.DeltaValueStore#create(Record)
-   * DeltaValueStore#create()} and in the wire format during sync requests.
-   */
-  String TOKEN = "ReportRecord";
-
   Property<String> approvedSupervisorKey = new Property<String>("approvedSupervisorKey", "Approved Supervisor Key",
       String.class);
   Property<Date> created = new Property<Date>("created", "Created", Date.class);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
index 77fb2c1..2a807b1 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
@@ -18,7 +18,8 @@
 import com.google.gwt.requestfactory.shared.RecordListRequest;
 import com.google.gwt.requestfactory.shared.RecordRequest;
 import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.requestfactory.shared.ServerOperation;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.sample.expenses.server.domain.Report;
 import com.google.gwt.valuestore.shared.PropertyReference;
 
 /**
@@ -28,150 +29,38 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
+@Service(Report.class)
 public interface ReportRequest {
 
   /**
-   * Defines the server operations that handle these requests.
-   */
-  public enum ServerOperations implements RequestFactory.RequestDefinition {
-    COUNT_REPORTS {
-      public String getDomainMethodName() {
-        return "countReports";
-      }
-
-      public Class<?> getReturnType() {
-        return long.class;
-      }
-
-      public boolean isReturnTypeList() {
-        return false;
-      }
-    },
-
-    COUNT_REPORTS_BY_SEARCH {
-      public String getDomainMethodName() {
-        return "countReportsBySearch";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.Long.class, java.lang.String.class,
-            java.lang.String.class};
-      }
-
-      public Class<?> getReturnType() {
-        return long.class;
-      }
-
-      public boolean isReturnTypeList() {
-        return false;
-      }
-    },
-
-    FIND_ALL_REPORTS {
-      public String getDomainMethodName() {
-        return "findAllReports";
-      }
-    },
-
-    FIND_REPORT {
-      public String getDomainMethodName() {
-        return "findReport";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.Long.class};
-      }
-
-      public boolean isReturnTypeList() {
-        return false;
-      }
-    },
-
-    FIND_REPORTS_BY_EMPLOYEE {
-      public String getDomainMethodName() {
-        return "findReportsByEmployee";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.Long.class};
-      }
-    },
-
-    FIND_REPORT_ENTRIES {
-      public String getDomainMethodName() {
-        return "findReportEntries";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[] {int.class, int.class};
-      }
-    },
-
-    FIND_REPORT_ENTRIES_BY_SEARCH {
-      public String getDomainMethodName() {
-        return "findReportEntriesBySearch";
-      }
-
-      public Class<?>[] getParameterTypes() {
-        return new Class[]{
-            java.lang.Long.class, java.lang.String.class,
-            java.lang.String.class, java.lang.String.class, int.class,
-            int.class};
-      }
-    };
-
-    public String getDomainClassName() {
-      return "com.google.gwt.sample.expenses.server.domain.Report";
-    }
-
-    public Class<?>[] getParameterTypes() {
-      return null;
-    }
-
-    public Class<?> getReturnType() {
-      return ReportRecord.class;
-    }
-
-    public boolean isReturnTypeList() {
-      return true;
-    }
-  }
-
-  /**
    * @return a request object
    */
-  @ServerOperation("COUNT_REPORTS")
   RequestFactory.RequestObject<Long> countReports();
 
   /**
    * @return a request object
    */
-  @ServerOperation("COUNT_REPORTS_BY_SEARCH")
   RequestFactory.RequestObject<Long> countReportsBySearch(Long employeeId,
       String department, String startsWith);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_ALL_REPORTS")
   RecordListRequest<ReportRecord> findAllReports();
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_REPORT")
   RecordRequest<ReportRecord> findReport(PropertyReference<String> id);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_REPORT_ENTRIES")
   RecordListRequest<ReportRecord> findReportEntries(int firstResult, int maxResults);
 
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_REPORT_ENTRIES_BY_SEARCH")
   RecordListRequest<ReportRecord> findReportEntriesBySearch(Long employeeId,
       String department, String startsWith, String orderBy, int firstResult,
       int maxResults);
@@ -179,7 +68,6 @@
   /**
    * @return a request object
    */
-  @ServerOperation("FIND_REPORTS_BY_EMPLOYEE")
   RecordListRequest<ReportRecord> findReportsByEmployee(
       PropertyReference<String> id);
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
index 509ca40..16a59b5 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
@@ -75,7 +75,7 @@
   }
 
   @Override
-  protected String getRecordToken() {
-    return EmployeeRecord.TOKEN;
+  protected Class getRecordClass() {
+    return EmployeeRecord.class;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
index 9fa5505..0898b19 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
@@ -74,7 +74,7 @@
   }
   
   @Override
-  protected String getRecordToken() {
-    return ReportRecord.TOKEN;
+  protected Class getRecordClass() {
+    return ReportRecord.class;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java b/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java
index 30c6af8..4ab7e84 100644
--- a/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java
+++ b/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java
@@ -260,7 +260,7 @@
     return syncResults;
   }
 
-  public Record create(String token) {
+  public Record create(Class token) {
     if (used) {
       throw new IllegalStateException(
           "create can only be called on an un-used DeltaValueStore");
@@ -456,7 +456,7 @@
       } else {
         requestData.append(",");
       }
-      requestData.append("{\"" + entry.getValue().getSchema().getToken()
+      requestData.append("{\"" + entry.getValue().getSchema().getToken().getName()
           + "\":");
       if (writeOperation != WriteOperation.DELETE) {
         requestData.append(impl.toJson());
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java b/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java
index d7df718..65d6f01 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java
@@ -31,7 +31,7 @@
    */
   void clearUsed();
 
-  Record create(String token);
+  Record create(Class token);
 
   void delete(Record record);
 
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordSchema.java b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordSchema.java
index 2f9d8c0..24f12e6 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordSchema.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordSchema.java
@@ -62,6 +62,6 @@
     return createChangeEvent(record, writeOperation);
   }
 
-  public abstract String getToken();
+  public abstract Class<? extends Record> getToken();
 
 }
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordToTypeMap.java b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordToTypeMap.java
index 09b0c1d..46b5725 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordToTypeMap.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordToTypeMap.java
@@ -27,5 +27,5 @@
  * Record class to its internal "type" representation.
  */
 public interface RecordToTypeMap {
-  RecordSchema<? extends Record> getType(String token);
+  RecordSchema<? extends Record> getType(Class<? extends Record> recordClass);
 }
diff --git a/bikeshed/test/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistryTest.java b/bikeshed/test/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistryTest.java
new file mode 100644
index 0000000..2b5577c
--- /dev/null
+++ b/bikeshed/test/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistryTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.RequestFactory;
+import com.google.gwt.valuestore.shared.SimpleFooRecord;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for
+ * {@link com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistry}
+ * .
+ */
+public class ReflectionBasedOperationRegistryTest extends TestCase {
+
+  private ReflectionBasedOperationRegistry registry;
+
+  public void setUp() {
+    registry = new ReflectionBasedOperationRegistry(
+        new DefaultSecurityProvider());
+  }
+
+  public void testGetOperationListNoArgs() {
+    RequestFactory.RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.server.SimpleFooRequest::findAll");
+    assert request != null;
+    assertEquals("com.google.gwt.requestfactory.server.SimpleFoo",
+        request.getDomainClassName());
+    assertEquals("findAll", request.getDomainMethodName());
+    assertEquals(SimpleFooRecord.class, request.getReturnType());
+    assertEquals(0, request.getParameterTypes().length);
+    assertEquals(true, request.isReturnTypeList());
+  }
+
+  public void testGetOperationScalarNoArgs() {
+    RequestFactory.RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.server.SimpleFooRequest::countSimpleFoo");
+    assert request != null;
+    assertEquals("com.google.gwt.requestfactory.server.SimpleFoo",
+        request.getDomainClassName());
+    assertEquals("countSimpleFoo", request.getDomainMethodName());
+    assertEquals(Long.class, request.getReturnType());
+    assertEquals(0, request.getParameterTypes().length);
+    assertEquals(false, request.isReturnTypeList());
+  }
+
+  public void testGetOpertionScalarWithArgs() {
+    RequestFactory.RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.server.SimpleFooRequest::findSimpleFooById");
+    assert request != null;
+    assertEquals("com.google.gwt.requestfactory.server.SimpleFoo",
+        request.getDomainClassName());
+    assertEquals("findSimpleFooById", request.getDomainMethodName());
+    assertEquals(SimpleFooRecord.class, request.getReturnType());
+    assert request.getParameterTypes().length == 1
+        && request.getParameterTypes()[0] == Long.class;
+    assertEquals(false, request.isReturnTypeList());
+  }
+
+  public void testInsecureOperations() {
+    try {
+      // bogus class
+      registry.getOperation("com.foo.Foo::bar");
+      fail("Access to non-existent class.");
+    } catch (SecurityException se) {
+      // expected
+    }
+    try {
+      // no @Service
+      registry.getOperation("java.lang.System::currentTimeMillis");
+      fail("Access allowed to class without @Service annotation");
+    } catch (SecurityException se) {
+      // expected
+    }
+  }
+
+  public void testPrivateMethodFails() {
+    RequestFactory.RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.server.SimpleFooRequest::privateMethod");
+    assert request == null;
+  }
+}
diff --git a/bikeshed/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/bikeshed/test/com/google/gwt/requestfactory/server/SimpleFoo.java
new file mode 100644
index 0000000..7a6c504
--- /dev/null
+++ b/bikeshed/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -0,0 +1,40 @@
+/*
+ * 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 java.util.List;
+
+/**
+ * Domain object for SimpleFooRequest.
+ */
+public class SimpleFoo {
+  public static Long countSimpleFoo() {
+    return 0L;
+  }
+
+  public static List<SimpleFoo> findAll() {
+    return null;
+  }
+
+  public static SimpleFoo findSimpleFooById(Long id) {
+    return null;
+  }
+
+  @SuppressWarnings("unused")
+  private static Integer privateMethod() {
+    return 0;
+  }
+}
diff --git a/bikeshed/test/com/google/gwt/requestfactory/server/SimpleFooRequest.java b/bikeshed/test/com/google/gwt/requestfactory/server/SimpleFooRequest.java
new file mode 100644
index 0000000..87300b2
--- /dev/null
+++ b/bikeshed/test/com/google/gwt/requestfactory/server/SimpleFooRequest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.server;
+
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.valuestore.shared.SimpleFooRecord;
+
+/**
+ * Do nothing test interface.
+ */
+@Service(com.google.gwt.requestfactory.server.SimpleFoo.class)
+public interface SimpleFooRequest {
+  RequestFactory.RequestObject<Long> countSimpleFoo();
+  RecordListRequest<SimpleFooRecord> findAll();
+  RequestFactory.RequestObject<SimpleFooRecord> findSimpleFooById(Long id);
+  RequestFactory.RequestObject<Integer> privateMethod();
+}
diff --git a/bikeshed/test/com/google/gwt/valuestore/client/DeltaValueStoreJsonImplTest.java b/bikeshed/test/com/google/gwt/valuestore/client/DeltaValueStoreJsonImplTest.java
index e766120..651c447 100644
--- a/bikeshed/test/com/google/gwt/valuestore/client/DeltaValueStoreJsonImplTest.java
+++ b/bikeshed/test/com/google/gwt/valuestore/client/DeltaValueStoreJsonImplTest.java
@@ -58,11 +58,12 @@
   @Override
   public void gwtSetUp() {
     valueStore = new ValueStoreJsonImpl(null, new RecordToTypeMap() {
-      public RecordSchema<? extends Record> getType(String token) {
-        if (token.equals(SimpleFooRecordImpl.TOKEN)) {
+      public RecordSchema<? extends Record> getType(Class<? extends Record>
+          recordClass) {
+        if (recordClass.equals(SimpleFooRecord.class)) {
           return SimpleFooRecordImpl.SCHEMA;
         }
-        throw new IllegalArgumentException("Unknown token " + token);
+        throw new IllegalArgumentException("Unknown token " + recordClass);
       }
 
     });
@@ -81,7 +82,7 @@
 
   public void testCreate() {
     DeltaValueStoreJsonImpl deltaValueStore = valueStore.spawnDeltaView();
-    Record created = deltaValueStore.create(SimpleFooRecordImpl.TOKEN);
+    Record created = deltaValueStore.create(SimpleFooRecord.class);
     assertNotNull(created.getId());
     assertNotNull(created.getVersion());
 
@@ -91,7 +92,7 @@
 
   public void testCreateDelete() {
     DeltaValueStoreJsonImpl deltaValueStore = valueStore.spawnDeltaView();
-    Record created = deltaValueStore.create(SimpleFooRecordImpl.TOKEN);
+    Record created = deltaValueStore.create(SimpleFooRecord.class);
     assertTrue(deltaValueStore.isChanged());
     deltaValueStore.delete(created);
     assertFalse(deltaValueStore.isChanged());
@@ -102,7 +103,7 @@
 
   public void testCreateUpdate() {
     DeltaValueStoreJsonImpl deltaValueStore = valueStore.spawnDeltaView();
-    Record created = deltaValueStore.create(SimpleFooRecordImpl.TOKEN);
+    Record created = deltaValueStore.create(SimpleFooRecord.class);
     assertTrue(deltaValueStore.isChanged());
     deltaValueStore.set(SimpleFooRecord.userName, created, "harry");
     assertTrue(deltaValueStore.isChanged());
@@ -202,9 +203,9 @@
 
     JSONObject recordWithName = writeOperationArray.get(0).isObject();
     assertEquals(1, recordWithName.size());
-    assertTrue(recordWithName.containsKey(SimpleFooRecordImpl.TOKEN));
+    assertTrue(recordWithName.containsKey(SimpleFooRecord.class.getName()));
 
-    JSONObject record = recordWithName.get(SimpleFooRecordImpl.TOKEN).isObject();
+    JSONObject record = recordWithName.get(SimpleFooRecord.class.getName()).isObject();
     assertTrue(record.containsKey("id"));
     assertTrue(record.containsKey("version"));
 
diff --git a/bikeshed/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java b/bikeshed/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
index c9b459d..0061fc0 100644
--- a/bikeshed/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
+++ b/bikeshed/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
@@ -15,12 +15,16 @@
  */
 package com.google.gwt.valuestore.shared;
 
+import com.google.gwt.requestfactory.server.SimpleFoo;
+import com.google.gwt.requestfactory.shared.DataTransferObject;
+
 import java.util.Date;
 
 /**
  * A simple entity used for testing. Has an int field and date field. Add other
  * data types as their support gets built in.
  */
+@DataTransferObject(SimpleFoo.class)
 public interface SimpleFooRecord extends Record {
 
   String TOKEN = "SimpleFooRecord";
diff --git a/bikeshed/test/com/google/gwt/valuestore/shared/impl/SimpleFooRecordImpl.java b/bikeshed/test/com/google/gwt/valuestore/shared/impl/SimpleFooRecordImpl.java
index 122b485..342e4ac 100644
--- a/bikeshed/test/com/google/gwt/valuestore/shared/impl/SimpleFooRecordImpl.java
+++ b/bikeshed/test/com/google/gwt/valuestore/shared/impl/SimpleFooRecordImpl.java
@@ -64,8 +64,8 @@
       return null;
     }
 
-    public String getToken() {
-      return SimpleFooRecord.TOKEN; // special field
+    public Class<? extends Record> getToken() {
+      return SimpleFooRecord.class; // special field
     }
   }
 
diff --git a/bikeshed/war/WEB-INF/web.xml b/bikeshed/war/WEB-INF/web.xml
index 572ccb8..71aea7d 100644
--- a/bikeshed/war/WEB-INF/web.xml
+++ b/bikeshed/war/WEB-INF/web.xml
@@ -5,16 +5,10 @@
 
 <web-app>
 
-  <!--  Configures expensesData servlet -->
-  <context-param>
-    <param-name>servlet.serverOperation</param-name>
-    <param-value>com.google.gwt.sample.expenses.gwt.request.ExpensesServerSideOperations</param-value>
-  </context-param>
-
   <!-- Servlets -->
   <servlet>
     <servlet-name>requestFactoryServlet</servlet-name>
-    <servlet-class> com.google.gwt.requestfactory.server.RequestFactoryServlet</servlet-class>
+    <servlet-class>com.google.gwt.requestfactory.server.RequestFactoryServlet</servlet-class>
   </servlet>
 
   <servlet>