Overhaul the RequestFactory server code.
Make RequestFactory usable from non-GWT code.
http://code.google.com/p/google-web-toolkit/wiki/RequestFactory_2_1_1

Move AutoBeans to a top-level package and add AutoBeanCodex.
Add explicit support for Collections and Maps to AutoBeanVisitor.
Add support to AutoBean and AutoBeanCodex for Map properties.
Add @PropertyName annotation to decrease the size of the RF payload.
http://code.google.com/p/google-web-toolkit/wiki/AutoBean

Patch by: bobv
Review by: rjrjr, rchandia

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9189 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/Console.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/Console.java
new file mode 100644
index 0000000..0db3125
--- /dev/null
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/Console.java
@@ -0,0 +1,84 @@
+/*
+ * 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.dynatablerf.console;
+
+import com.google.gwt.event.shared.SimpleEventBus;
+import com.google.gwt.requestfactory.server.testing.RequestFactoryMagic;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.dynatablerf.shared.AddressProxy;
+import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
+import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A proof-of-concept to demonstrate how RequestFactory can be used from
+ * non-client code.
+ */
+public class Console {
+  public static void main(String[] args) {
+    String url = "http://localhost:8888/gwtRequest";
+    if (args.length == 1) {
+      url = args[0];
+    }
+    try {
+      new Console(new URI(url)).exec();
+      System.exit(0);
+    } catch (URISyntaxException e) {
+      System.err.println("Could not parse argument");
+    }
+    System.exit(1);
+  }
+
+  private final DynaTableRequestFactory rf;
+
+  private Console(URI uri) {
+    /*
+     * Instantiation of the RequestFactory interface uses the
+     * RequestFactoryMagic class instead of GWT.create().
+     */
+    this.rf = RequestFactoryMagic.create(DynaTableRequestFactory.class);
+    // Initialization follows the same pattern as client code
+    rf.initialize(new SimpleEventBus(), new HttpClientTransport(uri));
+  }
+
+  /**
+   * Making a request from non-GWT code is similar. The implementation of the
+   * demonstration HttpClientTransport issues the requests synchronously. A
+   * different transport system might use asynchronous callbacks.
+   */
+  private void exec() {
+    rf.schoolCalendarRequest().getPeople(0, 100,
+        Arrays.asList(true, true, true, true, true, true, true)).with("address").fire(
+        new Receiver<List<PersonProxy>>() {
+          @Override
+          public void onSuccess(List<PersonProxy> response) {
+            // Print each record to the console
+            for (PersonProxy person : response) {
+              AddressProxy address = person.getAddress();
+              String addressBlob = address.getStreet() + " "
+                  + address.getCity() + " " + address.getState() + " "
+                  + address.getZip();
+              System.out.printf("%-40s%40s\n%80s\n\n", person.getName(),
+                  person.getDescription(), addressBlob);
+            }
+          }
+        });
+  }
+}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
new file mode 100644
index 0000000..4325979
--- /dev/null
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
@@ -0,0 +1,70 @@
+/*
+ * 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.dynatablerf.console;
+
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.requestfactory.shared.RequestTransport;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+
+/**
+ * This is a simple implementation of {@link RequestTransport} that uses
+ * HttpClient. It is not suitable for production use, but demonstrates the
+ * minimum functionality necessary to implement a custom RequestTransport.
+ */
+class HttpClientTransport implements RequestTransport {
+  private final URI uri;
+
+  public HttpClientTransport(URI uri) {
+    this.uri = uri;
+  }
+
+  @Override
+  public void send(String payload, TransportReceiver receiver) {
+    HttpClient client = new DefaultHttpClient();
+    HttpPost post = new HttpPost();
+    post.setHeader("Content-Type", "application/json;charset=UTF-8");
+    post.setURI(uri);
+    Throwable ex;
+    try {
+      post.setEntity(new StringEntity(payload, "UTF-8"));
+      HttpResponse response = client.execute(post);
+      if (200 == response.getStatusLine().getStatusCode()) {
+        String contents = Util.readStreamAsString(response.getEntity().getContent());
+        receiver.onTransportSuccess(contents);
+      } else {
+        receiver.onTransportFailure(response.getStatusLine().getReasonPhrase());
+      }
+      return;
+    } catch (UnsupportedEncodingException e) {
+      ex = e;
+    } catch (ClientProtocolException e) {
+      ex = e;
+    } catch (IOException e) {
+      ex = e;
+    }
+    receiver.onTransportFailure(ex.getMessage());
+  }
+}
\ No newline at end of file
diff --git a/tools/api-checker/config/gwt20_21userApi.conf b/tools/api-checker/config/gwt20_21userApi.conf
index a384fed..697b3cc 100644
--- a/tools/api-checker/config/gwt20_21userApi.conf
+++ b/tools/api-checker/config/gwt20_21userApi.conf
@@ -66,6 +66,7 @@
 :**/server/**\
 :**/tools/**\
 :user/src/com/google/gwt/regexp/shared/**\
+:user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java\
 :user/src/com/google/gwt/core/client/impl/WeakMapping.java\
 :user/src/com/google/gwt/junit/*.java\
 :user/src/com/google/gwt/junit/client/GWTTestCase.java\
@@ -75,6 +76,7 @@
 :user/src/com/google/gwt/resources/css\
 :user/src/com/google/gwt/resources/ext\
 :user/src/com/google/gwt/resources/rg\
+:user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
 :user/src/com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
 :user/src/com/google/gwt/rpc/client/impl/EscapeUtil.java\
 :user/src/com/google/gwt/rpc/linker\
diff --git a/user/src/com/google/gwt/autobean/AutoBean.gwt.xml b/user/src/com/google/gwt/autobean/AutoBean.gwt.xml
new file mode 100644
index 0000000..cc23165
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/AutoBean.gwt.xml
@@ -0,0 +1,24 @@
+<?xml version="1.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.
+-->
+
+<!-- AutoBean framework -->
+<module>
+  <inherits name="com.google.gwt.core.Core" />
+  <inherits name="com.google.gwt.user.User" />
+  <source path="client" />
+  <source path="shared" />
+  <super-source path="super" />
+  <generate-with class="com.google.gwt.autobean.rebind.AutoBeanFactoryGenerator">
+    <when-type-assignable class="com.google.gwt.autobean.shared.AutoBeanFactory" />
+  </generate-with>
+</module>
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
similarity index 90%
rename from user/src/com/google/gwt/editor/client/impl/AbstractAutoBeanFactory.java
rename to user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
index 695efd4..a04e5f6 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractAutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
@@ -13,10 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.client.impl;
+package com.google.gwt.autobean.client.impl;
 
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
new file mode 100644
index 0000000..f3a0bb2
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -0,0 +1,137 @@
+/*
+ * 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.autobean.client.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implements the EntityCodex.Splittable interface
+ */
+public final class JsoSplittable extends JavaScriptObject implements Splittable {
+  /**
+   * This type is only used in DevMode because we can't treat Strings as JSOs.
+   */
+  public static class StringSplittable implements Splittable {
+    private final String value;
+
+    public StringSplittable(String value) {
+      this.value = value;
+    }
+
+    public String asString() {
+      return value;
+    }
+
+    public Splittable get(int index) {
+      throw new UnsupportedOperationException();
+    }
+
+    public Splittable get(String key) {
+      throw new UnsupportedOperationException();
+    }
+
+    public String getPayload() {
+      return StringQuoter.quote(value);
+    }
+
+    public List<String> getPropertyKeys() {
+      return Collections.emptyList();
+    }
+
+    public boolean isNull(int index) {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean isNull(String key) {
+      throw new UnsupportedOperationException();
+    }
+
+    public int size() {
+      return 0;
+    }
+  }
+
+  public static Splittable create(Object object) {
+    if (object instanceof String) {
+      return new StringSplittable((String) object);
+    }
+    return create0(object);
+  }
+
+  private static native Splittable create0(Object object) /*-{
+    return object;
+  }-*/;
+
+  protected JsoSplittable() {
+  }
+
+  public native String asString() /*-{
+    return String(this);
+  }-*/;
+
+  public Splittable get(int index) {
+    return create(get0(index));
+  }
+
+  public Splittable get(String key) {
+    return create(get0(key));
+  }
+
+  public String getPayload() {
+    throw new UnsupportedOperationException(
+        "Cannot convert JsoSplittable to payload");
+  }
+
+  public List<String> getPropertyKeys() {
+    List<String> toReturn = new ArrayList<String>();
+    getPropertyKeys0(toReturn);
+    return Collections.unmodifiableList(toReturn);
+  }
+
+  public native boolean isNull(int index) /*-{
+    return this[index] == null;
+  }-*/;
+
+  public native boolean isNull(String key) /*-{
+    return this[key] == null;
+  }-*/;
+
+  public native int size() /*-{
+    return this.length;
+  }-*/;
+
+  private native Object get0(int index) /*-{
+    return Object(this[index]);
+  }-*/;
+
+  private native Object get0(String key) /*-{
+    return Object(this[key]);
+  }-*/;
+
+  private native void getPropertyKeys0(List<String> list) /*-{
+    for (key in this) {
+      if (this.hasOwnProperty(key)) {
+        list.@java.util.List::add(Ljava/lang/Object;)(key);
+      }
+    }
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/editor/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
similarity index 85%
rename from user/src/com/google/gwt/editor/rebind/AutoBeanFactoryGenerator.java
rename to user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index 0c0dae5..febc68d 100644
--- a/user/src/com/google/gwt/editor/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -13,8 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.rebind;
+package com.google.gwt.autobean.rebind;
 
+import com.google.gwt.autobean.client.impl.AbstractAutoBeanFactory;
+import com.google.gwt.autobean.rebind.model.AutoBeanFactoryMethod;
+import com.google.gwt.autobean.rebind.model.AutoBeanFactoryModel;
+import com.google.gwt.autobean.rebind.model.AutoBeanMethod;
+import com.google.gwt.autobean.rebind.model.AutoBeanMethod.Action;
+import com.google.gwt.autobean.rebind.model.AutoBeanType;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.PropertyContext;
+import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
+import com.google.gwt.autobean.shared.impl.AbstractAutoBean.OneShotContext;
 import com.google.gwt.core.client.impl.WeakMapping;
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
@@ -26,18 +40,6 @@
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanUtils;
-import com.google.gwt.editor.client.AutoBeanVisitor;
-import com.google.gwt.editor.client.AutoBeanVisitor.PropertyContext;
-import com.google.gwt.editor.client.impl.AbstractAutoBean;
-import com.google.gwt.editor.client.impl.AbstractAutoBean.OneShotContext;
-import com.google.gwt.editor.client.impl.AbstractAutoBeanFactory;
-import com.google.gwt.editor.rebind.model.AutoBeanFactoryMethod;
-import com.google.gwt.editor.rebind.model.AutoBeanFactoryModel;
-import com.google.gwt.editor.rebind.model.AutoBeanMethod;
-import com.google.gwt.editor.rebind.model.AutoBeanMethod.Action;
-import com.google.gwt.editor.rebind.model.AutoBeanType;
 import com.google.gwt.editor.rebind.model.ModelUtils;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -183,6 +185,10 @@
     sw.indentln("return new %s(this, deep);", type.getSimpleSourceName());
     sw.println("}");
 
+    // public Class<Intf> getType() {return Intf.class;}
+    sw.println("public Class<%1$s> getType() {return %1$s.class;}",
+        type.getPeerType().getQualifiedSourceName());
+
     if (type.isSimpleBean()) {
       writeCreateSimpleBean(sw, type);
     }
@@ -501,24 +507,55 @@
         }
       }
 
+      // The type of property influences the visitation
+      String valueExpression = String.format(
+          "%1$s value = (%1$s) %2$s.getAutoBean(as().%3$s());",
+          AbstractAutoBean.class.getCanonicalName(),
+          AutoBeanUtils.class.getCanonicalName(), method.getMethod().getName());
+      String visitMethod;
+      Class<?> propertyContextType;
+      if (method.isCollection()) {
+        propertyContextType = CollectionPropertyContext.class;
+        visitMethod = "Collection";
+      } else if (method.isMap()) {
+        propertyContextType = MapPropertyContext.class;
+        visitMethod = "Map";
+      } else if (method.isValueType()) {
+        propertyContextType = PropertyContext.class;
+        valueExpression = String.format("Object value = as().%s();",
+            method.getMethod().getName());
+        visitMethod = "Value";
+      } else {
+        visitMethod = "Reference";
+        propertyContextType = PropertyContext.class;
+      }
+
       // Make the PropertyContext that lets us call the setter
       String propertyContextName = method.getPropertyName() + "PropertyContext";
       sw.println("class %s implements %s {", propertyContextName,
-          PropertyContext.class.getCanonicalName());
+          propertyContextType.getCanonicalName());
       sw.indent();
       sw.println("public boolean canSet() { return %s; }", type.isSimpleBean()
           || setter != null);
-      // Will return the collection's element type or null if not a collection
-      sw.println("public Class<?> getElementType() { return %s; }",
-          method.isCollection()
-              ? (method.getElementType().getQualifiedSourceName() + ".class")
-              : "null");
+      if (method.isCollection()) {
+        // Will return the collection's element type or null if not a collection
+        sw.println("public Class<?> getElementType() { return %s; }",
+            method.getElementType().getQualifiedSourceName() + ".class");
+      } else if (method.isMap()) {
+        // Will return the map's value type
+        sw.println("public Class<?> getValueType() { return %s; }",
+            method.getValueType().getQualifiedSourceName() + ".class");
+        // Will return the map's key type
+        sw.println("public Class<?> getKeyType() { return %s; }",
+            method.getKeyType().getQualifiedSourceName() + ".class");
+      }
       // Return the property type
       sw.println("public Class<?> getType() { return %s.class; }",
           method.getMethod().getReturnType().getQualifiedSourceName());
       sw.println("public void set(Object obj) { ");
       if (setter != null) {
         // Prefer the setter if one exists
+        // as().setFoo((Foo) obj);
         sw.indentln(
             "as().%s((%s) obj);",
             setter.getMethod().getName(),
@@ -532,36 +569,25 @@
       sw.println("}");
       sw.outdent();
       sw.println("}");
+
+      sw.print("{");
+      sw.indent();
       sw.println("%1$s %1$s = new %1$s();", propertyContextName);
 
-      if (method.isValueType()) {
-        // visitor.visitValueProperty("foo", as().getFoo(), ctx);
-        sw.println("visitor.visitValueProperty(\"%s\", as().%s(), %s);",
-            method.getPropertyName(), method.getMethod().getName(),
-            propertyContextName);
-        // visitor.endVisitValueProperty("foo", as().getFoo(), ctx);
-        sw.println("visitor.endVisitValueProperty(\"%s\", as().%s(), %s);",
-            method.getPropertyName(), method.getMethod().getName(),
-            propertyContextName);
-      } else {
-        sw.println("{");
-        sw.indent();
-        // AbstractAutoBean auto=(cast)AutoBeanUtils.getAutoBean(as().getFoo());
-        sw.println("%1$s auto = (%1$s) %2$s.getAutoBean(as().%3$s());",
-            AbstractAutoBean.class.getCanonicalName(),
-            AutoBeanUtils.class.getCanonicalName(),
-            method.getMethod().getName());
-        // if (visitor.visitReferenceProperty("foo", auto, ctx))
-        sw.println("if (visitor.visitReferenceProperty(\"%s\", auto, %s))",
-            method.getPropertyName(), propertyContextName);
+      // Call the visit methods
+      sw.println(valueExpression);
+      // if (visitor.visitReferenceProperty("foo", value, ctx))
+      sw.println("if (visitor.visit%sProperty(\"%s\", value, %s))",
+          visitMethod, method.getPropertyName(), propertyContextName);
+      if (!method.isValueType()) {
         // Cycle-detection in AbstractAutoBean.traverse
-        sw.indentln("if (auto != null) { auto.traverse(visitor, ctx); }");
-        // visitor.endVisitorReferenceProperty("foo", auto' ctx);
-        sw.println("visitor.endVisitReferenceProperty(\"%s\", auto, %s);",
-            method.getPropertyName(), propertyContextName);
-        sw.outdent();
-        sw.println("}");
+        sw.indentln("if (value != null) { value.traverse(visitor, ctx); }");
       }
+      // visitor.endVisitorReferenceProperty("foo", value, ctx);
+      sw.println("visitor.endVisit%sProperty(\"%s\", value, %s);", visitMethod,
+          method.getPropertyName(), propertyContextName);
+      sw.outdent();
+      sw.print("}");
     }
     sw.outdent();
     sw.println("}");
diff --git a/user/src/com/google/gwt/editor/rebind/model/AutoBeanFactoryMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
similarity index 97%
rename from user/src/com/google/gwt/editor/rebind/model/AutoBeanFactoryMethod.java
rename to user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
index 1584d0b..ee9cdae 100644
--- a/user/src/com/google/gwt/editor/rebind/model/AutoBeanFactoryMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.rebind.model;
+package com.google.gwt.autobean.rebind.model;
 
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
diff --git a/user/src/com/google/gwt/editor/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
similarity index 93%
rename from user/src/com/google/gwt/editor/rebind/model/AutoBeanFactoryModel.java
rename to user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
index c8d6646..55c727c 100644
--- a/user/src/com/google/gwt/editor/rebind/model/AutoBeanFactoryModel.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
@@ -13,8 +13,13 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.rebind.model;
+package com.google.gwt.autobean.rebind.model;
 
+import com.google.gwt.autobean.rebind.model.AutoBeanMethod.Action;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
+import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
@@ -24,11 +29,7 @@
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanFactory;
-import com.google.gwt.editor.client.AutoBeanFactory.Category;
-import com.google.gwt.editor.client.AutoBeanFactory.NoWrap;
-import com.google.gwt.editor.rebind.model.AutoBeanMethod.Action;
+import com.google.gwt.editor.rebind.model.ModelUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -75,9 +76,10 @@
      * so we'll extract them here.
      */
     JClassType objectType = oracle.getJavaLangObject();
-    objectMethods = Arrays.asList(objectType.findMethod("equals",
-        new JType[] {objectType}), objectType.findMethod("hashCode",
-        EMPTY_JTYPE), objectType.findMethod("toString", EMPTY_JTYPE));
+    objectMethods = Arrays.asList(
+        objectType.findMethod("equals", new JType[]{objectType}),
+        objectType.findMethod("hashCode", EMPTY_JTYPE),
+        objectType.findMethod("toString", EMPTY_JTYPE));
 
     // Process annotations
     {
@@ -90,12 +92,11 @@
         categoryTypes = null;
       }
 
+      noWrapTypes = new ArrayList<JClassType>();
+      noWrapTypes.add(oracle.findType(AutoBean.class.getCanonicalName()));
       NoWrap noWrapAnnotation = factoryType.getAnnotation(NoWrap.class);
       if (noWrapAnnotation != null) {
-        noWrapTypes = new ArrayList<JClassType>(noWrapAnnotation.value().length);
         processClassArrayAnnotation(noWrapAnnotation.value(), noWrapTypes);
-      } else {
-        noWrapTypes = null;
       }
     }
 
@@ -184,6 +185,10 @@
     return Collections.unmodifiableCollection(peers.values());
   }
 
+  public List<JClassType> getCategoryTypes() {
+    return categoryTypes;
+  }
+
   public List<AutoBeanFactoryMethod> getMethods() {
     return Collections.unmodifiableList(methods);
   }
@@ -358,6 +363,7 @@
     AutoBeanType toReturn = peers.get(beanType);
     if (toReturn == null) {
       AutoBeanType.Builder builder = new AutoBeanType.Builder();
+      builder.setOwnerFactory(this);
       builder.setPeerType(beanType);
       builder.setMethods(computeMethods(beanType));
       builder.setInterceptor(findInterceptor(beanType));
@@ -390,7 +396,8 @@
 
     // Check using base types to account for erasure semantics
     JParameterizedType expectedFirst = oracle.getParameterizedType(
-        autoBeanInterface, new JClassType[] {ModelUtils.ensureBaseType(beanType)});
+        autoBeanInterface,
+        new JClassType[]{ModelUtils.ensureBaseType(beanType)});
     return expectedFirst.isAssignableTo(paramAsClass);
   }
 
diff --git a/user/src/com/google/gwt/editor/rebind/model/AutoBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
similarity index 69%
rename from user/src/com/google/gwt/editor/rebind/model/AutoBeanMethod.java
rename to user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
index 3cc2b45..ddb02b8 100644
--- a/user/src/com/google/gwt/editor/rebind/model/AutoBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
@@ -13,13 +13,16 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.rebind.model;
+package com.google.gwt.autobean.rebind.model;
 
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
 
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * Describes a method implemented by an AutoBean.
@@ -40,10 +43,15 @@
     public AutoBeanMethod build() {
       if (toReturn.action.equals(Action.GET)
           || toReturn.action.equals(Action.SET)) {
-        String name = toReturn.method.getName();
-        // setFoo
-        toReturn.propertyName = Character.toLowerCase(name.charAt(3))
-            + (name.length() >= 5 ? name.substring(4) : "");
+        PropertyName annotation = toReturn.method.getAnnotation(PropertyName.class);
+        if (annotation != null) {
+          toReturn.propertyName = annotation.value();
+        } else {
+          String name = toReturn.method.getName();
+          // setFoo
+          toReturn.propertyName = Character.toLowerCase(name.charAt(3))
+              + (name.length() >= 5 ? name.substring(4) : "");
+        }
       }
 
       try {
@@ -65,12 +73,19 @@
           method.getReturnType());
 
       if (!toReturn.isValueType) {
-        // See if it's a collection
+        // See if it's a collection or a map
         JClassType returnClass = method.getReturnType().isClassOrInterface();
         JClassType collectionInterface = oracle.findType(Collection.class.getCanonicalName());
+        JClassType mapInterface = oracle.findType(Map.class.getCanonicalName());
         if (collectionInterface.isAssignableFrom(returnClass)) {
-          toReturn.elementType = ModelUtils.findParameterizationOf(
-              collectionInterface, returnClass)[0];
+          JClassType[] parameterizations = ModelUtils.findParameterizationOf(
+              collectionInterface, returnClass);
+          toReturn.elementType = parameterizations[0];
+        } else if (mapInterface.isAssignableFrom(returnClass)) {
+          JClassType[] parameterizations = ModelUtils.findParameterizationOf(
+              mapInterface, returnClass);
+          toReturn.keyType = parameterizations[0];
+          toReturn.valueType = parameterizations[1];
         }
       }
     }
@@ -86,11 +101,13 @@
 
   private Action action;
   private JClassType elementType;
+  private JClassType keyType;
   private JMethod method;
   private boolean isNoWrap;
   private boolean isValueType;
   private String propertyName;
   private JMethod staticImpl;
+  private JClassType valueType;
 
   private AutoBeanMethod() {
   }
@@ -103,6 +120,10 @@
     return elementType;
   }
 
+  public JClassType getKeyType() {
+    return keyType;
+  }
+
   public JMethod getMethod() {
     return method;
   }
@@ -120,10 +141,18 @@
     return staticImpl;
   }
 
+  public JClassType getValueType() {
+    return valueType;
+  }
+
   public boolean isCollection() {
     return elementType != null;
   }
 
+  public boolean isMap() {
+    return keyType != null;
+  }
+
   public boolean isNoWrap() {
     return isNoWrap;
   }
diff --git a/user/src/com/google/gwt/editor/rebind/model/AutoBeanType.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
similarity index 74%
rename from user/src/com/google/gwt/editor/rebind/model/AutoBeanType.java
rename to user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
index 605047b..1697a9f 100644
--- a/user/src/com/google/gwt/editor/rebind/model/AutoBeanType.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
@@ -13,11 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.rebind.model;
+package com.google.gwt.autobean.rebind.model;
 
+import com.google.gwt.autobean.rebind.model.AutoBeanMethod.Action;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.editor.rebind.model.AutoBeanMethod.Action;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -33,9 +33,15 @@
    * Builder.
    */
   public static class Builder {
+    private boolean affectedByCategories;
+    private String beanSimpleSourceName;
+    private String categorySuffix;
     private AutoBeanType toReturn = new AutoBeanType();
 
     public AutoBeanType build() {
+      // Different implementations necessary for category-affected impls
+      toReturn.simpleSourceName = beanSimpleSourceName
+          + (affectedByCategories ? categorySuffix : "");
       try {
         return toReturn;
       } finally {
@@ -44,6 +50,7 @@
     }
 
     public void setInterceptor(JMethod interceptor) {
+      affectedByCategories = interceptor != null;
       toReturn.interceptor = interceptor;
     }
 
@@ -64,10 +71,12 @@
 
       toReturn.simpleBean = true;
       for (AutoBeanMethod method : methods) {
-        if (method.getAction().equals(Action.CALL)
-            && method.getStaticImpl() == null) {
-          toReturn.simpleBean = false;
-          break;
+        if (method.getAction().equals(Action.CALL)) {
+          if (method.getStaticImpl() == null) {
+            toReturn.simpleBean = false;
+          } else {
+            affectedByCategories = true;
+          }
         }
       }
     }
@@ -76,6 +85,18 @@
       toReturn.noWrap = noWrap;
     }
 
+    public void setOwnerFactory(AutoBeanFactoryModel autoBeanFactoryModel) {
+      if (autoBeanFactoryModel.getCategoryTypes() == null) {
+        return;
+      }
+      StringBuilder sb = new StringBuilder();
+      for (JClassType category : autoBeanFactoryModel.getCategoryTypes()) {
+        sb.append("_").append(
+            category.getQualifiedSourceName().replace('.', '_'));
+      }
+      categorySuffix = sb.toString();
+    }
+
     public void setPeerType(JClassType type) {
       assert type.isParameterized() == null && type.isRawType() == null;
       toReturn.peerType = type;
@@ -84,7 +105,7 @@
         packageName = "emul." + packageName;
       }
       toReturn.packageName = packageName;
-      toReturn.simpleSourceName = type.getName().replace('.', '_') + "AutoBean";
+      beanSimpleSourceName = type.getName().replace('.', '_') + "AutoBean";
     }
   }
 
diff --git a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
new file mode 100644
index 0000000..e7a7071
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
@@ -0,0 +1,86 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
+import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+
+/**
+ * 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 {
+  /**
+   * Create an instance of an AutoBeanFactory.
+   * 
+   * @param <F> the factory type
+   * @param clazz the Class representing the factory interface
+   * @return an instance of the AutoBeanFactory
+   */
+  public static <F extends AutoBeanFactory> F create(Class<F> clazz) {
+    Configuration.Builder builder = new Configuration.Builder();
+    Category cat = clazz.getAnnotation(Category.class);
+    if (cat != null) {
+      builder.setCategories(cat.value());
+    }
+    NoWrap noWrap = clazz.getAnnotation(NoWrap.class);
+    if (noWrap != null) {
+      builder.setNoWrap(noWrap.value());
+    }
+
+    return makeProxy(clazz, new FactoryHandler(builder.build()));
+  }
+
+  /**
+   * Create an instance of an AutoBean directly.
+   * 
+   * @param <T> the interface type implemented by the AutoBean
+   * @param clazz the interface type implemented by the AutoBean
+   * @return an instance of an AutoBean
+   */
+  public static <T> AutoBean<T> createBean(Class<T> clazz,
+      Configuration configuration) {
+    return new ProxyAutoBean<T>(clazz, configuration);
+  }
+
+  /**
+   * Utility method to crete a new {@link Proxy} instance.
+   * 
+   * @param <T> the interface type to be implemented by the Proxy
+   * @param intf the Class representing the interface type
+   * @param handler the implementation of the interface
+   * @return a Proxy instance
+   */
+  static <T> T makeProxy(Class<T> intf, InvocationHandler handler) {
+    return intf.cast(Proxy.newProxyInstance(
+        Thread.currentThread().getContextClassLoader(), new Class<?>[]{intf},
+        handler));
+  }
+}
diff --git a/user/src/com/google/gwt/autobean/server/BeanMethod.java b/user/src/com/google/gwt/autobean/server/BeanMethod.java
new file mode 100644
index 0000000..43b13e2
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/BeanMethod.java
@@ -0,0 +1,154 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Breakout of method types that an AutoBean shim interface can implement. The
+ * order of the values of the enum is important.
+ */
+enum BeanMethod {
+  /**
+   * Methods defined in Object.
+   */
+  OBJECT {
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
+        throws Throwable {
+      if (CALL.matches(handler, method)) {
+        return CALL.invoke(handler, method, args);
+      }
+      return method.invoke(handler, args);
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      return method.getDeclaringClass().equals(Object.class);
+    }
+  },
+  /**
+   * Getters.
+   */
+  GET {
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+      Object toReturn = handler.getBean().getValues().get(
+          method.getName().substring(3));
+      if (toReturn == null && method.getReturnType().isPrimitive()) {
+        toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType());
+      }
+      return toReturn;
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      return method.getName().startsWith("get")
+          && method.getParameterTypes().length == 0
+          && !method.getReturnType().equals(Void.TYPE);
+    }
+  },
+  /**
+   * Setters.
+   */
+  SET {
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+      handler.getBean().getValues().put(method.getName().substring(3), args[0]);
+      return null;
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      return method.getName().startsWith("set")
+          && method.getParameterTypes().length == 1
+          && method.getReturnType().equals(Void.TYPE);
+    }
+  },
+  /**
+   * Domain methods.
+   */
+  CALL {
+    @Override
+    Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
+        throws Throwable {
+      if (args == null) {
+        args = EMPTY_OBJECT;
+      }
+
+      Method found = findMethod(handler, method);
+      if (found != null) {
+        Object[] realArgs = new Object[args.length + 1];
+        realArgs[0] = handler.getBean();
+        System.arraycopy(args, 0, realArgs, 1, args.length);
+        return found.invoke(null, realArgs);
+      }
+      throw new RuntimeException("Could not find category implementation of "
+          + method.toGenericString());
+    }
+
+    @Override
+    boolean matches(SimpleBeanHandler<?> handler, Method method) {
+      return handler.getBean().isWrapper()
+          || !handler.getBean().getConfiguration().getCategories().isEmpty()
+          && findMethod(handler, method) != null;
+    }
+  };
+
+  private static final Object[] EMPTY_OBJECT = new Object[0];
+
+  static Method findMethod(SimpleBeanHandler<?> handler, Method method) {
+    Class<?>[] declaredParams = method.getParameterTypes();
+    Class<?>[] searchParams = new Class<?>[declaredParams.length + 1];
+    searchParams[0] = AutoBean.class;
+    System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length);
+
+    for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) {
+      try {
+        Method found = clazz.getMethod(method.getName(), searchParams);
+        if (Modifier.isStatic(found.getModifiers())) {
+          return found;
+        }
+      } catch (NoSuchMethodException expected) {
+      } catch (IllegalArgumentException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Invoke the method.
+   */
+  abstract Object invoke(SimpleBeanHandler<?> handler, Method method,
+      Object[] args) throws Throwable;
+
+  /**
+   * Convenience method, not valid for {@link BeanMethod#CALL}.
+   */
+  boolean matches(Method method) {
+    return matches(null, method);
+  }
+
+  /**
+   * Determine if the method maches the given type.
+   */
+  abstract boolean matches(SimpleBeanHandler<?> handler, Method method);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/BeanPropertyContext.java b/user/src/com/google/gwt/autobean/server/BeanPropertyContext.java
new file mode 100644
index 0000000..f0014b7
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/BeanPropertyContext.java
@@ -0,0 +1,46 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * A property context that allows setters to be called on a simple peer,
+ * regardless of whether or not the interface actually has a setter.
+ */
+class BeanPropertyContext extends MethodPropertyContext {
+  private final String propertyName;
+  private final Map<String, Object> map;
+
+  public BeanPropertyContext(ProxyAutoBean<?> bean, Method getter) {
+    super(getter);
+    propertyName = getter.getName().substring(3);
+    map = bean.getPropertyMap();
+  }
+
+  @Override
+  public boolean canSet() {
+    return true;
+  }
+
+  @Override
+  public void set(Object value) {
+    map.put(propertyName, TypeUtils.maybeAutobox(getType()).cast(value));
+  }
+}
diff --git a/user/src/com/google/gwt/autobean/server/Configuration.java b/user/src/com/google/gwt/autobean/server/Configuration.java
new file mode 100644
index 0000000..31942e9
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/Configuration.java
@@ -0,0 +1,73 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.shared.AutoBean;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Used by {@link AutoBeanFactoryMagic#createBean()}.
+ */
+public class Configuration {
+  /**
+   * Builds {@link Configuration} objects.
+   */
+  public static class Builder {
+    private Configuration toReturn = new Configuration();
+
+    public Configuration build() {
+      toReturn.noWrap.add(AutoBean.class);
+      toReturn.noWrap = Collections.unmodifiableSet(toReturn.noWrap);
+      try {
+        return toReturn;
+      } finally {
+        toReturn = null;
+      }
+    }
+
+    public Builder setCategories(Class<?>... categories) {
+      toReturn.categories = Collections.unmodifiableList(new ArrayList<Class<?>>(
+          Arrays.asList(categories)));
+      return this;
+    }
+
+    public Builder setNoWrap(Class<?>... noWrap) {
+      toReturn.noWrap.addAll(Arrays.asList(noWrap));
+      return this;
+    }
+  }
+
+  private List<Class<?>> categories = Collections.emptyList();
+
+  private Set<Class<?>> noWrap = new HashSet<Class<?>>();
+
+  private Configuration() {
+  }
+
+  public List<Class<?>> getCategories() {
+    return categories;
+  }
+
+  public Set<Class<?>> getNoWrap() {
+    return noWrap;
+  }
+}
diff --git a/user/src/com/google/gwt/autobean/server/FactoryHandler.java b/user/src/com/google/gwt/autobean/server/FactoryHandler.java
new file mode 100644
index 0000000..8e2ceaf
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/FactoryHandler.java
@@ -0,0 +1,79 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * Handles dispatches on AutoBeanFactory interfaces.
+ */
+class FactoryHandler implements InvocationHandler {
+  private final Configuration configuration;
+
+  /**
+   * Constructor.
+   * 
+   * @param categories the classes specified by a Category annotation
+   */
+  public FactoryHandler(Configuration configuration) {
+    this.configuration = configuration;
+  }
+
+  /**
+   * Handles both declared factory methods as well as the dynamic create
+   * methods.
+   */
+  public Object invoke(Object proxy, Method method, Object[] args)
+      throws Throwable {
+
+    Class<?> beanType;
+    Object toWrap = null;
+    if (method.getName().equals("create")) {
+      // Dynamic create. Guaranteed to have at least one argument
+      // create(clazz); or create(clazz, toWrap);
+      beanType = (Class<?>) args[0];
+      if (args.length == 2) {
+        toWrap = args[1];
+      }
+    } else {
+      // Declared factory method, use the parameterization
+      // AutoBean<Foo> foo(); or Autobean<foo> foo(Foo toWrap);
+      ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType();
+      beanType = (Class<?>) returnType.getActualTypeArguments()[0];
+
+      if (args != null && args.length == 1) {
+        toWrap = args[0];
+      }
+    }
+
+    // Return any existing wrapper
+    ProxyAutoBean<Object> toReturn = (ProxyAutoBean<Object>) AutoBeanUtils.getAutoBean(toWrap);
+    if (toReturn == null) {
+      // Create the implementation bean
+      if (toWrap == null) {
+        toReturn = new ProxyAutoBean<Object>(beanType, configuration);
+      } else {
+        toReturn = new ProxyAutoBean<Object>(beanType, configuration, toWrap);
+      }
+    }
+
+    return toReturn;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/GetterPropertyContext.java b/user/src/com/google/gwt/autobean/server/GetterPropertyContext.java
new file mode 100644
index 0000000..ed904ea
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/GetterPropertyContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.autobean.server;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Used by {@link ProxyAutoBean#traverseProperties()}.
+ */
+class GetterPropertyContext extends MethodPropertyContext {
+  private final Method setter;
+  private final Object shim;
+
+  GetterPropertyContext(ProxyAutoBean<?> bean, Method getter) {
+    super(getter);
+    this.shim = bean.as();
+
+    // Look for the setter method.
+    Method found;
+    try {
+      found = bean.getBeanType().getMethod(
+          "set" + getter.getName().substring(3), getter.getReturnType());
+    } catch (NoSuchMethodException expected) {
+      found = null;
+    }
+    setter = found;
+  }
+
+  @Override
+  public boolean canSet() {
+    return setter != null;
+  }
+
+  @Override
+  public void set(Object value) {
+    if (!canSet()) {
+      throw new UnsupportedOperationException("No setter");
+    }
+    try {
+      setter.setAccessible(true);
+      setter.invoke(shim, value);
+    } catch (IllegalArgumentException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e.getCause());
+    }
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java b/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
new file mode 100644
index 0000000..ac2653b
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
@@ -0,0 +1,77 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * A base type to handle analyzing the return value of a getter method. The
+ * accessor methods are implemented in subtypes.
+ */
+abstract class MethodPropertyContext implements CollectionPropertyContext,
+    MapPropertyContext {
+  private final Class<?> keyType;
+  private final Class<?> valueType;
+  private final Class<?> elementType;
+  private final Class<?> type;
+
+  public MethodPropertyContext(Method getter) {
+    this.type = getter.getReturnType();
+
+    // Compute collection element type
+    if (Collection.class.isAssignableFrom(getType())) {
+      elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+          Collection.class, getter.getGenericReturnType(),
+          getter.getReturnType()));
+      keyType = valueType = null;
+    } else if (Map.class.isAssignableFrom(getType())) {
+      Type[] types = TypeUtils.getParameterization(Map.class,
+          getter.getGenericReturnType());
+      keyType = TypeUtils.ensureBaseType(types[0]);
+      valueType = TypeUtils.ensureBaseType(types[1]);
+      elementType = null;
+    } else {
+      elementType = keyType = valueType = null;
+    }
+  }
+
+  public abstract boolean canSet();
+
+  public Class<?> getElementType() {
+    return elementType;
+  }
+
+  public Class<?> getKeyType() {
+    return keyType;
+  }
+
+  public Class<?> getType() {
+    return type;
+  }
+
+  public Class<?> getValueType() {
+    return valueType;
+  }
+
+  public abstract void set(Object value);
+}
diff --git a/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java b/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
new file mode 100644
index 0000000..b30b36c
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
@@ -0,0 +1,248 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.impl.AbstractAutoBean;
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation of an AutoBean that uses reflection.
+ * 
+ * @param <T> the type of interface being wrapped
+ */
+class ProxyAutoBean<T> extends AbstractAutoBean<T> {
+  private final Class<T> beanType;
+  private final Configuration configuration;
+  private final List<Method> getters;
+  private final T shim;
+
+  // These constructors mirror the generated constructors.
+  @SuppressWarnings("unchecked")
+  public ProxyAutoBean(Class<?> beanType, Configuration configuration) {
+    super();
+    this.beanType = (Class<T>) beanType;
+    this.configuration = configuration;
+    this.getters = calculateGetters();
+    this.shim = createShim();
+  }
+
+  @SuppressWarnings("unchecked")
+  public ProxyAutoBean(Class<?> beanType, Configuration configuration, T toWrap) {
+    super(toWrap);
+    if (Proxy.isProxyClass(toWrap.getClass())) {
+      System.out.println("blah");
+    }
+    this.beanType = (Class<T>) beanType;
+    this.configuration = configuration;
+    this.getters = calculateGetters();
+    this.shim = createShim();
+  }
+
+  private ProxyAutoBean(ProxyAutoBean<T> toClone, boolean deep) {
+    super(toClone, deep);
+    this.beanType = toClone.beanType;
+    this.configuration = toClone.configuration;
+    this.getters = toClone.getters;
+    this.shim = createShim();
+  }
+
+  @Override
+  public T as() {
+    return shim;
+  }
+
+  @Override
+  public AutoBean<T> clone(boolean deep) {
+    return new ProxyAutoBean<T>(this, deep);
+  }
+
+  public Configuration getConfiguration() {
+    return configuration;
+  }
+
+  public Class<T> getType() {
+    return beanType;
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void call(String method, Object returned, Object... parameters) {
+    super.call(method, returned, parameters);
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void checkFrozen() {
+    super.checkFrozen();
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void checkWrapped() {
+    super.checkWrapped();
+  }
+
+  @Override
+  protected T createSimplePeer() {
+    return AutoBeanFactoryMagic.makeProxy(beanType, new SimpleBeanHandler<T>(
+        this));
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected <V> V get(String method, V toReturn) {
+    return super.get(method, toReturn);
+  }
+
+  /**
+   * Allow access by {@link BeanMethod}.
+   */
+  protected Map<String, Object> getValues() {
+    return values;
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  protected T getWrapped() {
+    return super.getWrapped();
+  }
+
+  /**
+   * Allow access by {@link ShimHandler}.
+   */
+  @Override
+  protected void set(String method, Object value) {
+    super.set(method, value);
+  }
+
+  // TODO: Port to model-based when class-based TypeOracle is available.
+  @Override
+  protected void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx) {
+    for (final Method getter : getters) {
+      String name;
+      PropertyName annotation = getter.getAnnotation(PropertyName.class);
+      if (annotation != null) {
+        name = annotation.value();
+      } else {
+        name = getter.getName();
+        name = Character.toLowerCase(name.charAt(3))
+            + (name.length() >= 5 ? name.substring(4) : "");
+      }
+
+      // Use the shim to handle automatic wrapping
+      Object value;
+      try {
+        getter.setAccessible(true);
+        value = getter.invoke(shim);
+      } catch (IllegalArgumentException e) {
+        throw new RuntimeException(e);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e.getCause());
+      }
+
+      // Create the context used for the property visitation
+      MethodPropertyContext x = isUsingSimplePeer() ? new BeanPropertyContext(
+          this, getter) : new GetterPropertyContext(this, getter);
+
+      if (TypeUtils.isValueType(x.getType())) {
+        if (visitor.visitValueProperty(name, value, x)) {
+        }
+        visitor.endVisitValueProperty(name, value, x);
+      } else if (Collection.class.isAssignableFrom(x.getType())) {
+        // Workaround for generics bug in mac javac 1.6.0_22
+        @SuppressWarnings("rawtypes")
+        AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value);
+        @SuppressWarnings("unchecked")
+        AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp;
+        if (visitor.visitCollectionProperty(name, bean, x)) {
+          if (value != null) {
+            ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+          }
+        }
+        visitor.endVisitCollectionProperty(name, bean, x);
+      } else if (Map.class.isAssignableFrom(x.getType())) {
+        // Workaround for generics bug in mac javac 1.6.0_22
+        @SuppressWarnings("rawtypes")
+        AutoBean temp = AutoBeanUtils.getAutoBean((Map) value);
+        @SuppressWarnings("unchecked")
+        AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp;
+        if (visitor.visitMapProperty(name, bean, x)) {
+          if (value != null) {
+            ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+          }
+        }
+        visitor.endVisitMapProperty(name, bean, x);
+      } else {
+        ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value);
+        if (visitor.visitReferenceProperty(name, bean, x)) {
+          if (value != null) {
+            bean.traverse(visitor, ctx);
+          }
+        }
+        visitor.endVisitReferenceProperty(name, bean, x);
+      }
+    }
+  }
+
+  Class<?> getBeanType() {
+    return beanType;
+  }
+
+  Map<String, Object> getPropertyMap() {
+    return values;
+  }
+
+  private List<Method> calculateGetters() {
+    List<Method> toReturn = new ArrayList<Method>();
+    for (Method method : beanType.getMethods()) {
+      if (BeanMethod.GET.matches(method)) {
+        toReturn.add(method);
+      }
+    }
+    return Collections.unmodifiableList(toReturn);
+  }
+
+  private T createShim() {
+    T toReturn = AutoBeanFactoryMagic.makeProxy(beanType, new ShimHandler<T>(
+        this, getWrapped()));
+    WeakMapping.set(toReturn, AutoBean.class.getName(), this);
+    return toReturn;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/ShimHandler.java b/user/src/com/google/gwt/autobean/server/ShimHandler.java
new file mode 100644
index 0000000..12e365d
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/ShimHandler.java
@@ -0,0 +1,133 @@
+/*
+ * 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.autobean.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Implements an AutoBean's shim interface that intercepts calls to the backing
+ * object.
+ * 
+ * @param <T> the interface type of the AutoBean
+ */
+class ShimHandler<T> implements InvocationHandler {
+  private final ProxyAutoBean<T> bean;
+  private final Method interceptor;
+  private final T toWrap;
+
+  public ShimHandler(ProxyAutoBean<T> bean, T toWrap) {
+    this.bean = bean;
+    this.toWrap = toWrap;
+
+    Method maybe = null;
+    for (Class<?> clazz : bean.getConfiguration().getCategories()) {
+      try {
+        maybe = clazz.getMethod("__intercept", AutoBean.class, Object.class);
+        break;
+      } catch (SecurityException expected) {
+      } catch (NoSuchMethodException expected) {
+      }
+    }
+    interceptor = maybe;
+  }
+
+  @Override
+  public boolean equals(Object couldBeShim) {
+    if (couldBeShim == null) {
+      return false;
+    }
+    // Handles the foo.equals(foo) case
+    if (Proxy.isProxyClass(couldBeShim.getClass())
+        && this == Proxy.getInvocationHandler(couldBeShim)) {
+      return true;
+    }
+    return bean.getWrapped().equals(couldBeShim);
+  }
+
+  @Override
+  public int hashCode() {
+    return bean.getWrapped().hashCode();
+  }
+
+  public Object invoke(Object proxy, Method method, Object[] args)
+      throws Throwable {
+    method.setAccessible(true);
+    Object toReturn;
+    String name = method.getName();
+    bean.checkWrapped();
+    method.setAccessible(true);
+    if (BeanMethod.OBJECT.matches(method)) {
+      return method.invoke(this, args);
+    } else if (BeanMethod.GET.matches(method)) {
+      toReturn = method.invoke(toWrap, args);
+      toReturn = bean.get(name, toReturn);
+    } else if (BeanMethod.SET.matches(method)) {
+      bean.checkFrozen();
+      toReturn = method.invoke(toWrap, args);
+      bean.set(name, args[0]);
+    } else {
+      // XXX How should freezing and calls work together?
+      // bean.checkFrozen();
+      toReturn = method.invoke(toWrap, args);
+      bean.call(name, toReturn, args);
+    }
+    Class<?> intf = method.getReturnType();
+    if (!Object.class.equals(intf)) {
+      // XXX Need to deal with resolving generic T return types
+      toReturn = maybeWrap(intf, toReturn);
+    }
+    if (interceptor != null) {
+      toReturn = interceptor.invoke(null, bean, toReturn);
+    }
+    return toReturn;
+  }
+
+  @Override
+  public String toString() {
+    return bean.getWrapped().toString();
+  }
+
+  private Object maybeWrap(Class<?> intf, Object toReturn) {
+    if (toReturn == null) {
+      return null;
+    }
+    if (TypeUtils.isValueType(intf)
+        || TypeUtils.isValueType(toReturn.getClass())
+        || AutoBeanUtils.getAutoBean(toReturn) != null
+        || bean.getConfiguration().getNoWrap().contains(intf)) {
+      return toReturn;
+    }
+    if (toReturn.getClass().isArray()) {
+      for (int i = 0, j = Array.getLength(toReturn); i < j; i++) {
+        Object value = Array.get(toReturn, i);
+        if (value != null) {
+          Array.set(toReturn, i, maybeWrap(value.getClass(), value));
+        }
+      }
+      return toReturn;
+    }
+    ProxyAutoBean<Object> newBean = new ProxyAutoBean<Object>(intf,
+        bean.getConfiguration(), toReturn);
+    return newBean.as();
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
new file mode 100644
index 0000000..a15d27b
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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.autobean.server;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+/**
+ * Dynamic implementation of an AutoBean's simple peer object.
+ * 
+ * @param <T> the type of interface the shim allows access to
+ */
+class SimpleBeanHandler<T> implements InvocationHandler {
+  private final ProxyAutoBean<T> bean;
+
+  public SimpleBeanHandler(ProxyAutoBean<T> bean) {
+    this.bean = bean;
+  }
+
+  public ProxyAutoBean<T> getBean() {
+    return bean;
+  }
+
+  /**
+   * Delegates most work to {@link BeanMethod}.
+   */
+  public Object invoke(Object proxy, Method method, Object[] args)
+      throws Throwable {
+    for (BeanMethod type : BeanMethod.values()) {
+      if (type.matches(this, method)) {
+        Object toReturn = type.invoke(this, method, args);
+        return toReturn;
+      }
+    }
+    throw new RuntimeException("Unhandled invocation " + method.getName());
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
new file mode 100644
index 0000000..6b9f22e
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -0,0 +1,150 @@
+/*
+ * 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.autobean.server.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Uses the org.json packages to slice and dice request payloads.
+ */
+public class JsonSplittable implements Splittable {
+  public static Splittable create(String payload) {
+    try {
+      switch (payload.charAt(0)) {
+        case '{':
+          return new JsonSplittable(new JSONObject(payload));
+        case '[':
+          return new JsonSplittable(new JSONArray(payload));
+        case '"':
+          return new JsonSplittable(
+              new JSONArray("[" + payload + "]").getString(0));
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          return new JsonSplittable(payload);
+        default:
+          throw new RuntimeException("Could not parse payload: payload[0] = "
+              + payload.charAt(0));
+      }
+    } catch (JSONException e) {
+      throw new RuntimeException("Could not parse payload", e);
+    }
+  }
+
+  private final JSONArray array;
+  private final JSONObject obj;
+
+  private final String string;
+
+  private JsonSplittable(JSONArray array) {
+    this.array = array;
+    this.obj = null;
+    this.string = null;
+  }
+
+  private JsonSplittable(JSONObject obj) {
+    this.array = null;
+    this.obj = obj;
+    this.string = null;
+  }
+
+  private JsonSplittable(String string) {
+    this.array = null;
+    this.obj = null;
+    this.string = string;
+  }
+
+  public String asString() {
+    return string;
+  }
+
+  public Splittable get(int index) {
+    try {
+      return makeSplittable(array.get(index));
+    } catch (JSONException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public Splittable get(String key) {
+    try {
+      return makeSplittable(obj.get(key));
+    } catch (JSONException e) {
+      throw new RuntimeException(key, e);
+    }
+  }
+
+  public String getPayload() {
+    if (obj != null) {
+      return obj.toString();
+    }
+    if (array != null) {
+      return array.toString();
+    }
+    if (string != null) {
+      return StringQuoter.quote(string);
+    }
+    throw new RuntimeException("No data in this JsonSplittable");
+  }
+
+  public List<String> getPropertyKeys() {
+    String[] names = JSONObject.getNames(obj);
+    if (names == null) {
+      return Collections.emptyList();
+    } else {
+      return Collections.unmodifiableList(Arrays.asList(names));
+    }
+  }
+
+  public boolean isNull(int index) {
+    return array.isNull(index);
+  }
+
+  public boolean isNull(String key) {
+    // Treat undefined and null as the same
+    return !obj.has(key) || obj.isNull(key);
+  }
+
+  public int size() {
+    return array.length();
+  }
+
+  private JsonSplittable makeSplittable(Object object) {
+    if (object instanceof JSONObject) {
+      return new JsonSplittable((JSONObject) object);
+    }
+    if (object instanceof JSONArray) {
+      return new JsonSplittable((JSONArray) object);
+    }
+    return new JsonSplittable(object.toString());
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
new file mode 100644
index 0000000..d4d954b
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -0,0 +1,173 @@
+/*
+ * 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.autobean.server.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Shared code for answering question about Class objects. This is a
+ * server-compatible analog to ModelUtils.
+ */
+public class TypeUtils {
+  static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
+  static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES;
+  @SuppressWarnings("unchecked")
+  static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
+      Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
+          Enum.class, Number.class, String.class, Void.class)));
+
+  static {
+    Map<Class<?>, Object> temp = new HashMap<Class<?>, Object>();
+    temp.put(boolean.class, false);
+    temp.put(byte.class, (byte) 0);
+    temp.put(char.class, (char) 0);
+    temp.put(double.class, (double) 0);
+    temp.put(float.class, (float) 0);
+    temp.put(int.class, (int) 0);
+    temp.put(long.class, (long) 0);
+    temp.put(short.class, (short) 0);
+    temp.put(void.class, null);
+
+    DEFAULT_PRIMITIVE_VALUES = Collections.unmodifiableMap(temp);
+  }
+
+  static {
+    Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
+    autoBoxMap.put(byte.class, Byte.class);
+    autoBoxMap.put(char.class, Character.class);
+    autoBoxMap.put(double.class, Double.class);
+    autoBoxMap.put(float.class, Float.class);
+    autoBoxMap.put(int.class, Integer.class);
+    autoBoxMap.put(long.class, Long.class);
+    autoBoxMap.put(short.class, Short.class);
+    autoBoxMap.put(void.class, Void.class);
+    AUTOBOX_MAP = Collections.unmodifiableMap(autoBoxMap);
+  }
+
+  /**
+   * Similar to ModelUtils#ensureBaseType(JType) but for the reflection API.
+   */
+  public static Class<?> ensureBaseType(Type type) {
+    if (type instanceof Class<?>) {
+      return (Class<?>) type;
+    }
+    if (type instanceof GenericArrayType) {
+      return Array.newInstance(
+          ensureBaseType(((GenericArrayType) type).getGenericComponentType()),
+          0).getClass();
+    }
+    if (type instanceof ParameterizedType) {
+      return ensureBaseType(((ParameterizedType) type).getRawType());
+    }
+    if (type instanceof TypeVariable<?>) {
+      return ensureBaseType(((TypeVariable<?>) type).getBounds()[0]);
+    }
+    if (type instanceof WildcardType) {
+      WildcardType wild = (WildcardType) type;
+      return ensureBaseType(wild.getUpperBounds()[0]);
+    }
+    throw new RuntimeException("Cannot handle " + type.getClass().getName());
+  }
+
+  /**
+   * Given a primitive Class type, return a default value.
+   */
+  public static Object getDefaultPrimitiveValue(Class<?> clazz) {
+    assert clazz.isPrimitive() : "Expecting primitive type";
+    return DEFAULT_PRIMITIVE_VALUES.get(clazz);
+  }
+
+  public static Type[] getParameterization(Class<?> intf, Type... types) {
+    for (Type type : types) {
+      if (type == null) {
+        continue;
+      } else if (type instanceof ParameterizedType) {
+        ParameterizedType param = (ParameterizedType) type;
+        Type[] actualTypeArguments = param.getActualTypeArguments();
+        Class<?> base = ensureBaseType(param.getRawType());
+        Type[] typeParameters = base.getTypeParameters();
+
+        Map<Type, Type> map = new HashMap<Type, Type>();
+        for (int i = 0, j = typeParameters.length; i < j; i++) {
+          map.put(typeParameters[i], actualTypeArguments[i]);
+        }
+        Type[] lookFor = intf.equals(base) ? intf.getTypeParameters()
+            : getParameterization(intf, base.getGenericInterfaces());
+        List<Type> toReturn = new ArrayList<Type>();
+        for (int i = 0, j = lookFor.length; i < j; i++) {
+          Type found = map.get(lookFor[i]);
+          if (found != null) {
+            toReturn.add(found);
+          }
+        }
+        return toReturn.toArray(new Type[toReturn.size()]);
+      } else if (type instanceof Class<?>) {
+        Class<?> clazz = (Class<?>) type;
+        if (intf.equals(clazz)) {
+          return intf.getTypeParameters();
+        }
+        Type[] found = getParameterization(intf, clazz.getGenericSuperclass());
+        if (found != null) {
+          return found;
+        }
+        found = getParameterization(intf, clazz.getGenericInterfaces());
+        if (found != null) {
+          return found;
+        }
+      }
+    }
+    return null;
+  }
+
+  public static Type getSingleParameterization(Class<?> intf, Type... types) {
+    Type[] found = getParameterization(intf, types);
+    return found == null ? null : found[0];
+  }
+
+  public static boolean isValueType(Class<?> clazz) {
+    if (clazz.isPrimitive() || VALUE_TYPES.contains(clazz)) {
+      return true;
+    }
+    for (Class<?> c : VALUE_TYPES) {
+      if (c.isAssignableFrom(clazz)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public static Class<?> maybeAutobox(Class<?> domainType) {
+    Class<?> autoBoxType = AUTOBOX_MAP.get(domainType);
+    return autoBoxType == null ? domainType : autoBoxType;
+  }
+
+  private TypeUtils() {
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/AutoBean.java b/user/src/com/google/gwt/autobean/shared/AutoBean.java
similarity index 73%
rename from user/src/com/google/gwt/editor/client/AutoBean.java
rename to user/src/com/google/gwt/autobean/shared/AutoBean.java
index f90e621..025c341 100644
--- a/user/src/com/google/gwt/editor/client/AutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBean.java
@@ -13,7 +13,13 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.client;
+package com.google.gwt.autobean.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
 /**
  * A controller for an implementation of a bean interface.
@@ -22,8 +28,22 @@
  */
 public interface AutoBean<T> {
   /**
+   * An annotation that allows inferred property names to be overridden.
+   * <p>
+   * This annotation is asymmetric, applying it to a getter will not affect the
+   * setter. The asymmetry allows existing users of an interface to read old
+   * {@link AutoBeanCodex} messages, but write new ones.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface PropertyName {
+    String value();
+  }
+
+  /**
    * Accept an AutoBeanVisitor.
-   *
+   * 
    * @param visitor an {@link AutoBeanVisitor}
    */
   void accept(AutoBeanVisitor visitor);
@@ -31,7 +51,7 @@
   /**
    * Returns a proxy implementation of the <code>T</code> interface which will
    * delegate to the underlying wrapped object, if any.
-   *
+   * 
    * @return a proxy that delegates to the wrapped object
    */
   T as();
@@ -60,24 +80,28 @@
   <Q> Q getTag(String tagName);
 
   /**
-   * Returns the value most recently passed to {@link #setFrozen}, or {@code
-   * false} if it has never been called.
+   * Returns the wrapped interface type.
+   */
+  Class<T> getType();
+
+  /**
+   * Returns the value most recently passed to {@link #setFrozen}, or
+   * {@code false} if it has never been called.
    * 
    * @return {@code true} if this instance is frozen
    */
   boolean isFrozen();
 
   /**
-   * Returns {@code true} if the AutoBean was provided with an external
-   * object.
-   *
+   * Returns {@code true} if the AutoBean was provided with an external object.
+   * 
    * @return {@code true} if this instance is a wrapper
    */
   boolean isWrapper();
 
   /**
    * Disallows any method calls other than getters. All setter and call
-   * operations will throw an {@link UnsupportedOperationException}.
+   * operations will throw an {@link IllegalStateException}.
    * 
    * @param frozen if {@code true}, freeze this instance
    */
@@ -86,7 +110,7 @@
   /**
    * A tag is an arbitrary piece of external metadata to be associated with the
    * wrapped value.
-   *
+   * 
    * @param tagName the tag name
    * @param value the wrapped value
    * @see #getTag(String)
@@ -94,9 +118,9 @@
   void setTag(String tagName, Object value);
 
   /**
-   * If the AutoBean wraps an object, return the underlying object.
-   * The AutoBean will no longer function once unwrapped.
-   *
+   * If the AutoBean wraps an object, return the underlying object. The AutoBean
+   * will no longer function once unwrapped.
+   * 
    * @return the previously-wrapped object
    * @throws IllegalStateException if the AutoBean is not a wrapper
    */
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
new file mode 100644
index 0000000..425271a
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -0,0 +1,497 @@
+/*
+ * 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.autobean.shared;
+
+import com.google.gwt.autobean.shared.impl.LazySplittable;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Utility methods for encoding an AutoBean graph into a JSON-compatible string.
+ * This codex intentionally does not preserve object identity, nor does it
+ * encode cycles, but it will detect them.
+ */
+public class AutoBeanCodex {
+  static class Decoder extends AutoBeanVisitor {
+    private final Stack<AutoBean<?>> beanStack = new Stack<AutoBean<?>>();
+    private final Stack<Splittable> dataStack = new Stack<Splittable>();
+    private AutoBean<?> bean;
+    private Splittable data;
+    private final AutoBeanFactory factory;
+
+    public Decoder(AutoBeanFactory factory) {
+      this.factory = factory;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> AutoBean<T> decode(Splittable data, Class<T> type) {
+      push(data, type);
+      bean.accept(this);
+      return (AutoBean<T>) pop();
+    }
+
+    @Override
+    public boolean visitCollectionProperty(String propertyName,
+        AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+      if (data.isNull(propertyName)) {
+        return false;
+      }
+
+      Collection<Object> collection;
+      if (List.class.equals(ctx.getType())) {
+        collection = new ArrayList<Object>();
+      } else if (Set.class.equals(ctx.getType())) {
+        collection = new HashSet<Object>();
+      } else {
+        throw new UnsupportedOperationException("Only List and Set supported");
+      }
+
+      boolean isValue = ValueCodex.canDecode(ctx.getElementType());
+      boolean isEncoded = Splittable.class.equals(ctx.getElementType());
+      Splittable listData = data.get(propertyName);
+      for (int i = 0, j = listData.size(); i < j; i++) {
+        if (listData.isNull(i)) {
+          collection.add(null);
+        } else {
+          if (isValue) {
+            collection.add(ValueCodex.decode(ctx.getElementType(),
+                listData.get(i)));
+          } else if (isEncoded) {
+            collection.add(listData.get(i));
+          } else {
+            collection.add(decode(listData.get(i), ctx.getElementType()).as());
+          }
+        }
+      }
+      ctx.set(collection);
+      return false;
+    }
+
+    @Override
+    public boolean visitMapProperty(String propertyName,
+        AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
+      if (data.isNull(propertyName)) {
+        return false;
+      }
+
+      Map<?, ?> map;
+      if (ValueCodex.canDecode(ctx.getKeyType())) {
+        map = decodeValueKeyMap(data.get(propertyName), ctx.getKeyType(),
+            ctx.getValueType());
+      } else {
+        map = decodeObjectKeyMap(data.get(propertyName), ctx.getKeyType(),
+            ctx.getValueType());
+      }
+      ctx.set(map);
+      return false;
+    }
+
+    @Override
+    public boolean visitReferenceProperty(String propertyName,
+        AutoBean<?> value, PropertyContext ctx) {
+      if (data.isNull(propertyName)) {
+        return false;
+      }
+
+      if (Splittable.class.equals(ctx.getType())) {
+        ctx.set(data.get(propertyName));
+        return false;
+      }
+
+      push(data.get(propertyName), ctx.getType());
+      bean.accept(this);
+      ctx.set(pop().as());
+      return false;
+    }
+
+    @Override
+    public boolean visitValueProperty(String propertyName, Object value,
+        PropertyContext ctx) {
+      if (!data.isNull(propertyName)) {
+        Splittable propertyValue = data.get(propertyName);
+        ctx.set(ValueCodex.decode(ctx.getType(), propertyValue));
+      }
+      return false;
+    }
+
+    private Map<?, ?> decodeObjectKeyMap(Splittable map, Class<?> keyType,
+        Class<?> valueType) {
+      boolean isEncodedKey = Splittable.class.equals(keyType);
+      boolean isEncodedValue = Splittable.class.equals(valueType);
+      boolean isValueValue = Splittable.class.equals(valueType);
+
+      Splittable keyList = map.get(0);
+      Splittable valueList = map.get(1);
+      assert keyList.size() == valueList.size();
+
+      Map<Object, Object> toReturn = new HashMap<Object, Object>(keyList.size());
+      for (int i = 0, j = keyList.size(); i < j; i++) {
+        Object key;
+        if (isEncodedKey) {
+          key = keyList.get(i);
+        } else {
+          key = decode(keyList.get(i), keyType).as();
+        }
+
+        Object value;
+        if (valueList.isNull(i)) {
+          value = null;
+        } else if (isEncodedValue) {
+          value = keyList.get(i);
+        } else if (isValueValue) {
+          value = ValueCodex.decode(valueType, keyList.get(i));
+        } else {
+          value = decode(valueList.get(i), valueType).as();
+        }
+
+        toReturn.put(key, value);
+      }
+      return toReturn;
+    }
+
+    private Map<?, ?> decodeValueKeyMap(Splittable map, Class<?> keyType,
+        Class<?> valueType) {
+      Map<Object, Object> toReturn = new HashMap<Object, Object>();
+
+      boolean isEncodedValue = Splittable.class.equals(valueType);
+      boolean isValueValue = ValueCodex.canDecode(valueType);
+      for (String encodedKey : map.getPropertyKeys()) {
+        Object key = ValueCodex.decode(keyType, encodedKey);
+        Object value;
+        if (map.isNull(encodedKey)) {
+          value = null;
+        } else if (isEncodedValue) {
+          value = map.get(encodedKey);
+        } else if (isValueValue) {
+          value = ValueCodex.decode(valueType, map.get(encodedKey));
+        } else {
+          value = decode(map.get(encodedKey), valueType).as();
+        }
+        toReturn.put(key, value);
+      }
+
+      return toReturn;
+    }
+
+    private AutoBean<?> pop() {
+      dataStack.pop();
+      if (dataStack.isEmpty()) {
+        data = null;
+      } else {
+        data = dataStack.peek();
+      }
+      AutoBean<?> toReturn = beanStack.pop();
+      if (beanStack.isEmpty()) {
+        bean = null;
+      } else {
+        bean = beanStack.peek();
+      }
+      return toReturn;
+    }
+
+    private void push(Splittable data, Class<?> type) {
+      this.data = data;
+      bean = factory.create(type);
+      dataStack.push(data);
+      beanStack.push(bean);
+    }
+  }
+
+  static class Encoder extends AutoBeanVisitor {
+    private Set<AutoBean<?>> seen = new HashSet<AutoBean<?>>();
+    private Stack<StringBuilder> stack = new Stack<StringBuilder>();
+    private StringBuilder sb;
+
+    @Override
+    public void endVisit(AutoBean<?> bean, Context ctx) {
+      if (sb.length() == 0) {
+        // No properties
+        sb.append("{");
+      } else {
+        sb.setCharAt(0, '{');
+      }
+      sb.append("}");
+    }
+
+    @Override
+    public void endVisitReferenceProperty(String propertyName,
+        AutoBean<?> value, PropertyContext ctx) {
+      StringBuilder popped = pop();
+      if (popped.length() > 0) {
+        sb.append(",\"").append(propertyName).append("\":").append(
+            popped.toString());
+      }
+    }
+
+    @Override
+    public boolean visitCollectionProperty(String propertyName,
+        AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+      push(new StringBuilder());
+
+      if (value == null) {
+        return false;
+      }
+
+      Collection<?> collection = value.as();
+      if (collection.isEmpty()) {
+        sb.append("[]");
+        return false;
+      }
+
+      if (ValueCodex.canDecode(ctx.getElementType())) {
+        for (Object element : collection) {
+          sb.append(",").append(ValueCodex.encode(element).getPayload());
+        }
+      } else {
+        boolean isEncoded = Splittable.class.equals(ctx.getElementType());
+        for (Object element : collection) {
+          sb.append(",");
+          if (element == null) {
+            sb.append("null");
+          } else if (isEncoded) {
+            sb.append(((Splittable) element).getPayload());
+          } else {
+            encodeToStringBuilder(sb, element);
+          }
+        }
+      }
+      sb.setCharAt(0, '[');
+      sb.append("]");
+      return false;
+    }
+
+    @Override
+    public boolean visitMapProperty(String propertyName,
+        AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
+      push(new StringBuilder());
+
+      if (value == null) {
+        return false;
+      }
+
+      Map<?, ?> map = value.as();
+      if (map.isEmpty()) {
+        sb.append("{}");
+        return false;
+      }
+
+      boolean isEncodedKey = Splittable.class.equals(ctx.getKeyType());
+      boolean isEncodedValue = Splittable.class.equals(ctx.getValueType());
+      boolean isValueKey = ValueCodex.canDecode(ctx.getKeyType());
+      boolean isValueValue = ValueCodex.canDecode(ctx.getValueType());
+
+      if (isValueKey) {
+        writeValueKeyMap(map, isEncodedValue, isValueValue);
+      } else {
+        writeObjectKeyMap(map, isEncodedKey, isEncodedValue, isValueValue);
+      }
+
+      return false;
+    }
+
+    @Override
+    public boolean visitReferenceProperty(String propertyName,
+        AutoBean<?> value, PropertyContext ctx) {
+      push(new StringBuilder());
+
+      if (value == null) {
+        return false;
+      }
+
+      if (Splittable.class.equals(ctx.getType())) {
+        sb.append(((Splittable) value.as()).getPayload());
+        return false;
+      }
+
+      if (seen.contains(value)) {
+        haltOnCycle();
+      }
+
+      return true;
+    }
+
+    @Override
+    public boolean visitValueProperty(String propertyName, Object value,
+        PropertyContext ctx) {
+      // Skip primitive types whose values are uninteresting.
+      if (value != null) {
+        if (value.equals(ValueCodex.getUninitializedFieldValue(ctx.getType()))) {
+          return false;
+        }
+      }
+      sb.append(",\"").append(propertyName).append("\":").append(
+          ValueCodex.encode(value).getPayload());
+      return false;
+    }
+
+    StringBuilder pop() {
+      StringBuilder toReturn = stack.pop();
+      sb = stack.peek();
+      return toReturn;
+    }
+
+    void push(StringBuilder sb) {
+      stack.push(sb);
+      this.sb = sb;
+    }
+
+    private void encodeToStringBuilder(StringBuilder accumulator, Object value) {
+      push(new StringBuilder());
+      AutoBean<?> bean = AutoBeanUtils.getAutoBean(value);
+      if (!seen.add(bean)) {
+        haltOnCycle();
+      }
+      bean.accept(this);
+      accumulator.append(pop().toString());
+      seen.remove(bean);
+    }
+
+    private void haltOnCycle() {
+      throw new HaltException(new UnsupportedOperationException(
+          "Cycle detected"));
+    }
+
+    /**
+     * Writes a map JSON literal where the keys are object types. This is
+     * encoded as a list of two lists, since it's possible that two distinct
+     * objects have the same encoded form.
+     */
+    private void writeObjectKeyMap(Map<?, ?> map, boolean isEncodedKey,
+        boolean isEncodedValue, boolean isValueValue) {
+      StringBuilder keys = new StringBuilder();
+      StringBuilder values = new StringBuilder();
+
+      for (Map.Entry<?, ?> entry : map.entrySet()) {
+        if (isEncodedKey) {
+          keys.append(",").append(((Splittable) entry.getKey()).getPayload());
+        } else {
+          encodeToStringBuilder(keys.append(","), entry.getKey());
+        }
+
+        if (isEncodedValue) {
+          values.append(",").append(
+              ((Splittable) entry.getValue()).getPayload());
+        } else if (isValueValue) {
+          values.append(",").append(
+              ValueCodex.encode(entry.getValue()).getPayload());
+        } else {
+          encodeToStringBuilder(values.append(","), entry.getValue());
+        }
+      }
+      keys.setCharAt(0, '[');
+      keys.append("]");
+      values.setCharAt(0, '[');
+      values.append("]");
+
+      sb.append("[").append(keys.toString()).append(",").append(
+          values.toString()).append("]");
+    }
+
+    /**
+     * Writes a map JSON literal where the keys are value types.
+     */
+    private void writeValueKeyMap(Map<?, ?> map, boolean isEncodedValue,
+        boolean isValueValue) {
+      for (Map.Entry<?, ?> entry : map.entrySet()) {
+        sb.append(",").append(ValueCodex.encode(entry.getKey()).getPayload()).append(
+            ":");
+        if (isEncodedValue) {
+          sb.append(((Splittable) entry.getValue()).getPayload());
+        } else if (isValueValue) {
+          sb.append(ValueCodex.encode(entry.getValue()).getPayload());
+        } else {
+          encodeToStringBuilder(sb, entry.getValue());
+        }
+      }
+      sb.setCharAt(0, '{');
+      sb.append("}");
+    }
+  }
+
+  /**
+   * Used to stop processing.
+   */
+  static class HaltException extends RuntimeException {
+    public HaltException(RuntimeException cause) {
+      super(cause);
+    }
+
+    @Override
+    public RuntimeException getCause() {
+      return (RuntimeException) super.getCause();
+    }
+  }
+
+  public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz,
+      Splittable data) {
+    return new Decoder(factory).decode(data, clazz);
+  }
+
+  /**
+   * Decode an AutoBeanCodex payload.
+   * 
+   * @param <T> the expected return type
+   * @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>}
+   * @param clazz the expected return type
+   * @param payload a payload string previously generated by
+   *          {@link #encode(AutoBean)}
+   * @return an AutoBean containing the payload contents
+   */
+  public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz,
+      String payload) {
+    Splittable data = StringQuoter.split(payload);
+    return decode(factory, clazz, data);
+  }
+
+  /**
+   * Encodes an AutoBean. The actual payload contents can be retrieved through
+   * {@link Splittable#getPayload()}.
+   * 
+   * @param bean the bean to encode
+   * @return a Splittable that encodes the state of the AutoBean
+   */
+  public static Splittable encode(AutoBean<?> bean) {
+    if (bean == null) {
+      return LazySplittable.NULL;
+    }
+
+    StringBuilder sb = new StringBuilder();
+    encodeForJsoPayload(sb, bean);
+    return new LazySplittable(sb.toString());
+  }
+
+  // ["prop",value,"prop",value, ...]
+  private static void encodeForJsoPayload(StringBuilder sb, AutoBean<?> bean) {
+    Encoder e = new Encoder();
+    e.push(sb);
+    try {
+      bean.accept(e);
+    } catch (HaltException ex) {
+      throw ex.getCause();
+    }
+  }
+
+  private AutoBeanCodex() {
+  }
+}
diff --git a/user/src/com/google/gwt/editor/client/AutoBeanFactory.java b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
similarity index 98%
rename from user/src/com/google/gwt/editor/client/AutoBeanFactory.java
rename to user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
index 872e1b5..d5832e7 100644
--- a/user/src/com/google/gwt/editor/client/AutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.client;
+package com.google.gwt.autobean.shared;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
diff --git a/user/src/com/google/gwt/editor/client/AutoBeanUtils.java b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
similarity index 94%
rename from user/src/com/google/gwt/editor/client/AutoBeanUtils.java
rename to user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
index 2299b9f..129892d 100644
--- a/user/src/com/google/gwt/editor/client/AutoBeanUtils.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.client;
+package com.google.gwt.autobean.shared;
 
 import com.google.gwt.core.client.impl.WeakMapping;
 
@@ -35,7 +35,7 @@
    * although the diff produced is likely meaningless.
    * <p>
    * This will work for both simple and wrapper AutoBeans.
-   *
+   * 
    * @param a an {@link AutoBean}
    * @param b an {@link AutoBean}
    * @return a {@link Map} of differing properties
@@ -85,9 +85,9 @@
       }
 
       private boolean equal(String propertyName, Object previousValue) {
-        return previousValue == null && toReturn.get(propertyName) == null
-            || previousValue != null
-            && previousValue.equals(toReturn.get(propertyName));
+        Object currentValue = toReturn.get(propertyName);
+        return previousValue == null && currentValue == null
+            || previousValue != null && previousValue.equals(currentValue);
       }
     });
     return toReturn;
@@ -97,7 +97,7 @@
    * Returns a map that is a copy of the properties contained in an AutoBean.
    * The returned map is mutable, but editing it will not have any effect on the
    * bean that produced it.
-   *
+   * 
    * @param bean an {@link AutoBean}
    * @return a {@link Map} of the bean's properties
    */
@@ -125,8 +125,7 @@
 
   /**
    * Return the single AutoBean wrapper that is observing the delegate object or
-   * {@code null} if the parameter is {@code null}or not wrapped by
-   * an AutoBean.
+   * {@code null} if the parameter is {@code null}or not wrapped by an AutoBean.
    * 
    * @param delegate a delegate object, or {@code null}
    * @return the {@link AutoBean} wrapper for the delegate, or {@code null}
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java b/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
new file mode 100644
index 0000000..49e81dc
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
@@ -0,0 +1,205 @@
+/*
+ * 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.autobean.shared;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Allows traversal of an AutoBean object graph.
+ */
+public class AutoBeanVisitor {
+  /**
+   * A PropertyContext that describes the parameterization of the Collection
+   * being visited.
+   */
+  public interface CollectionPropertyContext extends PropertyContext {
+    /**
+     * Returns the collection's element type.
+     * 
+     * @return a Class object representing the element type
+     */
+    Class<?> getElementType();
+  }
+
+  /**
+   * Reserved for future expansion to avoid API breaks.
+   */
+  public interface Context {
+  }
+
+  /**
+   * A PropertyContext that describes the parameterization of the Map being
+   * visited.
+   */
+  public interface MapPropertyContext extends PropertyContext {
+    /**
+     * Returns the map's key type.
+     * 
+     * @return a Class object representing the key type
+     */
+    Class<?> getKeyType();
+
+    /**
+     * Returns the map's value type.
+     * 
+     * @return a Class object representing the value type
+     */
+    Class<?> getValueType();
+  }
+
+  /**
+   * Allows properties to be reset.
+   */
+  public interface PropertyContext {
+    /**
+     * Indicates if the {@link #set} method will succeed.
+     * 
+     * @return {@code true} if the property can be set
+     */
+    boolean canSet();
+
+    /**
+     * Returns the expected type of the property.
+     * 
+     * @return a Class object representing the property type
+     */
+    Class<?> getType();
+
+    /**
+     * Sets a property value.
+     * 
+     * @param value the new value
+     */
+    void set(Object value);
+  }
+
+  /**
+   * Called after visiting an {@link AutoBean}.
+   * 
+   * @param bean an {@link AutoBean}
+   * @param ctx a Context
+   */
+  public void endVisit(AutoBean<?> bean, Context ctx) {
+  }
+
+  /**
+   * Called after visiting a reference property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitCollectionProperty(String propertyName,
+      AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+    endVisitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called after visiting a reference property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitMapProperty(String propertyName,
+      AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
+    endVisitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called after visiting a reference property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitReferenceProperty(String propertyName, AutoBean<?> value,
+      PropertyContext ctx) {
+  }
+
+  /**
+   * Called after visiting a value property.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public void endVisitValueProperty(String propertyName, Object value,
+      PropertyContext ctx) {
+  }
+
+  /**
+   * Called when visiting an {@link AutoBean}.
+   * 
+   * @param bean an {@link AutoBean}
+   * @param ctx a Context
+   */
+  public boolean visit(AutoBean<?> bean, Context ctx) {
+    return true;
+  }
+
+  /**
+   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+   * the value only the first time it is encountered.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitCollectionProperty(String propertyName,
+      AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+    return visitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+   * the value only the first time it is encountered.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitMapProperty(String propertyName,
+      AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
+    return visitReferenceProperty(propertyName, value, ctx);
+  }
+
+  /**
+   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+   * the value only the first time it is encountered.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+      PropertyContext ctx) {
+    return true;
+  }
+
+  /**
+   * TODO: document.
+   * 
+   * @param propertyName the property name, as a String
+   * @param value the property value
+   * @param ctx a PropertyContext
+   */
+  public boolean visitValueProperty(String propertyName, Object value,
+      PropertyContext ctx) {
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/Splittable.java b/user/src/com/google/gwt/autobean/shared/Splittable.java
new file mode 100644
index 0000000..0059d3c
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/Splittable.java
@@ -0,0 +1,66 @@
+/*
+ * 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.autobean.shared;
+
+import java.util.List;
+
+/**
+ * This interface provides an abstraction around the underlying data model
+ * (JavaScriptObject, {@code org.json}, or XML) used to encode an AutoBeanCodex
+ * payload.
+ */
+public interface Splittable {
+  /**
+   * Returns a string representation of the data.
+   */
+  String asString();
+
+  /**
+   * Returns the nth element of a list.
+   */
+  Splittable get(int index);
+
+  /**
+   * Returns the named property.
+   */
+  Splittable get(String key);
+
+  /**
+   * Returns a wire-format representation of the data.
+   */
+  String getPayload();
+
+  /**
+   * Returns all keys available in the Splittable. This method may be expensive
+   * to compute.
+   */
+  List<String> getPropertyKeys();
+
+  /**
+   * Indicates if the nth element of a list is null.
+   */
+  boolean isNull(int index);
+
+  /**
+   * Indicates if the named property is null.
+   */
+  boolean isNull(String key);
+
+  /**
+   * Returns the size of the list.
+   */
+  int size();
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/ValueCodex.java b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
similarity index 60%
rename from user/src/com/google/gwt/requestfactory/shared/ValueCodex.java
rename to user/src/com/google/gwt/autobean/shared/ValueCodex.java
index 1763759..19ef456 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -13,9 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.shared;
+package com.google.gwt.autobean.shared;
 
-import com.google.gwt.core.client.JsonUtils;
+import com.google.gwt.autobean.shared.impl.LazySplittable;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -33,26 +34,36 @@
       public BigDecimal decode(Class<?> clazz, String value) {
         return new BigDecimal(value);
       }
+
+      @Override
+      public String toJsonExpression(Object value) {
+        return StringQuoter.quote(((BigDecimal) value).toString());
+      }
     },
     BIG_INTEGER(BigInteger.class) {
       @Override
       public BigInteger decode(Class<?> clazz, String value) {
         return new BigInteger(value);
       }
+
+      @Override
+      public String toJsonExpression(Object value) {
+        return StringQuoter.quote(((BigInteger) value).toString());
+      }
     },
-    BOOLEAN(Boolean.class, boolean.class) {
+    BOOLEAN(Boolean.class, boolean.class, false) {
       @Override
       public Boolean decode(Class<?> clazz, String value) {
         return Boolean.valueOf(value);
       }
     },
-    BYTE(Byte.class, byte.class) {
+    BYTE(Byte.class, byte.class, (byte) 0) {
       @Override
       public Byte decode(Class<?> clazz, String value) {
         return Byte.valueOf(value);
       }
     },
-    CHARACTER(Character.class, char.class) {
+    CHARACTER(Character.class, char.class, (char) 0) {
       @Override
       public Character decode(Class<?> clazz, String value) {
         return value.charAt(0);
@@ -65,11 +76,11 @@
       }
 
       @Override
-      public String encode(Object value) {
+      public String toJsonExpression(Object value) {
         return String.valueOf(((Date) value).getTime());
       }
     },
-    DOUBLE(Double.class, double.class) {
+    DOUBLE(Double.class, double.class, 0d) {
       @Override
       public Double decode(Class<?> clazz, String value) {
         return Double.valueOf(value);
@@ -82,29 +93,35 @@
       }
 
       @Override
-      public String encode(Object value) {
+      public String toJsonExpression(Object value) {
         return String.valueOf(((Enum<?>) value).ordinal());
       }
     },
-    FLOAT(Float.class, float.class) {
+    FLOAT(Float.class, float.class, 0f) {
       @Override
       public Float decode(Class<?> clazz, String value) {
         return Float.valueOf(value);
       }
     },
-    INTEGER(Integer.class, int.class) {
+    INTEGER(Integer.class, int.class, 0) {
       @Override
       public Integer decode(Class<?> clazz, String value) {
         return Integer.valueOf(value);
       }
     },
-    LONG(Long.class, long.class) {
+    LONG(Long.class, long.class, 0L) {
       @Override
       public Long decode(Class<?> clazz, String value) {
         return Long.valueOf(value);
       }
+
+      @Override
+      public String toJsonExpression(Object value) {
+        // Longs cannot be expressed as a JS double
+        return StringQuoter.quote(String.valueOf((Long) value));
+      }
     },
-    SHORT(Short.class, short.class) {
+    SHORT(Short.class, short.class, (short) 0) {
       @Override
       public Short decode(Class<?> clazz, String value) {
         return Short.valueOf(value);
@@ -117,32 +134,34 @@
       }
 
       @Override
-      public String encode(Object value) {
-        return JsonUtils.escapeValue(String.valueOf(value));
+      public String toJsonExpression(Object value) {
+        return StringQuoter.quote((String) value);
       }
     },
-    VOID(Void.class, void.class) {
+    VOID(Void.class, void.class, null) {
       @Override
       public Void decode(Class<?> clazz, String value) {
         return null;
       }
     };
+    private final Object defaultValue;
     private final Class<?> type;
     private final Class<?> primitiveType;
 
     Type(Class<?> objectType) {
-      this(objectType, null);
+      this(objectType, null, null);
     }
 
-    Type(Class<?> objectType, Class<?> primitiveType) {
+    Type(Class<?> objectType, Class<?> primitiveType, Object defaultValue) {
       this.type = objectType;
       this.primitiveType = primitiveType;
+      this.defaultValue = defaultValue;
     }
 
     public abstract Object decode(Class<?> clazz, String value);
 
-    public String encode(Object value) {
-      return String.valueOf(value);
+    public Object getDefaultValue() {
+      return defaultValue;
     }
 
     public Class<?> getPrimitiveType() {
@@ -152,6 +171,10 @@
     public Class<?> getType() {
       return type;
     }
+
+    public String toJsonExpression(Object value) {
+      return String.valueOf(value);
+    }
   }
 
   private static Map<Class<?>, Type> typesByClass = new HashMap<Class<?>, Type>();
@@ -166,53 +189,67 @@
 
   /**
    * Returns true if ValueCodex can operate on values of the given type.
-   *
+   * 
    * @param clazz a Class object
    * @return {@code true} if the given object type can be decoded
    */
   public static boolean canDecode(Class<?> clazz) {
-    return typesByClass.containsKey(clazz);
+    return findType(clazz) != null;
   }
 
-  /**
-   * Convert an encoded representation of a value into a value.
-   * 
-   * @param <T> the type of value desired
-   * @param clazz the type of value desired
-   * @param encoded the encoded representation of the value
-   * @return the value
-   * @throws IllegalArgumentException if <code>clazz</code> is not a supported
-   *           value type
-   */
-  @SuppressWarnings("unchecked")
-  public static <T> T convertFromString(Class<T> clazz, String encoded) {
-    if (encoded == null) {
+  public static <T> T decode(Class<T> clazz, Splittable split) {
+    if (split == null || split == LazySplittable.NULL) {
       return null;
     }
-    return (T) getType(clazz).decode(clazz, encoded);
+    return decode(clazz, split.asString());
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> T decode(Class<T> clazz, String string) {
+    if (string == null) {
+      return null;
+    }
+    return (T) getTypeOrDie(clazz).decode(clazz, string);
+  }
+
+  public static Splittable encode(Object obj) {
+    if (obj == null) {
+      return LazySplittable.NULL;
+    }
+    return new LazySplittable(
+        getTypeOrDie(obj.getClass()).toJsonExpression(obj));
   }
 
   /**
-   * Convert a value into an encoded string representation.
-   * 
-   * @param obj the value to encode
-   * @return an encoded representation of the object
-   * @throws IllegalArgumentException if <code>obj</code> is not of a supported
-   *           type
+   * Returns the uninitialized field value for the given primitive type.
    */
-  public static String encodeForJsonPayload(Object obj) {
-    return getType(obj.getClass()).encode(obj);
+  public static Object getUninitializedFieldValue(Class<?> clazz) {
+    Type type = getTypeOrDie(clazz);
+    if (clazz.equals(type.getPrimitiveType())) {
+      return type.getDefaultValue();
+    }
+    return null;
   }
 
-  private static <T> Type getType(Class<T> clazz) {
+  /**
+   * May return <code>null</code>.
+   */
+  private static <T> Type findType(Class<T> clazz) {
     Type type = typesByClass.get(clazz);
     if (type == null) {
       // Necessary due to lack of Class.isAssignable() in client-side
       if (clazz.getEnumConstants() != null) {
         return Type.ENUM;
       }
-      throw new IllegalArgumentException(clazz.getName());
     }
     return type;
   }
+
+  private static <T> Type getTypeOrDie(Class<T> clazz) {
+    Type toReturn = findType(clazz);
+    if (toReturn == null) {
+      throw new UnsupportedOperationException(clazz.getName());
+    }
+    return toReturn;
+  }
 }
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractAutoBean.java b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
similarity index 89%
rename from user/src/com/google/gwt/editor/client/impl/AbstractAutoBean.java
rename to user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
index a043a8c..95d1926 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractAutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
@@ -13,13 +13,13 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.client.impl;
+package com.google.gwt.autobean.shared.impl;
 
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.AutoBeanVisitor.Context;
 import com.google.gwt.core.client.impl.WeakMapping;
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanUtils;
-import com.google.gwt.editor.client.AutoBeanVisitor;
-import com.google.gwt.editor.client.AutoBeanVisitor.Context;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -37,6 +37,10 @@
    */
   public static class OneShotContext implements Context {
     private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>();
+
+    public boolean hasSeen(AbstractAutoBean<?> bean) {
+      return !seen.add(bean);
+    }
   }
 
   protected static final Object[] EMPTY_OBJECT = new Object[0];
@@ -47,6 +51,7 @@
   protected final Map<String, Object> values;
 
   private boolean frozen;
+
   /**
    * Lazily initialized by {@link #setTag(String, Object)} because not all
    * instances will make use of tags.
@@ -61,10 +66,6 @@
   protected AbstractAutoBean() {
     usingSimplePeer = true;
     values = new HashMap<String, Object>();
-    wrapped = createSimplePeer();
-
-    // Used by AutoBeanUtils
-    WeakMapping.set(wrapped, AutoBean.class.getName(), this);
   }
 
   /**
@@ -79,7 +80,6 @@
     }
     usingSimplePeer = true;
     values = new HashMap<String, Object>(toClone.values);
-    wrapped = createSimplePeer();
 
     if (deep) {
       for (Map.Entry<String, Object> entry : values.entrySet()) {
@@ -89,9 +89,6 @@
         }
       }
     }
-
-    // Used by AutoBeanUtils
-    WeakMapping.set(wrapped, AutoBean.class.getName(), this);
   }
 
   /**
@@ -140,7 +137,7 @@
 
   public void traverse(AutoBeanVisitor visitor, OneShotContext ctx) {
     // Avoid cycles
-    if (!ctx.seen.add(this)) {
+    if (ctx.hasSeen(this)) {
       return;
     }
     if (visitor.visit(this, ctx)) {
@@ -178,7 +175,7 @@
   }
 
   protected void checkWrapped() {
-    if (wrapped == null) {
+    if (wrapped == null && !usingSimplePeer) {
       throw new IllegalStateException("The AutoBean has been unwrapped");
     }
   }
@@ -203,9 +200,17 @@
   }
 
   protected T getWrapped() {
+    if (wrapped == null) {
+      assert usingSimplePeer : "checkWrapped should have failed";
+      wrapped = createSimplePeer();
+    }
     return wrapped;
   }
 
+  protected boolean isUsingSimplePeer() {
+    return usingSimplePeer;
+  }
+
   protected boolean isWrapped(Object obj) {
     return AutoBeanUtils.getAutoBean(obj) != null;
   }
diff --git a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
new file mode 100644
index 0000000..3e804d1
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
@@ -0,0 +1,80 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+
+import java.util.List;
+
+/**
+ * Holds a string payload with the expectation that the object will be used only
+ * for creating a larger payload.
+ */
+public class LazySplittable implements Splittable {
+  public static final Splittable NULL = new LazySplittable("null");
+
+  private final String payload;
+  private Splittable split;
+
+  public LazySplittable(String payload) {
+    this.payload = payload;
+  }
+
+  public String asString() {
+    maybeSplit();
+    return split.asString();
+  }
+
+  public Splittable get(int index) {
+    maybeSplit();
+    return split.get(index);
+  }
+
+  public Splittable get(String key) {
+    maybeSplit();
+    return split.get(key);
+  }
+
+  public String getPayload() {
+    return payload;
+  }
+
+  public List<String> getPropertyKeys() {
+    maybeSplit();
+    return split.getPropertyKeys();
+  }
+
+  public boolean isNull(int index) {
+    maybeSplit();
+    return split.isNull(index);
+  }
+
+  public boolean isNull(String key) {
+    maybeSplit();
+    return split.isNull(key);
+  }
+
+  public int size() {
+    maybeSplit();
+    return split.size();
+  }
+
+  private void maybeSplit() {
+    if (split == null) {
+      split = StringQuoter.split(payload);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
new file mode 100644
index 0000000..9f65e5e
--- /dev/null
+++ b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.autobean.shared.impl;
+
+import com.google.gwt.autobean.server.impl.JsonSplittable;
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
+
+/**
+ * This class has a super-source version with a client-only implementation.
+ */
+public class StringQuoter {
+  /**
+   * Create a quoted JSON string.
+   */
+  public static String quote(String raw) {
+    return ServerSerializationStreamWriter.escapeString(raw);
+  }
+
+  public static Splittable split(String payload) {
+    return JsonSplittable.create(payload);
+  }
+}
diff --git a/user/src/com/google/gwt/core/client/impl/WeakMapping.java b/user/src/com/google/gwt/core/client/impl/WeakMapping.java
index 6afb310..4906f96 100644
--- a/user/src/com/google/gwt/core/client/impl/WeakMapping.java
+++ b/user/src/com/google/gwt/core/client/impl/WeakMapping.java
@@ -46,7 +46,7 @@
     /**
      * The identity hash code of the referent, cached during construction.
      */
-    private int hashCode;
+    private final int hashCode;
 
     public IdentityWeakReference(Object referent, ReferenceQueue<Object> queue) {
       super(referent, queue);
@@ -93,7 +93,7 @@
    * identity. Weak references are used to allow otherwise unreferenced Objects
    * to be garbage collected.
    */
-  private static Map<IdentityWeakReference, HashMap<String, Object>> map = new HashMap<IdentityWeakReference, HashMap<String, Object>>();
+  private static Map<IdentityWeakReference, Map<String, Object>> map = new HashMap<IdentityWeakReference, Map<String, Object>>();
 
   /**
    * A ReferenceQueue used to clean up the map as its keys are
@@ -109,11 +109,11 @@
    * @param key a String key.
    * @return an Object associated with that key on the given instance, or null.
    */
-  public static Object get(Object instance, String key) {
+  public static synchronized Object get(Object instance, String key) {
     cleanup();
 
     Object ref = new IdentityWeakReference(instance, queue);
-    HashMap<String, Object> m = map.get(ref);
+    Map<String, Object> m = map.get(ref);
     if (m == null) {
       return null;
     }
@@ -135,7 +135,7 @@
    *          Object.
    * @throws IllegalArgumentException if instance is a String.
    */
-  public static void set(Object instance, String key, Object value) {
+  public static synchronized void set(Object instance, String key, Object value) {
     cleanup();
 
     if (instance instanceof String) {
@@ -143,7 +143,7 @@
     }
 
     IdentityWeakReference ref = new IdentityWeakReference(instance, queue);
-    HashMap<String, Object> m = map.get(ref);
+    Map<String, Object> m = map.get(ref);
     if (m == null) {
       m = new HashMap<String, Object>();
       map.put(ref, m);
diff --git a/user/src/com/google/gwt/editor/Editor.gwt.xml b/user/src/com/google/gwt/editor/Editor.gwt.xml
index adda3ed..1fd667d 100644
--- a/user/src/com/google/gwt/editor/Editor.gwt.xml
+++ b/user/src/com/google/gwt/editor/Editor.gwt.xml
@@ -14,13 +14,11 @@
 <!-- Editor framework support -->
 <module>
   <inherits name="com.google.gwt.core.Core" />
-  
+
   <source path="client"/>
+  <source path="shared"/>
   <source path="ui/client"/>
-  
-  <generate-with class="com.google.gwt.editor.rebind.AutoBeanFactoryGenerator">
-    <when-type-assignable class="com.google.gwt.editor.client.AutoBeanFactory" />
-  </generate-with>
+
   <generate-with class="com.google.gwt.editor.rebind.SimpleBeanEditorDriverGenerator">
     <when-type-assignable class="com.google.gwt.editor.client.SimpleBeanEditorDriver" />
   </generate-with>
diff --git a/user/src/com/google/gwt/editor/client/AutoBeanVisitor.java b/user/src/com/google/gwt/editor/client/AutoBeanVisitor.java
deleted file mode 100644
index 40c14d1..0000000
--- a/user/src/com/google/gwt/editor/client/AutoBeanVisitor.java
+++ /dev/null
@@ -1,128 +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.editor.client;
-
-/**
- * Allows traversal of an AutoBean object graph.
- */
-public class AutoBeanVisitor {
-  /**
-   * Reserved for future expansion to avoid API breaks.
-   */
-  public interface Context {
-  }
-
-  /**
-   * Allows properties to be reset.
-   */
-  public interface PropertyContext {
-    /**
-     * Indicates if the {@link #set} method will succeed.
-     *
-     * @return {@code true} if the property can be set
-     */
-    boolean canSet();
-
-    /**
-     * If the reference property is a collection, returns the collection's
-     * element type.
-     * 
-     * @return a Class object representing the element type or {@code null} if
-     *         the property is not a collection type
-     */
-    Class<?> getElementType();
-
-    /**
-     * Returns the expected type of the property.
-     *
-     * @return a Class object representing the property type
-     */
-    Class<?> getType();
-
-    /**
-     * Sets a property value.
-     *
-     * @param value the new value
-     */
-    void set(Object value);
-  }
-
-  /**
-   * Called after visiting an {@link AutoBean}.
-   * 
-   * @param bean an {@link AutoBean}
-   * @param ctx a Context
-   */
-  public void endVisit(AutoBean<?> bean, Context ctx) {
-  }
-
-  /**
-   * Called after visiting a reference property.
-   * 
-   * @param propertyName the property name, as a String
-   * @param value the property value
-   * @param ctx a PropertyContext
-   */
-  public void endVisitReferenceProperty(String propertyName, AutoBean<?> value,
-      PropertyContext ctx) {
-  }
-
-  /**
-   * Called after visiting a value property.
-   * 
-   * @param propertyName the property name, as a String
-   * @param value the property value
-   * @param ctx a PropertyContext
-   */
-  public void endVisitValueProperty(String propertyName, Object value,
-      PropertyContext ctx) {
-  }
-
-  /**
-   * Called when visiting an {@link AutoBean}.
-   * 
-   * @param bean an {@link AutoBean}
-   * @param ctx a Context
-   */
-  public boolean visit(AutoBean<?> bean, Context ctx) {
-    return true;
-  }
-
-  /**
-   * Called every time, but {@link #visit(AutoBean, Context)} will be called for
-   * the value only the first time it is encountered.
-   * 
-   * @param propertyName the property name, as a String
-   * @param value the property value
-   * @param ctx a PropertyContext
-   */
-  public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
-      PropertyContext ctx) {
-    return true;
-  }
-
-  /**
-   * TODO: document.
-   * 
-   * @param propertyName the property name, as a String
-   * @param value the property value
-   * @param ctx a PropertyContext
-   */
-  public boolean visitValueProperty(String propertyName, Object value,
-      PropertyContext ctx) {
-    return true;
-  }
-}
diff --git a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
index 1a2c815..5545cd1 100644
--- a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
+++ b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
@@ -20,13 +20,11 @@
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.util.collect.HashMap;
 
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -34,24 +32,19 @@
  */
 public class ModelUtils {
 
-  static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
+  @SuppressWarnings("unchecked")
+  static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
+      Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
+          Enum.class, Number.class, String.class, Void.class)));
 
-  static final Set<String> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<String>(
-      Arrays.asList(Boolean.class.getName(), Character.class.getName(),
-          Class.class.getName(), Date.class.getName(), Enum.class.getName(),
-          Number.class.getName(), String.class.getName(), Void.class.getName())));
+  static final Set<String> VALUE_TYPE_NAMES;
 
   static {
-    Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
-    autoBoxMap.put(byte.class, Byte.class);
-    autoBoxMap.put(char.class, Character.class);
-    autoBoxMap.put(double.class, Double.class);
-    autoBoxMap.put(float.class, Float.class);
-    autoBoxMap.put(int.class, Integer.class);
-    autoBoxMap.put(long.class, Long.class);
-    autoBoxMap.put(short.class, Short.class);
-    autoBoxMap.put(void.class, Void.class);
-    AUTOBOX_MAP = Collections.unmodifiableMap(autoBoxMap);
+    Set<String> names = new HashSet<String>(VALUE_TYPES.size());
+    for (Class<?> clazz : VALUE_TYPES) {
+      names.add(clazz.getName());
+    }
+    VALUE_TYPE_NAMES = Collections.unmodifiableSet(names);
   }
 
   @SuppressWarnings("unchecked")
@@ -109,7 +102,7 @@
       return true;
     }
 
-    for (String valueType : VALUE_TYPES) {
+    for (String valueType : VALUE_TYPE_NAMES) {
       JClassType found = oracle.findType(valueType);
       // null check to accommodate limited mock CompilationStates
       if (found != null && found.isAssignableFrom(classType)) {
@@ -119,11 +112,6 @@
     return false;
   }
 
-  public static Class<?> maybeAutobox(Class<?> domainType) {
-    Class<?> autoBoxType = AUTOBOX_MAP.get(domainType);
-    return autoBoxType == null ? domainType : autoBoxType;
-  }
-
   private ModelUtils() {
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/RequestFactory.gwt.xml b/user/src/com/google/gwt/requestfactory/RequestFactory.gwt.xml
index 9ff3aba..c942293 100644
--- a/user/src/com/google/gwt/requestfactory/RequestFactory.gwt.xml
+++ b/user/src/com/google/gwt/requestfactory/RequestFactory.gwt.xml
@@ -17,6 +17,7 @@
 -->
 <module>
   <inherits name='com.google.gwt.core.Core'/>
+  <inherits name='com.google.gwt.autobean.AutoBean'/>
   <inherits name='com.google.gwt.editor.Editor'/>
   <inherits name='com.google.gwt.http.HTTP'/>
   <inherits name='com.google.gwt.logging.LoggingDisabled'/>
@@ -24,6 +25,7 @@
   <source path="client"/>
   <source path="shared"/>
   <source path="ui/client"/>
+  <super-source path="super" />
   <generate-with
     class="com.google.gwt.requestfactory.rebind.RequestFactoryEditorDriverGenerator">
     <when-type-assignable
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
new file mode 100644
index 0000000..2e6a312
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.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.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.requestfactory.client.DefaultRequestTransport;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+
+/**
+ * A RequestFactory that uses a {@link DefaultRequestTransport} by default.
+ */
+public abstract class AbstractClientRequestFactory extends
+    AbstractRequestFactory {
+  @Override
+  public void initialize(EventBus eventBus) {
+    initialize(eventBus, new DefaultRequestTransport(eventBus));
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
deleted file mode 100644
index 66ec958..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl;
-
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.requestfactory.client.impl.messages.JsonResults;
-import com.google.gwt.requestfactory.client.impl.messages.JsonServerException;
-import com.google.gwt.requestfactory.client.impl.messages.RelatedObjects;
-import com.google.gwt.requestfactory.client.impl.messages.RequestData;
-import com.google.gwt.requestfactory.client.impl.messages.ReturnRecord;
-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.Receiver;
-import com.google.gwt.requestfactory.shared.Request;
-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.WriteOperation;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * <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>
- * Abstract implementation of {@link Request}. Each request stores a
- * {@link DeltaValueStoreJsonImpl}.
- *
- * @param <T> return type
- */
-public abstract class AbstractRequest<T> implements Request<T>,
-    InstanceRequest<EntityProxy, T> {
-
-  /**
-   * Used by generated subtypes.
-   */
-  protected final Set<String> propertyRefs = new HashSet<String>();
-  protected final AbstractRequestContext requestContext;
-  private Receiver<? super T> receiver;
-  private RequestData requestData;
-
-  protected AbstractRequest(AbstractRequestContext requestContext) {
-    this.requestContext = requestContext;
-  }
-
-  public void fire() {
-    requestContext.fire();
-  }
-
-  public void fire(Receiver<? super T> receiver) {
-    to(receiver);
-    fire();
-  }
-
-  /**
-   * Returns the properties.
-   */
-  public Set<String> getPropertyRefs() {
-    return Collections.unmodifiableSet(propertyRefs);
-  }
-
-  public RequestData getRequestData() {
-    if (requestData == null) {
-      requestData = makeRequestData();
-    }
-    return requestData;
-  }
-
-  public void handleResponseText(String responseText) {
-    JsonResults results = JsonResults.fromResults(responseText);
-    JsonServerException cause = results.getException();
-    if (cause != null) {
-      fail(new ServerFailure(cause.getMessage(), cause.getType(),
-          cause.getTrace()));
-      return;
-    }
-    // handle violations
-    JsArray<ReturnRecord> violationsArray = results.getViolations();
-    if (violationsArray != null) {
-      processViolations(violationsArray);
-    } else {
-      requestContext.processSideEffects(results.getSideEffects());
-      processRelated(results.getRelated());
-      if (results.isNullResult()) {
-        // Indicates the server explicitly meant to send a null value
-        succeed(null);
-      } else {
-        handleResult(results.getResult());
-      }
-    }
-  }
-
-  public RequestContext to(Receiver<? super T> receiver) {
-    this.receiver = receiver;
-    return requestContext;
-  }
-
-  /**
-   * This method comes from the {@link InstanceRequest} interface. Instance
-   * methods place the instance in the first parameter slot.
-   */
-  public Request<T> using(EntityProxy instanceObject) {
-    getRequestData().getParameters()[0] = instanceObject;
-    /*
-     * Instance methods enqueue themselves when their using() method is called.
-     * This ensures that the instance parameter will have been set when
-     * AbstractRequestContext.retainArg() is called.
-     */
-    requestContext.addInvocation(this);
-    return this;
-  }
-
-  public Request<T> with(String... propertyRefs) {
-    this.propertyRefs.addAll(Arrays.asList(propertyRefs));
-    return this;
-  }
-
-  /**
-   * This method is called by generated subclasses to process the main return
-   * property of the JSON payload. The return record isn't just a reference to a
-   * persist or update side-effect, so it has to be processed separately.
-   */
-  protected <Q extends EntityProxy> Q decodeReturnObject(Class<Q> clazz,
-      Object obj) {
-    ReturnRecord jso = (ReturnRecord) obj;
-    SimpleEntityProxyId<Q> id = requestContext.getRequestFactory().getId(clazz,
-        jso.getSimpleId());
-    Q proxy = requestContext.processReturnRecord(id, (ReturnRecord) obj,
-        WriteOperation.UPDATE);
-    return proxy;
-  }
-
-  protected <Q extends EntityProxy> void decodeReturnObjectList(
-      Class<Q> elementType, Object obj, Collection<Q> accumulator) {
-    @SuppressWarnings("unchecked")
-    JsArray<ReturnRecord> array = (JsArray<ReturnRecord>) obj;
-    for (int i = 0, j = array.length(); i < j; i++) {
-      ReturnRecord record = array.get(i);
-      if (record == null) {
-        accumulator.add(null);
-        continue;
-      }
-
-      // Decode the individual object
-      Q decoded = decodeReturnObject(elementType, record);
-
-      // Really want Class.isInstance()
-      assert elementType.equals(decoded.stableId().getProxyClass());
-      accumulator.add(decoded);
-    }
-  }
-
-  protected <Q> void decodeReturnValueList(Class<Q> elementType, Object obj,
-      Collection<Q> accumulator) {
-    @SuppressWarnings("unchecked")
-    List<Q> temp = (List<Q>) EntityCodex.decode(List.class, elementType,
-        requestContext, obj);
-    accumulator.addAll(temp);
-  }
-
-  protected void fail(ServerFailure failure) {
-    requestContext.reuse();
-    if (receiver != null) {
-      receiver.onFailure(failure);
-    }
-  }
-
-  /**
-   * Process the response and call {@link #succeed(Object) or
-   * #fail(com.google.gwt.requestfactory.shared.ServerFailure).
-   */
-  protected abstract void handleResult(Object result);
-
-  protected boolean hasReceiver() {
-    return receiver != null;
-  }
-
-  protected abstract RequestData makeRequestData();
-
-  protected void processRelated(RelatedObjects related) {
-    for (String token : related.getHistoryTokens()) {
-      SimpleEntityProxyId<EntityProxy> id = requestContext.getRequestFactory().getProxyId(
-          token);
-      requestContext.processReturnRecord(id, related.getReturnRecord(token));
-    }
-  }
-
-  protected void succeed(T t) {
-    // The user may not have called to()
-    if (receiver != null) {
-      receiver.onSuccess(t);
-    }
-  }
-
-  private void processViolations(JsArray<ReturnRecord> violationsArray) {
-    int length = violationsArray.length();
-    Set<Violation> errors = new HashSet<Violation>(length);
-
-    for (int i = 0; i < length; i++) {
-      ReturnRecord violationRecord = violationsArray.get(i);
-      final EntityProxyId<?> key = requestContext.getRequestFactory().getId(
-          violationRecord.getSchema(), violationRecord.getEncodedId(),
-          violationRecord.getFutureId());
-
-      HashMap<String, String> violations = new HashMap<String, String>();
-      assert violationRecord.hasViolations();
-      violationRecord.fillViolations(violations);
-
-      for (Map.Entry<String, String> entry : violations.entrySet()) {
-        final String path = entry.getKey();
-        final String message = entry.getValue();
-        errors.add(new Violation() {
-          public String getMessage() {
-            return message;
-          }
-
-          public String getPath() {
-            return path;
-          }
-
-          public EntityProxyId<?> getProxyId() {
-            return key;
-          }
-        });
-      }
-    }
-
-    requestContext.reuse();
-    requestContext.addErrors(errors);
-    if (receiver != null) {
-      receiver.onViolation(errors);
-    }
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestContext.java
deleted file mode 100644
index 2dbfd976..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestContext.java
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl;
-
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanUtils;
-import com.google.gwt.editor.client.AutoBeanVisitor;
-import com.google.gwt.requestfactory.client.impl.messages.RequestContentData;
-import com.google.gwt.requestfactory.client.impl.messages.ReturnRecord;
-import com.google.gwt.requestfactory.client.impl.messages.SideEffects;
-import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyChange;
-import com.google.gwt.requestfactory.shared.Receiver;
-import com.google.gwt.requestfactory.shared.RequestContext;
-import com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver;
-import com.google.gwt.requestfactory.shared.ServerFailure;
-import com.google.gwt.requestfactory.shared.ValueCodex;
-import com.google.gwt.requestfactory.shared.Violation;
-import com.google.gwt.requestfactory.shared.WriteOperation;
-import com.google.gwt.requestfactory.shared.impl.Constants;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Base implementations for RequestContext services.
- */
-public class AbstractRequestContext implements RequestContext {
-  private static final String PARENT_OBJECT = "parentObject";
-
-  private List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
-  private boolean locked;
-  private final AbstractRequestFactory requestFactory;
-  /**
-   * A map of all EntityProxies that the RequestContext has interacted with.
-   * Objects are placed into this map by being passed into {@link #edit} or as
-   * an invocation argument.
-   */
-  private final Map<SimpleEntityProxyId<?>, AutoBean<?>> editedProxies = new LinkedHashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
-  private Set<Violation> errors = new LinkedHashSet<Violation>();
-  /**
-   * A map that contains the canonical instance of an entity to return in the
-   * return graph, since this is built from scratch.
-   */
-  private final Map<SimpleEntityProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
-
-  protected AbstractRequestContext(AbstractRequestFactory factory) {
-    this.requestFactory = factory;
-  }
-
-  /**
-   * Create a new object, with an ephemeral id.
-   */
-  public <T extends EntityProxy> T create(Class<T> clazz) {
-    checkLocked();
-
-    AutoBean<T> created = requestFactory.createEntityProxy(clazz,
-        requestFactory.allocateId(clazz));
-    return takeOwnership(created);
-  }
-
-  public <T extends EntityProxy> T edit(T object) {
-    AutoBean<T> bean = checkStreamsNotCrossed(object);
-
-    checkLocked();
-
-    @SuppressWarnings("unchecked")
-    AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(object.stableId());
-    if (previouslySeen != null && !previouslySeen.isFrozen()) {
-      /*
-       * If we've seen the object before, it might be because it was passed in
-       * as a method argument. This does not guarantee its mutability, so check
-       * that here before returning the cached object.
-       */
-      return previouslySeen.as();
-    }
-
-    // Create editable copies
-    AutoBean<T> parent = bean;
-    bean = cloneBeanAndCollections(bean);
-    bean.setTag(PARENT_OBJECT, parent);
-    return takeOwnership(bean);
-  }
-
-  /**
-   * Make sure there's a default receiver so errors don't get dropped. This
-   * behavior should be revisited when chaining is supported, depending on
-   * whether or not chained invocations can fail independently.
-   */
-  public void fire() {
-    boolean needsReceiver = true;
-    for (AbstractRequest<?> request : invocations) {
-      if (request.hasReceiver()) {
-        needsReceiver = false;
-        break;
-      }
-    }
-
-    if (needsReceiver) {
-      doFire(new Receiver<Void>() {
-        @Override
-        public void onSuccess(Void response) {
-          // Don't care
-        }
-      });
-    } else {
-      doFire(null);
-    }
-  }
-
-  public void fire(final Receiver<Void> receiver) {
-    if (receiver == null) {
-      throw new IllegalArgumentException();
-    }
-    doFire(receiver);
-  }
-
-  public AbstractRequestFactory getRequestFactory() {
-    return requestFactory;
-  }
-
-  public boolean isChanged() {
-    /*
-     * NB: Don't use the presence of ephemeral objects for this test.
-     * 
-     * Diff the objects until one is found to be different. It's not just a
-     * simple flag-check because of the possibility of "unmaking" a change, per
-     * the JavaDoc.
-     */
-    for (AutoBean<?> bean : editedProxies.values()) {
-      AutoBean<?> previous = bean.getTag(PARENT_OBJECT);
-      if (previous == null) {
-        // Compare to empty object
-        Class<?> proxyClass = ((EntityProxy) bean.as()).stableId().getProxyClass();
-        previous = getRequestFactory().getAutoBeanFactory().create(proxyClass);
-      }
-      if (!AutoBeanUtils.diff(previous, bean).isEmpty()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public boolean isLocked() {
-    return locked;
-  }
-
-  public void reuse() {
-    freezeEntities(false);
-    locked = false;
-  }
-
-  /**
-   * Called by individual invocations to aggregate all errors.
-   */
-  protected void addErrors(Collection<Violation> errors) {
-    this.errors.addAll(errors);
-  }
-
-  /**
-   * Called by generated subclasses to enqueue a method invocation.
-   */
-  protected void addInvocation(AbstractRequest<?> request) {
-    if (invocations.size() > 0) {
-      // TODO(bobv): Upgrade wire protocol and server to handle chains
-      throw new IllegalStateException("Method chaining not implemented");
-    }
-    invocations.add(request);
-    for (Object arg : request.getRequestData().getParameters()) {
-      retainArg(arg);
-    }
-  }
-
-  /**
-   * Creates or retrieves a new canonical AutoBean to represent the given id in
-   * the returned payload.
-   */
-  <Q extends EntityProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
-      SimpleEntityProxyId<Q> id) {
-    assert !id.isEphemeral();
-
-    @SuppressWarnings("unchecked")
-    AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
-    if (bean == null) {
-      Class<Q> proxyClass = id.getProxyClass();
-      bean = requestFactory.createEntityProxy(proxyClass, id);
-      returnedProxies.put(id, bean);
-    }
-
-    return bean;
-  }
-
-  /**
-   * Create a new EntityProxy from a snapshot in the return payload.
-   * 
-   * @param id the EntityProxyId of the object
-   * @param returnRecord the JSON map containing property/value pairs
-   * @param operations the WriteOperation eventns to broadcast over the EventBus
-   */
-  <Q extends EntityProxy> Q processReturnRecord(SimpleEntityProxyId<Q> id,
-      final ReturnRecord returnRecord, WriteOperation... operations) {
-
-    AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
-
-    // Apply updates
-    toMutate.accept(new AutoBeanVisitor() {
-      @Override
-      public boolean visitReferenceProperty(String propertyName,
-          AutoBean<?> value, PropertyContext ctx) {
-        if (ctx.canSet()) {
-          if (returnRecord.hasProperty(propertyName)) {
-            Object raw = returnRecord.get(propertyName);
-            if (returnRecord.isNull(propertyName)) {
-              ctx.set(null);
-            } else {
-              Object decoded = EntityCodex.decode(ctx.getType(),
-                  ctx.getElementType(), AbstractRequestContext.this, raw);
-              ctx.set(decoded);
-            }
-          }
-        }
-        return false;
-      }
-
-      @Override
-      public boolean visitValueProperty(String propertyName, Object value,
-          PropertyContext ctx) {
-        if (ctx.canSet()) {
-          if (returnRecord.hasProperty(propertyName)) {
-            Object raw = returnRecord.get(propertyName);
-            if (returnRecord.isNull(propertyName)) {
-              ctx.set(null);
-            } else {
-              Object decoded = ValueCodex.convertFromString(ctx.getType(),
-                  String.valueOf(raw));
-              ctx.set(decoded);
-            }
-          }
-        }
-        return false;
-      }
-    });
-
-    // Finished applying updates, freeze the bean
-    makeImmutable(toMutate);
-    Q proxy = toMutate.as();
-
-    /*
-     * Notify subscribers if the object differs from when it first came into the
-     * RequestContext.
-     */
-    if (operations != null) {
-      for (WriteOperation op : operations) {
-        if (op.equals(WriteOperation.UPDATE)
-            && !requestFactory.hasVersionChanged(id, returnRecord.getVersion())) {
-          // No updates if the server reports no change
-          continue;
-        }
-        requestFactory.getEventBus().fireEventFromSource(
-            new EntityProxyChange<EntityProxy>(proxy, op), id.getProxyClass());
-      }
-    }
-    return proxy;
-  }
-
-  /**
-   * Process a SideEffects message.
-   */
-  void processSideEffects(SideEffects sideEffects) {
-    JsArray<ReturnRecord> persisted = sideEffects.getPersist();
-    if (persisted != null) {
-      processReturnRecords(persisted, WriteOperation.PERSIST);
-    }
-    JsArray<ReturnRecord> updated = sideEffects.getUpdate();
-    if (updated != null) {
-      processReturnRecords(updated, WriteOperation.UPDATE);
-    }
-    JsArray<ReturnRecord> deleted = sideEffects.getDelete();
-    if (deleted != null) {
-      processReturnRecords(deleted, WriteOperation.DELETE);
-    }
-  }
-
-  private void checkLocked() {
-    if (locked) {
-      throw new IllegalStateException("A request is already in progress");
-    }
-  }
-
-  /**
-   * This method checks that a proxy object is either immutable, or already
-   * edited by this context.
-   */
-  private <T> AutoBean<T> checkStreamsNotCrossed(T object) {
-    AutoBean<T> bean = AutoBeanUtils.getAutoBean(object);
-    if (bean == null) {
-      // Unexpected; some kind of foreign implementation?
-      throw new IllegalArgumentException(object.getClass().getName());
-    }
-
-    RequestContext context = bean.getTag(EntityProxyCategory.REQUEST_CONTEXT);
-    if (!bean.isFrozen() && context != this) {
-      /*
-       * This means something is way off in the weeds. If a bean is editable,
-       * it's supposed to be associated with a RequestContext.
-       */
-      assert context != null : "Unfrozen bean with null RequestContext";
-
-      /*
-       * Already editing the object in another context or it would have been in
-       * the editing map.
-       */
-      throw new IllegalArgumentException("Attempting to edit an EntityProxy"
-          + " previously edited by another RequestContext");
-    }
-    return bean;
-  }
-
-  /**
-   * Shallow-clones an autobean and makes duplicates of the collection types. A
-   * regular {@link AutoBean#clone} won't duplicate reference properties.
-   */
-  private <T> AutoBean<T> cloneBeanAndCollections(AutoBean<T> toClone) {
-    AutoBean<T> clone = toClone.clone(false);
-    clone.accept(new AutoBeanVisitor() {
-      @Override
-      public boolean visitReferenceProperty(String propertyName,
-          AutoBean<?> value, PropertyContext ctx) {
-        if (value != null) {
-          if (List.class == ctx.getType()) {
-            ctx.set(new ArrayList<Object>((List<?>) value.as()));
-          } else if (Set.class == ctx.getType()) {
-            ctx.set(new HashSet<Object>((Set<?>) value.as()));
-          }
-        }
-        return false;
-      }
-    });
-    return clone;
-  }
-
-  private void doFire(final Receiver<Void> receiver) {
-    checkLocked();
-    locked = true;
-
-    freezeEntities(true);
-
-    String payload = makePayload();
-
-    requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
-      public void onTransportFailure(String message) {
-        ServerFailure failure = new ServerFailure(message, null, null);
-        try {
-          // TODO: chained methods
-          assert invocations.size() == 1;
-          invocations.get(0).fail(failure);
-          if (receiver != null) {
-            receiver.onFailure(failure);
-          }
-        } finally {
-          postRequestCleanup();
-        }
-      }
-
-      public void onTransportSuccess(String payload) {
-        try {
-          // TODO: chained methods
-          assert invocations.size() == 1;
-          invocations.get(0).handleResponseText(payload);
-
-          if (receiver != null) {
-            if (errors.isEmpty()) {
-              receiver.onSuccess(null);
-              // After success, shut down the context
-              editedProxies.clear();
-              invocations.clear();
-              returnedProxies.clear();
-            } else {
-              receiver.onViolation(errors);
-            }
-          }
-        } finally {
-          postRequestCleanup();
-        }
-      }
-    });
-  }
-
-  /**
-   * Set the frozen status of all EntityProxies owned by this context.
-   */
-  private void freezeEntities(boolean frozen) {
-    for (AutoBean<?> bean : editedProxies.values()) {
-      bean.setFrozen(frozen);
-    }
-  }
-
-  /**
-   * Make an EntityProxy immutable.
-   */
-  private void makeImmutable(final AutoBean<? extends EntityProxy> toMutate) {
-    // Always diff'ed against itself, producing a no-op
-    toMutate.setTag(PARENT_OBJECT, toMutate);
-    // Act with entity-identity semantics
-    toMutate.setTag(EntityProxyCategory.REQUEST_CONTEXT, null);
-    toMutate.setFrozen(true);
-  }
-
-  /**
-   * Assemble all of the state that has been accumulated in this context. This
-   * includes:
-   * <ul>
-   * <li>Diffs accumulated on objects passed to {@link #edit}.
-   * <li>Invocations accumulated as Request subtypes passed to
-   * {@link #addInvocation}.
-   * </ul>
-   */
-  private String makePayload() {
-    // TODO: Chained invocations
-    assert invocations.size() == 1 : "addInvocation() should have failed";
-
-    // Produces the contentData payload fragment
-    RequestContentData data = new RequestContentData();
-
-    // Compute deltas for each entity seen by the context
-    for (AutoBean<?> currentView : editedProxies.values()) {
-      boolean isPersist = false;
-      @SuppressWarnings("unchecked")
-      SimpleEntityProxyId<EntityProxy> stableId = EntityProxyCategory.stableId((AutoBean<EntityProxy>) currentView);
-
-      // Encoded string representations of the properties
-      Map<String, String> encoded = new LinkedHashMap<String, String>();
-
-      {
-        // Find the object to compare against
-        AutoBean<?> parent = currentView.getTag(PARENT_OBJECT);
-        if (parent == null) {
-          // Newly-created object, use a blank object to compare against
-          parent = requestFactory.createEntityProxy(stableId.getProxyClass(),
-              stableId);
-
-          // Newly-created objects go into the persist operation bucket
-          isPersist = true;
-          // The ephemeral id is passed to the server
-          String clientId = String.valueOf(stableId.getClientId());
-          encoded.put(Constants.ENCODED_ID_PROPERTY,
-              ValueCodex.encodeForJsonPayload(clientId));
-        } else {
-          // Requests involving existing objects use the persisted id
-          encoded.put(Constants.ENCODED_ID_PROPERTY,
-              ValueCodex.encodeForJsonPayload(stableId.getServerId()));
-        }
-
-        // Compute the diff
-        Map<String, Object> diff = AutoBeanUtils.diff(parent, currentView);
-        for (Map.Entry<String, Object> entry : diff.entrySet()) {
-          // Make JSON representations of the new values
-          encoded.put(entry.getKey(),
-              EntityCodex.encodeForJsonPayload(entry.getValue()));
-        }
-      }
-
-      // Append the payload fragment to the correct bucket
-      String typeToken = requestFactory.getTypeToken(stableId.getProxyClass());
-      if (isPersist) {
-        data.addPersist(typeToken, encoded);
-      } else {
-        data.addUpdate(typeToken, encoded);
-      }
-    }
-
-    AbstractRequest<?> request = invocations.get(0);
-    // Known issue that the data is double-quoted
-    Map<String, String> requestMap = request.getRequestData().getRequestMap(
-        data.toJson());
-    String string = RequestContentData.flattenKeysToExpressions(requestMap);
-    return string;
-  }
-
-  /**
-   * Delete state that's no longer required.
-   */
-  private void postRequestCleanup() {
-    errors.clear();
-  }
-
-  /**
-   * Process an array of ReturnRecords, delegating to
-   * {@link #processReturnRecord}.
-   */
-  private void processReturnRecords(JsArray<ReturnRecord> records,
-      WriteOperation operations) {
-    for (int i = 0, j = records.length(); i < j; i++) {
-      ReturnRecord record = records.get(i);
-      SimpleEntityProxyId<EntityProxy> id = requestFactory.getId(
-          record.getSchema(), record.getEncodedId(), record.getFutureId());
-      processReturnRecord(id, record, operations);
-    }
-  }
-
-  /**
-   * Ensures that any method arguments are retained in the context's sphere of
-   * influence.
-   */
-  private void retainArg(Object arg) {
-    if (arg instanceof Iterable<?>) {
-      for (Object o : (Iterable<?>) arg) {
-        retainArg(o);
-      }
-    } else if (arg instanceof EntityProxy) {
-      // Calling edit will validate and set up the tracking we need
-      edit((EntityProxy) arg);
-    }
-  }
-
-  /**
-   * Make the EnityProxy bean edited and owned by this RequestContext.
-   */
-  private <T extends EntityProxy> T takeOwnership(AutoBean<T> bean) {
-    editedProxies.put(EntityProxyCategory.stableId(bean), bean);
-    bean.setTag(EntityProxyCategory.REQUEST_CONTEXT,
-        AbstractRequestContext.this);
-    return bean.as();
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactory.java
deleted file mode 100644
index bcd026c..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactory.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl;
-
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanFactory;
-import com.google.gwt.editor.client.AutoBeanUtils;
-import com.google.gwt.event.shared.EventBus;
-import com.google.gwt.requestfactory.client.DefaultRequestTransport;
-import com.google.gwt.requestfactory.client.impl.messages.RequestData;
-import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.Request;
-import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.requestfactory.shared.RequestTransport;
-
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * 
- */
-public abstract class AbstractRequestFactory implements RequestFactory {
-  protected static final String EPHEMERAL_SEPARATOR = "@IS@";
-  protected static final String TOKEN_SEPARATOR = "@NO@";
-  protected static final int ID_TOKEN_INDEX = 0;
-  protected static final int TYPE_TOKEN_INDEX = 1;
-  private static final int MAX_VERSION_ENTRIES = 10000;
-
-  public static String getHistoryToken(EntityProxy proxy) {
-    AutoBean<EntityProxy> bean = AutoBeanUtils.getAutoBean(proxy);
-    String historyToken = EntityProxyCategory.requestFactory(bean).getHistoryToken(
-        proxy.stableId());
-    return historyToken;
-  }
-
-  private EventBus eventBus;
-
-  /**
-   * Maps ephemeral history tokens to an id object. This canonicalizing mapping
-   * resolves the problem of EntityProxyIds hashcodes changing after persist.
-   * Only ids that are created in the RequestFactory are stored here.
-   */
-  /*
-   * All of the ephemeral id-tracking could be moved into RequestContext if we
-   * allowed ephemeral history tokens only to be used with the RequestConext
-   * where the create method was called.
-   */
-  private final Map<String, SimpleEntityProxyId<?>> ephemeralIds = new HashMap<String, SimpleEntityProxyId<?>>();
-  private final Map<String, Integer> version = new LinkedHashMap<String, Integer>(
-      16, 0.75f, true) {
-    @Override
-    protected boolean removeEldestEntry(Entry<String, Integer> eldest) {
-      return size() > MAX_VERSION_ENTRIES;
-    }
-  };
-  private RequestTransport transport;
-
-  /**
-   * Allocates an ephemeral proxy id. This object is only valid for the lifetime
-   * of the RequestFactory.
-   */
-  public <P extends EntityProxy> SimpleEntityProxyId<P> allocateId(
-      Class<P> clazz) {
-    SimpleEntityProxyId<P> toReturn = new SimpleEntityProxyId<P>(clazz,
-        ephemeralIds.size() + 1);
-    ephemeralIds.put(getHistoryToken(toReturn), toReturn);
-    return toReturn;
-  }
-
-  /**
-   * Creates a new EntityProxy with an assigned ID.
-   */
-  public <T extends EntityProxy> AutoBean<T> createEntityProxy(Class<T> clazz,
-      SimpleEntityProxyId<T> id) {
-    AutoBean<T> created = getAutoBeanFactory().create(clazz);
-    if (created == null) {
-      throw new IllegalArgumentException("Unknown EntityProxy type "
-          + clazz.getName());
-    }
-    created.setTag(EntityProxyCategory.REQUEST_FACTORY, this);
-    created.setTag(EntityProxyCategory.STABLE_ID, id);
-    return created;
-  }
-
-  public <P extends EntityProxy> Request<P> find(final EntityProxyId<P> proxyId) {
-    if (((SimpleEntityProxyId<P>) proxyId).isEphemeral()) {
-      throw new IllegalArgumentException("Cannot fetch unpersisted entity");
-    }
-
-    AbstractRequestContext context = new AbstractRequestContext(
-        AbstractRequestFactory.this);
-    return new AbstractRequest<P>(context) {
-      {
-        requestContext.addInvocation(this);
-      }
-
-      @Override
-      protected void handleResult(Object result) {
-        succeed(decodeReturnObject(proxyId.getProxyClass(), result));
-      }
-
-      @Override
-      protected RequestData makeRequestData() {
-        return new RequestData(
-            "com.google.gwt.requestfactory.client.impl.FindRequest::find",
-            new Object[] {getHistoryToken(proxyId)}, propertyRefs);
-      }
-    };
-  }
-
-  public EventBus getEventBus() {
-    return eventBus;
-  }
-
-  public String getHistoryToken(Class<? extends EntityProxy> clazz) {
-    return getTypeToken(clazz);
-  }
-
-  public String getHistoryToken(EntityProxyId<?> proxy) {
-    SimpleEntityProxyId<?> id = (SimpleEntityProxyId<?>) proxy;
-    if (id.isEphemeral()) {
-      return id.getClientId() + EPHEMERAL_SEPARATOR
-          + getHistoryToken(proxy.getProxyClass());
-    } else {
-      return id.getServerId() + TOKEN_SEPARATOR
-          + getHistoryToken(proxy.getProxyClass());
-    }
-  }
-
-  public Class<? extends EntityProxy> getProxyClass(String historyToken) {
-    String[] parts = historyToken.split(TOKEN_SEPARATOR);
-    if (parts.length == 2) {
-      return getTypeFromToken(parts[TYPE_TOKEN_INDEX]);
-    }
-    parts = historyToken.split(EPHEMERAL_SEPARATOR);
-    if (parts.length == 2) {
-      return getTypeFromToken(parts[TYPE_TOKEN_INDEX]);
-    }
-    return getTypeFromToken(historyToken);
-  }
-
-  public <P extends EntityProxy> SimpleEntityProxyId<P> getProxyId(
-      String historyToken) {
-    String[] parts = historyToken.split(TOKEN_SEPARATOR);
-    if (parts.length == 2) {
-      return getId(parts[TYPE_TOKEN_INDEX], parts[ID_TOKEN_INDEX]);
-    }
-    parts = historyToken.split(EPHEMERAL_SEPARATOR);
-    if (parts.length == 2) {
-      @SuppressWarnings("unchecked")
-      SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(historyToken);
-
-      /*
-       * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
-       * order to get here, the user would have to get an unpersisted history
-       * token and attempt to use it with a different RequestFactory instance.
-       * This could occur if an ephemeral token was bookmarked. In this case,
-       * we'll create a token, however it will never match anything.
-       */
-      if (toReturn == null) {
-        Class<P> clazz = checkTypeToken(parts[TYPE_TOKEN_INDEX]);
-        toReturn = new SimpleEntityProxyId<P>(clazz, -1 * ephemeralIds.size());
-        ephemeralIds.put(historyToken, toReturn);
-      }
-
-      return toReturn;
-    }
-    throw new IllegalArgumentException(historyToken);
-  }
-
-  public RequestTransport getRequestTransport() {
-    return transport;
-  }
-
-  public void initialize(EventBus eventBus) {
-    initialize(eventBus, new DefaultRequestTransport(eventBus));
-  }
-
-  public void initialize(EventBus eventBus, RequestTransport transport) {
-    this.eventBus = eventBus;
-    this.transport = transport;
-  }
-
-  /**
-   * Implementations of EntityProxies are provided by an AutoBeanFactory, which
-   * is itself a generated type.
-   */
-  protected abstract AutoBeanFactory getAutoBeanFactory();
-
-  /**
-   * Create or retrieve a SimpleEntityProxyId.
-   */
-  protected <P extends EntityProxy> SimpleEntityProxyId<P> getId(
-      Class<P> clazz, String serverId) {
-    return getId(getTypeToken(clazz), serverId);
-  }
-
-  /**
-   * Create or retrieve a SimpleEntityProxyId.
-   */
-  protected <P extends EntityProxy> SimpleEntityProxyId<P> getId(
-      String typeToken, String serverId) {
-    return getId(typeToken, serverId, null);
-  }
-
-  /**
-   * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
-   * are specified and the id is ephemeral, it will be updated with the server
-   * id.
-   */
-  protected <P extends EntityProxy> SimpleEntityProxyId<P> getId(
-      String typeToken, String serverId, String clientId) {
-    /*
-     * If there's a clientId, that probably means we've just created a brand-new
-     * EntityProxy or have just persisted something on the server.
-     */
-    if (clientId != null) {
-      // Try a cache lookup for the ephemeral key
-      String ephemeralKey = clientId + EPHEMERAL_SEPARATOR + typeToken;
-      @SuppressWarnings("unchecked")
-      SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(ephemeralKey);
-      // Cache hit based on client id
-      if (toReturn != null) {
-        // If it's ephemeral, see if we have a serverId and save it
-        if (toReturn.isEphemeral()) {
-          // Sanity check
-          assert toReturn.getProxyClass().equals(getTypeFromToken(typeToken));
-
-          // TODO: This happens when objects fail to validate on create
-          if (!"null".equals(serverId)) {
-            /*
-             * Record the server id so a later "find" operation will have an
-             * equal stableId.
-             */
-            toReturn.setServerId(serverId);
-            String serverKey = serverId + TOKEN_SEPARATOR + typeToken;
-            ephemeralIds.put(serverKey, toReturn);
-          }
-        }
-        return toReturn;
-      }
-    }
-
-    // Should never get this far without a server id
-    assert serverId != null : "serverId";
-
-    String serverKey = serverId + TOKEN_SEPARATOR + typeToken;
-    @SuppressWarnings("unchecked")
-    SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(serverKey);
-    if (toReturn != null) {
-      // A cache hit for a locally-created object that has been persisted
-      return toReturn;
-    }
-
-    /*
-     * No existing id, so it was never an ephemeral id created by this
-     * RequestFactory, so we don't need to record it. This should be the normal
-     * case for read-dominated applications.
-     */
-    Class<P> clazz = getTypeFromToken(typeToken);
-    return new SimpleEntityProxyId<P>(clazz, serverId);
-  }
-
-  protected abstract <P extends EntityProxy> Class<P> getTypeFromToken(
-      String typeToken);
-
-  protected abstract String getTypeToken(Class<?> clazz);
-
-  /**
-   * Used by {@link AbstractRequestContext} to quiesce update events for objects
-   * that haven't truly changed.
-   */
-  protected boolean hasVersionChanged(SimpleEntityProxyId<?> id,
-      int observedVersion) {
-    Integer existingVersion = version.get(id.getServerId());
-    // Return true if we haven't seen this before or the versions differ
-    boolean toReturn = existingVersion == null
-        || !existingVersion.equals(observedVersion);
-    if (toReturn) {
-      version.put(id.getServerId(), observedVersion);
-    }
-    return toReturn;
-  }
-
-  private <P> Class<P> checkTypeToken(String token) {
-    @SuppressWarnings("unchecked")
-    Class<P> clazz = (Class<P>) getTypeFromToken(token);
-    if (clazz == null) {
-      throw new IllegalArgumentException("Unknnown type");
-    }
-    return clazz;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/EntityCodex.java b/user/src/com/google/gwt/requestfactory/client/impl/EntityCodex.java
deleted file mode 100644
index 64e0a1f..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/EntityCodex.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.core.client.JsArrayString;
-import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.ValueCodex;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Analogous to {@link ValueCodex}, but for object types.
- */
-public class EntityCodex {
-  /**
-   * Collection support is limited to value types and resolving ids.
-   */
-  public static Object decode(Class<?> type, Class<?> elementType,
-      final AbstractRequestContext requestContext, Object jso) {
-    if (jso == null) {
-      return null;
-    }
-
-    // Collection support
-    if (elementType != null) {
-      Collection<Object> collection = null;
-      if (List.class == type) {
-        collection = new ArrayList<Object>();
-      } else if (Set.class == type) {
-        collection = new HashSet<Object>();
-      }
-
-      // OK, here's a Java typesystem wart, we need an JsAny type
-      if (ValueCodex.canDecode(elementType)) {
-        @SuppressWarnings("unchecked")
-        JsArray<JavaScriptObject> array = (JsArray<JavaScriptObject>) jso;
-        for (int i = 0, j = array.length(); i < j; i++) {
-          if (isNull(array, i)) {
-            collection.add(null);
-          } else {
-            // Use getString() to make DevMode not complain about primitives
-            Object element = ValueCodex.convertFromString(elementType,
-                getString(array, i));
-            collection.add(element);
-          }
-        }
-      } else {
-        JsArrayString array = (JsArrayString) jso;
-        for (int i = 0, j = array.length(); i < j; i++) {
-          Object element = decode(elementType, null, requestContext,
-              array.get(i));
-          collection.add(element);
-        }
-      }
-      return collection;
-    }
-
-    // Really want EntityProxy.isAssignableFrom(type)
-    if (requestContext.getRequestFactory().getTypeToken(type) != null) {
-      EntityProxyId<?> id = requestContext.getRequestFactory().getProxyId(
-          (String) jso);
-      return requestContext.getProxyForReturnPayloadGraph(
-          (SimpleEntityProxyId<?>) id).as();
-    }
-
-    // Fall back to values
-    return ValueCodex.convertFromString(type, String.valueOf(jso));
-  }
-
-  /**
-   * Create a wire-format representation of an object.
-   */
-  public static String encodeForJsonPayload(Object value) {
-    if (value == null) {
-      return "null";
-    }
-
-    if (value instanceof Iterable<?>) {
-      StringBuffer toReturn = new StringBuffer();
-      toReturn.append('[');
-      boolean first = true;
-      for (Object val : ((Iterable<?>) value)) {
-        if (!first) {
-          toReturn.append(',');
-        } else {
-          first = false;
-        }
-        toReturn.append(encodeForJsonPayload(val));
-      }
-      toReturn.append(']');
-      return toReturn.toString();
-    }
-
-    if (value instanceof EntityProxy) {
-      String historyToken = AbstractRequestFactory.getHistoryToken((EntityProxy) value);
-      return "\"" + historyToken + "\"";
-    }
-
-    return ValueCodex.encodeForJsonPayload(value);
-  }
-
-  private static native String getString(JavaScriptObject array, int index) /*-{
-    return String(array[index]);
-  }-*/;
-
-  /**
-   * Looks for an explicit null value.
-   */
-  private static native boolean isNull(JavaScriptObject array, int index) /*-{
-    return array[index] === null;
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/JsonResults.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/JsonResults.java
deleted file mode 100644
index 9129d39..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/JsonResults.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.core.client.JsonUtils;
-
-/**
- * JSO to hold result and related objects.
- */
-public class JsonResults extends JavaScriptObject {
-
-  public static JsonResults fromResults(String json) {
-    // Avoid a compiler bug that occurs when this is all on one line.
-    // Bug found in Mac OS X 10.5.8 using javac 1.5.0_24.
-    JavaScriptObject results = JsonUtils.safeEval(json);
-    return results.cast();
-  }
-
-  protected JsonResults() {
-  }
-
-  public final native JsonServerException getException() /*-{
-    return this.exception || null;
-  }-*/;
-
-  public final native RelatedObjects getRelated() /*-{
-    return this.related;
-  }-*/;
-
-  public final native Object getResult() /*-{
-    return Object(this.result);
-  }-*/;
-
-  public final native SideEffects getSideEffects() /*-{
-    return this.sideEffects;
-  }-*/;
-
-  public final native JsArray<ReturnRecord> getViolations()/*-{
-    return this.violations || null;
-  }-*/;
-
-  /**
-   * Looks for an explicit <code>{result: null}</code> in the payload.
-   */
-  public final native boolean isNullResult() /*-{
-    return this.result === null;
-  }-*/;
-}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/JsonServerException.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/JsonServerException.java
deleted file mode 100644
index 1e508f2..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/JsonServerException.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-/**
- * Contains details of a server error.
- */
- public final class JsonServerException extends JavaScriptObject {
-
-  protected JsonServerException() {
-  }
-
-  public native String getMessage() /*-{
-    return this.message || "";
-  }-*/;
-
-  public native String getTrace() /*-{
-    return this.trace || "";
-  }-*/;
-
-  public native String getType() /*-{
-    return this.type || "";
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/RelatedObjects.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/RelatedObjects.java
deleted file mode 100644
index de1bb19..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/RelatedObjects.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Provides access to the related field in the main payload.
- */
-public final class RelatedObjects extends JavaScriptObject {
-
-  protected RelatedObjects() {
-  }
-
-  public Set<String> getHistoryTokens() {
-    HashSet<String> toReturn = new HashSet<String>();
-    ReturnRecord.fillKeys(this, toReturn);
-    return toReturn;
-  }
-
-  public native ReturnRecord getReturnRecord(String key) /*-{
-    return this[key];
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/RequestContentData.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/RequestContentData.java
deleted file mode 100644
index f955bf0..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/RequestContentData.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.core.client.JsonUtils;
-import com.google.gwt.requestfactory.shared.WriteOperation;
-
-import java.util.EnumMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Accumulates data to place in the contentData section of a client request.
- */
-public class RequestContentData {
-  /**
-   * Create a JSON map ouf of keys and JSON expressions. The keys are quoted,
-   * however, the expressions are not.
-   */
-  public static String flattenKeysToExpressions(
-      Map<String, String> keysToExpressions) {
-    if (keysToExpressions.isEmpty()) {
-      return "{}";
-    }
-
-    StringBuilder flattenedProperties = new StringBuilder();
-    for (Map.Entry<String, String> entry : keysToExpressions.entrySet()) {
-      flattenedProperties.append(",").append(
-          JsonUtils.escapeValue(entry.getKey())).append(":").append(
-          entry.getValue());
-    }
-    capMap(flattenedProperties);
-    return flattenedProperties.toString();
-  }
-
-  /**
-   * Appends a flattened map object to a larger map of keys to lists.
-   */
-  private static void addToMap(Map<WriteOperation, StringBuilder> map,
-      WriteOperation op, String typeToken, Map<String, String> toFlatten) {
-    // { "id": 1, "foo": "bar", "baz": [1,2,3] }
-    String flattenedProperties = flattenKeysToExpressions(toFlatten);
-
-    StringBuilder sb = map.get(op);
-    if (sb == null) {
-      sb = new StringBuilder();
-      map.put(op, sb);
-    }
-
-    // {"com.google.Foo" : { id:1, foo:"bar"}}
-    sb.append(",").append("{\"").append(typeToken).append("\":").append(
-        flattenedProperties.toString()).append("}");
-  }
-
-  /**
-   * For simplicity, most lists are generated with a leading comma. This method
-   * makes the StringBuilder a properly terminated.
-   */
-  private static void capList(StringBuilder properties) {
-    properties.deleteCharAt(0).insert(0, "[").append("]");
-  }
-
-  private static void capMap(StringBuilder sb) {
-    sb.deleteCharAt(0).insert(0, "{").append("}");
-  }
-
-  private final Map<WriteOperation, StringBuilder> payloads = new EnumMap<WriteOperation, StringBuilder>(
-      WriteOperation.class);
-
-  public void addPersist(String typeToken, Map<String, String> encoded) {
-    addToMap(payloads, WriteOperation.PERSIST, typeToken, encoded);
-  }
-
-  public void addUpdate(String typeToken, Map<String, String> encoded) {
-    addToMap(payloads, WriteOperation.UPDATE, typeToken, encoded);
-  }
-
-  public String toJson() {
-    Map<String, String> toReturn = new LinkedHashMap<String, String>();
-    addToReturn(toReturn, WriteOperation.PERSIST);
-    addToReturn(toReturn, WriteOperation.UPDATE);
-    return flattenKeysToExpressions(toReturn);
-  }
-
-  /**
-   * For debugging use only.
-   */
-  @Override
-  public String toString() {
-    return toJson();
-  }
-
-  private void addToReturn(Map<String, String> toReturn, WriteOperation op) {
-    StringBuilder sb = payloads.get(op);
-    if (sb != null) {
-      capList(sb);
-      toReturn.put(op.getUnObfuscatedEnumName(), sb.toString());
-    }
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/RequestData.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/RequestData.java
deleted file mode 100644
index 487717d..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/RequestData.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.requestfactory.client.impl.EntityCodex;
-import com.google.gwt.requestfactory.shared.ValueCodex;
-import com.google.gwt.requestfactory.shared.impl.Constants;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * <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>
- * A class that encapsulates the parameters and method name to be invoked on the
- * server.
- */
-public class RequestData {
-  private final String operation;
-  private final Object[] parameters;
-  private final Set<String> propertyRefs;
-
-  public RequestData(String operation, Object[] parameters,
-      Set<String> propertyRefs) {
-    this.operation = operation;
-    this.parameters = parameters;
-    this.propertyRefs = propertyRefs;
-  }
-
-  /**
-   * Used by InstanceRequest subtypes to reset the instance object in the
-   * <code>using</code> method.
-   */
-  public Object[] getParameters() {
-    return parameters;
-  }
-
-  /**
-   * Returns the string that encodes the request data.
-   * 
-   */
-  public Map<String, String> getRequestMap(String contentData) {
-    Map<String, String> requestMap = new HashMap<String, String>();
-    requestMap.put(Constants.OPERATION_TOKEN,
-        ValueCodex.encodeForJsonPayload(operation));
-    if (parameters != null) {
-      for (int i = 0; i < parameters.length; i++) {
-        Object value = parameters[i];
-        requestMap.put(Constants.PARAM_TOKEN + i,
-            EntityCodex.encodeForJsonPayload(value));
-      }
-    }
-    if (contentData != null) {
-      // Yes, double-encoding
-      requestMap.put(Constants.CONTENT_TOKEN,
-          ValueCodex.encodeForJsonPayload(contentData));
-    }
-
-    if (propertyRefs != null && !propertyRefs.isEmpty()) {
-      StringBuffer props = new StringBuffer();
-      Iterator<String> propIt = propertyRefs.iterator();
-      while (propIt.hasNext()) {
-        props.append(propIt.next());
-        if (propIt.hasNext()) {
-          props.append(",");
-        }
-      }
-      requestMap.put(Constants.PROPERTY_REF_TOKEN,
-          ValueCodex.encodeForJsonPayload(props.toString()));
-    }
-    return requestMap;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/ReturnRecord.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/ReturnRecord.java
deleted file mode 100644
index 17446cf..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/ReturnRecord.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.core.client.JavaScriptObject;
-
-import java.util.HashMap;
-import java.util.HashSet;
-
-/**
- * JSO that holds an entity reference being returned from the server.
- */
-public class ReturnRecord extends JavaScriptObject {
-
-  static native void fillKeys(JavaScriptObject jso, HashSet<String> s) /*-{
-    for (key in jso) {
-      if (jso.hasOwnProperty(key)) {
-        s.@java.util.HashSet::add(Ljava/lang/Object;)(key);
-      }
-    }
-  }-*/;
-
-  protected ReturnRecord() {
-  }
-
-  public final native void fillViolations(HashMap<String, String> s) /*-{
-    for (key in this.violations) {
-      if (this.violations.hasOwnProperty(key)) {
-        s.@java.util.HashMap::put(Ljava/lang/Object;Ljava/lang/Object;)(key, this.violations[key]);
-      }
-    }
-  }-*/;
-
-  public final native Object get(String propertyName) /*-{
-    return Object(this[propertyName]);
-  }-*/;
-
-  public final String getEncodedId() {
-    String parts[] = getSchemaAndId().split("@");
-    return parts[1];
-  }
-
-  public final native String getFutureId()/*-{
-    return this[@com.google.gwt.requestfactory.shared.impl.Constants::ENCODED_FUTUREID_PROPERTY];
-  }-*/;
-
-  public final String getSchema() {
-    String parts[] = getSchemaAndId().split("@");
-    return parts[0];
-  }
-
-  public final native String getSchemaAndId() /*-{
-    return this[@com.google.gwt.requestfactory.shared.impl.Constants::ENCODED_ID_PROPERTY];
-  }-*/;
-
-  public final native String getSimpleId()/*-{
-    return this[@com.google.gwt.requestfactory.shared.impl.Constants::ENCODED_ID_PROPERTY];
-  }-*/;
-
-  public final native int getVersion()/*-{
-    return this[@com.google.gwt.requestfactory.shared.impl.Constants::ENCODED_VERSION_PROPERTY] || 0;
-  }-*/;
-
-  public final native boolean hasFutureId()/*-{
-    return @com.google.gwt.requestfactory.shared.impl.Constants::ENCODED_FUTUREID_PROPERTY in this;
-  }-*/;
-
-  public final native boolean hasId()/*-{
-    return @com.google.gwt.requestfactory.shared.impl.Constants::ENCODED_ID_PROPERTY in this;
-  }-*/;
-
-  public final native boolean hasProperty(String property) /*-{
-    return this.hasOwnProperty(property);
-  }-*/;
-
-  public final native boolean hasViolations()/*-{
-    return @com.google.gwt.requestfactory.shared.impl.Constants::VIOLATIONS_TOKEN in this;
-  }-*/;
-
-  /**
-   * Returns <code>true</code> if the property explicitly set to null.
-   */
-  public final native boolean isNull(String property) /*-{
-    return this[property] === null;
-  }-*/;
-
-}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/messages/SideEffects.java b/user/src/com/google/gwt/requestfactory/client/impl/messages/SideEffects.java
deleted file mode 100644
index 1fa1f36..0000000
--- a/user/src/com/google/gwt/requestfactory/client/impl/messages/SideEffects.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl.messages;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsArray;
-
-/**
- * Provides access to the changes that occurred on the server.
- */
-public final class SideEffects extends JavaScriptObject {
-  protected SideEffects() {
-  }
-
-  public native JsArray<ReturnRecord> getDelete() /*-{
-    return this['DELETE'];
-  }-*/;
-
-  public native JsArray<ReturnRecord> getPersist() /*-{
-    return this['PERSIST'];
-  }-*/;
-
-  public native JsArray<ReturnRecord> getUpdate() /*-{
-    return this['UPDATE'];
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index 53aaeed..b7eb331 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -15,6 +15,10 @@
  */
 package com.google.gwt.requestfactory.rebind;
 
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
+import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
@@ -25,37 +29,25 @@
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JTypeParameter;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanFactory;
-import com.google.gwt.editor.client.AutoBeanFactory.Category;
-import com.google.gwt.editor.client.AutoBeanFactory.NoWrap;
 import com.google.gwt.editor.rebind.model.ModelUtils;
-import com.google.gwt.requestfactory.client.impl.AbstractRequest;
-import com.google.gwt.requestfactory.client.impl.AbstractRequestContext;
-import com.google.gwt.requestfactory.client.impl.AbstractRequestFactory;
-import com.google.gwt.requestfactory.client.impl.EntityProxyCategory;
-import com.google.gwt.requestfactory.client.impl.messages.RequestData;
+import com.google.gwt.requestfactory.client.impl.AbstractClientRequestFactory;
 import com.google.gwt.requestfactory.rebind.model.ContextMethod;
 import com.google.gwt.requestfactory.rebind.model.EntityProxyModel;
 import com.google.gwt.requestfactory.rebind.model.RequestFactoryModel;
 import com.google.gwt.requestfactory.rebind.model.RequestMethod;
-import com.google.gwt.requestfactory.rebind.model.RequestMethod.CollectionType;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.ValueCodex;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequest;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequestContext;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.gwt.requestfactory.shared.impl.RequestData;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 
 /**
- * <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>
  * Generates implementations of
  * {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory}
  * and its nested interfaces.
@@ -90,7 +82,7 @@
 
     ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
         packageName, simpleSourceName);
-    factory.setSuperclass(AbstractRequestFactory.class.getCanonicalName());
+    factory.setSuperclass(AbstractClientRequestFactory.class.getCanonicalName());
     factory.addImplementedInterface(typeName);
     SourceWriter sw = factory.createSourceWriter(context, pw);
     writeAutoBeanFactory(sw);
@@ -211,50 +203,20 @@
         // makeRequestData()
         sw.println("@Override protected %s makeRequestData() {",
             RequestData.class.getCanonicalName());
-        // return new RequestData("Foo::bar", new Object
-        sw.indentln("return new %s(\"%s\", new Object[] {%s}, propertyRefs);",
-            RequestData.class.getCanonicalName(), operation, parameterArray);
+        // return new RequestData("Foo::bar", {parameters}, propertyRefs,
+        // List.class, FooProxy.class);
+        String elementType = request.isCollectionType()
+            ? request.getCollectionElementType().getQualifiedSourceName()
+                + ".class" : "null";
+        String returnTypeBaseQualifiedName = ModelUtils.ensureBaseType(
+            request.getDataType()).getQualifiedSourceName();
+        sw.indentln(
+            "return new %s(\"%s\", new Object[] {%s}, propertyRefs, %s.class, %s);",
+            RequestData.class.getCanonicalName(), operation, parameterArray,
+            returnTypeBaseQualifiedName, elementType);
         sw.println("}");
 
-        // handleResponse(Object obj)
-        sw.println("@Override protected void handleResult(Object obj) {");
-        sw.indent();
-        sw.println("Object decoded;");
-        if (request.isCollectionType()) {
-          // Lists are ArrayLists, Sets are HashSets
-          Class<?> collectionType = request.getCollectionType().equals(
-              CollectionType.LIST) ? ArrayList.class : HashSet.class;
-
-          // decoded = new ArrayList<Foo>();
-          sw.println("decoded = new %s();", collectionType.getCanonicalName());
-
-          // decodeReturnObjectList(FooEntityProxy.class,obj, (List)decoded);
-          String decodeMethod = request.isValueType() ? "decodeReturnValueList"
-              : "decodeReturnObjectList";
-          sw.println(
-              "%s(%s.class, obj, (%s)decoded);",
-              decodeMethod,
-              ModelUtils.getQualifiedBaseName(request.getCollectionElementType()),
-              collectionType.getCanonicalName());
-        } else if (request.isValueType()) {
-          // decoded = ValueCodex.cFString(Integer.class, String.valueOf(obj));
-          sw.println(
-              "decoded = %s.convertFromString(%s.class, String.valueOf(obj));",
-              ValueCodex.class.getCanonicalName(),
-              ModelUtils.getQualifiedBaseName(request.getDataType()));
-        } else if (request.isEntityType()) {
-          // Implicitly erased
-          sw.println("decoded = decodeReturnObject(%s.class, obj);",
-              request.getEntityType().getQualifiedSourceName());
-        } else {
-          sw.println("throw new UnsupportedOperationException()");
-        }
-        // succeed((Integer) decoded);
-        sw.println("succeed((%s) decoded);",
-            request.getDataType().getParameterizedQualifiedSourceName());
-        sw.outdent();
-        sw.println("}");
-
+        // end class X{}
         sw.outdent();
         sw.println("}");
 
diff --git a/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java b/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
index 84df340..535239e 100644
--- a/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
+++ b/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
@@ -19,17 +19,10 @@
  * Indicates the user attempted to perform an operation on an irretrievable
  * entity.
  */
-public class DeadEntityException extends RuntimeException {
-  
-  /**
-   * Contructs a new {@link DeadEntityException}.
-   */
-  public DeadEntityException() {
-  }
-
+class DeadEntityException extends RuntimeException {
   /**
    * Contructs a new {@link DeadEntityException} with a given message.
-   *
+   * 
    * @param message a message String
    */
   public DeadEntityException(String message) {
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
index 344b622..924b70e 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,9 +18,8 @@
 import com.google.gwt.requestfactory.shared.ServerFailure;
 
 /**
- * Default implementation for handling exceptions thrown while
- * processing a request. Suppresses stack traces and the exception
- * class name.
+ * Default implementation for handling exceptions thrown while processing a
+ * request. Suppresses stack traces and the exception class name.
  */
 public class DefaultExceptionHandler implements ExceptionHandler {
   public ServerFailure createServerFailure(Throwable throwable) {
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
deleted file mode 100644
index cdcb90f..0000000
--- a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import com.google.gwt.requestfactory.shared.Service;
-import com.google.gwt.requestfactory.shared.ServiceName;
-
-/**
- * A security provider that enforces
- * {@link com.google.gwt.requestfactory.shared.Service} annotations.
- */
-class DefaultSecurityProvider implements RequestSecurityProvider {
-
-  public void checkClass(Class<?> clazz) throws SecurityException {
-    Service service = clazz.getAnnotation(Service.class);
-    ServiceName serviceName = clazz.getAnnotation(ServiceName.class);
-    String className;
-    if (service != null) {
-      className = service.value().getName();
-    } else if (serviceName != null) {
-      className = serviceName.value();
-    } else {
-      throw new SecurityException("Class " + clazz.getName()
-          + " does not have a @Service annotation.");
-    }
-    try {
-      Class.forName(className);
-    } catch (ClassNotFoundException e) {
-      throw new SecurityException("Class " + className
-          + " from @Service annotation on " + clazz + " cannot be loaded.");
-    }
-  }
-
-  public String encodeClassType(Class<?> type) {
-    return type.getName();
-  }
-
-  public String mapOperation(String operationName) throws SecurityException {
-    return operationName;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
index 4faff11..8735633 100644
--- a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
@@ -19,11 +19,13 @@
 
 /**
  * Handles an exception produced while processing a request.
+ * 
+ * @see DefaultExceptionHandler
  */
 public interface ExceptionHandler {
   /**
-   * Generates a {@link ServerFailure} based on the information
-   * contained in the received {@code exception}.
+   * Generates a {@link ServerFailure} based on the information contained in the
+   * received {@code exception}.
    * 
    * @param throwable a Throwable instance
    * @return a {@link ServerFailure} instance
diff --git a/user/src/com/google/gwt/requestfactory/server/FindService.java b/user/src/com/google/gwt/requestfactory/server/FindService.java
index 06aefd8..d5b5353 100644
--- a/user/src/com/google/gwt/requestfactory/server/FindService.java
+++ b/user/src/com/google/gwt/requestfactory/server/FindService.java
@@ -22,7 +22,7 @@
 
   /**
    * For now, a simple implementation of find will work.
-   *
+   * 
    * @param entityInstance an entity instance
    * @return the passed-in entity instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
deleted file mode 100644
index ea7fb51..0000000
--- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
+++ /dev/null
@@ -1,1644 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-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.ServerFailure;
-import com.google.gwt.requestfactory.shared.WriteOperation;
-import com.google.gwt.requestfactory.shared.impl.CollectionProperty;
-import com.google.gwt.requestfactory.shared.impl.Constants;
-import com.google.gwt.requestfactory.shared.impl.Property;
-import com.google.gwt.user.server.Base64Utils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.beans.Introspector;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-
-import javax.validation.ConstraintViolation;
-import javax.validation.Validation;
-import javax.validation.Validator;
-import javax.validation.ValidatorFactory;
-
-/**
- * An implementation of RequestProcessor for JSON encoded payloads.
- */
-class JsonRequestProcessor implements RequestProcessor<String> {
-
-  // TODO should we consume String, InputStream, or JSONObject?
-  private class DvsData {
-    private final JSONObject jsonObject;
-    private final WriteOperation writeOperation;
-    // save version because it is deleted later.
-    private final Integer version;
-
-    DvsData(JSONObject jsonObject, WriteOperation writeOperation)
-        throws JSONException, SecurityException, IllegalAccessException,
-        InvocationTargetException, NoSuchMethodException,
-        InstantiationException {
-      this.jsonObject = jsonObject;
-      this.writeOperation = writeOperation;
-      this.version = (Integer) (jsonObject.has(Constants.ENCODED_VERSION_PROPERTY)
-          ? decodeParameterValue(Constants.ENTITY_VERSION_PROPERTY.getType(),
-              jsonObject.get(Constants.ENCODED_VERSION_PROPERTY).toString())
-          : null);
-    }
-  }
-
-  private static class EntityData {
-    private final Object entityInstance;
-    // TODO: violations should have more structure than JSONObject
-    private final JSONObject violations;
-
-    EntityData(Object entityInstance, JSONObject violations) {
-      this.entityInstance = entityInstance;
-      this.violations = violations;
-    }
-  }
-
-  private class EntityKey {
-    final boolean isFuture;
-    final String encodedId;
-    final Class<? extends EntityProxy> proxyType;
-
-    EntityKey(String id, boolean isFuture,
-        Class<? extends EntityProxy> proxyType) {
-      this.encodedId = id;
-      this.isFuture = isFuture;
-      assert proxyType != null;
-      this.proxyType = proxyType;
-    }
-
-    public Object decodedId(Class<?> entityIdType) throws SecurityException,
-        JSONException, IllegalAccessException, InvocationTargetException,
-        NoSuchMethodException, InstantiationException {
-      if (isFuture) {
-        return encodedId;
-      }
-
-      if (String.class.isAssignableFrom(entityIdType)) {
-        return base64Decode(encodedId);
-      }
-
-      return decodeParameterValue(entityIdType, encodedId);
-    }
-
-    @Override
-    public boolean equals(Object ob) {
-      if (!(ob instanceof EntityKey)) {
-        return false;
-      }
-      EntityKey other = (EntityKey) ob;
-      return (encodedId.equals(other.encodedId))
-          && (isFuture == other.isFuture)
-          && (proxyType.equals(other.proxyType));
-    }
-
-    @Override
-    public int hashCode() {
-      return 31 * this.proxyType.hashCode()
-          + (31 * this.encodedId.hashCode() + (isFuture ? 1 : 0));
-    }
-
-    @Override
-    public String toString() {
-      return encodedId + "@" + (isFuture ? "IS" : "NO") + "@"
-          + proxyType.getName();
-    }
-  }
-
-  private static class SerializedEntity {
-    // the field value of the entityInstance might change from under us.
-    private final Object entityInstance;
-
-    private final JSONObject serializedEntity;
-
-    SerializedEntity(Object entityInstance, JSONObject serializedEntity) {
-      this.entityInstance = entityInstance;
-      this.serializedEntity = serializedEntity;
-    }
-  }
-
-  public static final String RELATED = "related";
-
-  private static final Logger log = Logger.getLogger(JsonRequestProcessor.class.getName());
-
-  /**
-   * Decodes a String encoded as web-safe base64.
-   * 
-   * @param encoded the encoded String
-   * @return a decoded String
-   */
-  public static String base64Decode(String encoded) {
-    byte[] decodedBytes;
-    try {
-      decodedBytes = Base64Utils.fromBase64(encoded);
-    } catch (Exception e) {
-      throw new IllegalArgumentException("EntityKeyId was not Base64 encoded: "
-          + encoded);
-    }
-    try {
-      return new String(decodedBytes, "UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      // This can't happen
-      throw new IllegalStateException(e);
-    }
-  }
-
-  /**
-   * Encodes a String as web-safe base64.
-   * 
-   * @param decoded the decoded String
-   * @return an encoded String
-   */
-  public static String base64Encode(String decoded) {
-    try {
-      byte[] decodedBytes = decoded.getBytes("UTF-8");
-      return Base64Utils.toBase64(decodedBytes);
-    } catch (UnsupportedEncodingException e) {
-      // This can't happen
-      throw new IllegalStateException(e);
-    }
-  }
-
-  // TODO - document
-  @SuppressWarnings("unchecked")
-  public static Class<EntityProxy> getRecordFromClassToken(String recordToken) {
-    try {
-      // TODO(rjrjr) Should be getting class loader from servlet environment?
-      Class<?> clazz = Class.forName(recordToken, false,
-          JsonRequestProcessor.class.getClassLoader());
-      if (EntityProxy.class.isAssignableFrom(clazz)) {
-        return (Class<EntityProxy>) clazz;
-      }
-      throw new SecurityException("Attempt to access non-record class "
-          + recordToken);
-    } catch (ClassNotFoundException e) {
-      throw new IllegalArgumentException("Non-existent record class "
-          + recordToken);
-    }
-  }
-
-  private RequestProperty propertyRefs;
-
-  private final Map<String, JSONObject> relatedObjects = new HashMap<String, JSONObject>();
-
-  private OperationRegistry operationRegistry;
-
-  private ExceptionHandler exceptionHandler;
-
-  /*
-   * <li>Request comes in. Construct the involvedKeys, dvsDataMap and
-   * beforeDataMap, using DVS and parameters.
-   * 
-   * <li>Apply the DVS and construct the afterDvsDataMqp.
-   * 
-   * <li>Invoke the method noted in the operation.
-   * 
-   * <li>Find the changes that need to be sent back.
-   */
-  private final Map<EntityKey, Object> cachedEntityLookup = new HashMap<EntityKey, Object>();
-  private final Set<EntityKey> involvedKeys = new HashSet<EntityKey>();
-  private final Map<EntityKey, DvsData> dvsDataMap = new HashMap<EntityKey, DvsData>();
-  private final Map<EntityKey, SerializedEntity> beforeDataMap = new HashMap<EntityKey, SerializedEntity>();
-
-  private Map<EntityKey, EntityData> afterDvsDataMap = new HashMap<EntityKey, EntityData>();
-
-  // TODO - document
-  public Collection<Property<?>> allProperties(
-      Class<? extends EntityProxy> clazz) throws IllegalArgumentException {
-    return getPropertiesFromRecordProxyType(clazz).values();
-  }
-
-  // TODO - document
-  public String decodeAndInvokeRequest(String encodedRequest)
-      throws RequestProcessingException {
-    try {
-      Logger.getLogger(this.getClass().getName()).finest(
-          "Incoming request " + encodedRequest);
-      String response = processJsonRequest(encodedRequest).toString();
-      Logger.getLogger(this.getClass().getName()).finest(
-          "Outgoing response " + response);
-      return response;
-    } catch (InvocationTargetException e) {
-      JSONObject exceptionResponse = buildExceptionResponse(e.getCause());
-      throw new RequestProcessingException("Unexpected exception", e,
-          exceptionResponse.toString());
-    } catch (DeadEntityException e) {
-      // This is a normal, if exceptional, condition
-      return buildExceptionResponse(e).toString();
-    } catch (Exception e) {
-      JSONObject exceptionResponse = buildExceptionResponse(e);
-      throw new RequestProcessingException("Unexpected exception", e,
-          exceptionResponse.toString());
-    }
-  }
-
-  /**
-   * Decodes parameter value.
-   */
-  // TODO - document parameters and return value
-  public Object decodeParameterValue(Type genericParameterType,
-      String parameterValue) throws SecurityException, JSONException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException,
-      InstantiationException {
-    if (parameterValue == null) {
-      return null;
-    }
-    Class<?> parameterType = null;
-    if (genericParameterType instanceof Class<?>) {
-      parameterType = (Class<?>) genericParameterType;
-    }
-    if (genericParameterType instanceof ParameterizedType) {
-      ParameterizedType pType = (ParameterizedType) genericParameterType;
-      if (pType.getRawType() instanceof Class<?>) {
-        Class<?> rType = (Class<?>) pType.getRawType();
-        // Ensure parameterType is initialized
-        parameterType = rType;
-        if (Collection.class.isAssignableFrom(rType)) {
-          Collection<Object> collection = createCollection(rType);
-          if (collection != null) {
-            JSONArray array = new JSONArray(parameterValue);
-            for (int i = 0; i < array.length(); i++) {
-              String value = array.isNull(i) ? null : array.getString(i);
-              collection.add(decodeParameterValue(
-                  pType.getActualTypeArguments()[0], value));
-            }
-            return collection;
-          }
-        }
-      }
-    }
-    if (String.class == parameterType) {
-      return parameterValue;
-    }
-    if (Boolean.class == parameterType || boolean.class == parameterType) {
-      return Boolean.valueOf(parameterValue);
-    }
-    if (Integer.class == parameterType || int.class == parameterType) {
-      return new Integer(parameterValue);
-    }
-    if (Byte.class == parameterType || byte.class == parameterType) {
-      return new Byte(parameterValue);
-    }
-    if (Short.class == parameterType || short.class == parameterType) {
-      return new Short(parameterValue);
-    }
-    if (Float.class == parameterType || float.class == parameterType) {
-      return new Float(parameterValue);
-    }
-    if (Double.class == parameterType || double.class == parameterType) {
-      return new Double(parameterValue);
-    }
-    if (Long.class == parameterType || long.class == parameterType) {
-      return new Long(parameterValue);
-    }
-    if (Character.class == parameterType || char.class == parameterType) {
-      return parameterValue.charAt(0);
-    }
-    if (BigInteger.class == parameterType) {
-      return new BigInteger(parameterValue);
-    }
-    if (BigDecimal.class == parameterType) {
-      return new BigDecimal(parameterValue);
-    }
-    if (parameterType.isEnum()) {
-      int ordinal = Integer.parseInt(parameterValue);
-      Method valuesMethod = parameterType.getDeclaredMethod("values",
-          new Class[0]);
-
-      if (valuesMethod != null) {
-        valuesMethod.setAccessible(true);
-        Enum<?>[] values = (Enum<?>[]) valuesMethod.invoke(null);
-        // we use ordinal serialization instead of name since future compiler
-        // opts may remove names
-        for (Enum<?> e : values) {
-          if (ordinal == e.ordinal()) {
-            return e;
-          }
-        }
-      }
-      throw new IllegalArgumentException("Can't decode enum " + parameterType
-          + " no matching ordinal " + ordinal);
-    }
-    if (Date.class == parameterType) {
-      return new Date(Long.parseLong(parameterValue));
-    }
-    if (EntityProxy.class.isAssignableFrom(parameterType)) {
-      /*
-       * TODO: 1. Don't resolve in this step, just get EntityKey. May need to
-       * use DVS.
-       * 
-       * 2. Merge the following and the object resolution code in getEntityKey.
-       * 3. Update the involvedKeys set.
-       */
-      ProxyFor proxyFor = parameterType.getAnnotation(ProxyFor.class);
-      ProxyForName proxyForName = parameterType.getAnnotation(ProxyForName.class);
-      if (proxyFor != null || proxyForName != null) {
-        EntityKey entityKey = getEntityKey(parameterValue.toString());
-
-        DvsData dvsData = dvsDataMap.get(entityKey);
-        if (dvsData != null) {
-          EntityData entityData = getEntityDataForRecordWithSettersApplied(
-              entityKey, dvsData.jsonObject, dvsData.writeOperation);
-          return entityData.entityInstance;
-        } else {
-          involvedKeys.add(entityKey);
-          return getEntityInstance(entityKey);
-        }
-      }
-    }
-    if (EntityProxyId.class.isAssignableFrom(parameterType)) {
-      EntityKey entityKey = getEntityKey(parameterValue.toString());
-      ProxyFor proxyFor = entityKey.proxyType.getAnnotation(ProxyFor.class);
-      ProxyForName proxyForName = entityKey.proxyType.getAnnotation(ProxyForName.class);
-      if (proxyFor == null && proxyForName == null) {
-        throw new IllegalArgumentException("Unknown service, unable to decode "
-            + parameterValue);
-      }
-      involvedKeys.add(entityKey);
-      return getEntityInstance(entityKey);
-    }
-    throw new IllegalArgumentException("Unknown parameter type: "
-        + parameterType);
-  }
-
-  /**
-   * Encode a property value to be sent across the wire.
-   * 
-   * @param value a value Object
-   * @return an encoded value Object
-   */
-  public Object encodePropertyValue(Object value) {
-    if (value == null) {
-      return JSONObject.NULL;
-    }
-    Class<?> type = value.getClass();
-    if (Boolean.class == type) {
-      return value;
-    }
-    if (Date.class.isAssignableFrom(type)) {
-      return String.valueOf(((Date) value).getTime());
-    }
-    if (Enum.class.isAssignableFrom(type)) {
-      return Double.valueOf(((Enum<?>) value).ordinal());
-    }
-    if (BigDecimal.class == type || BigInteger.class == type
-        || Long.class == type) {
-      return String.valueOf(value);
-    }
-    if (Number.class.isAssignableFrom(type)) {
-      return ((Number) value).doubleValue();
-    }
-    return String.valueOf(value);
-  }
-
-  /**
-   * Returns the propertyValue in the right type, from the DataStore. The value
-   * is sent into the response.
-   */
-  // TODO - document parameters and return value
-  public Object encodePropertyValueFromDataStore(Object entityElement,
-      Property<?> property, String propertyName, RequestProperty propertyContext)
-      throws SecurityException, NoSuchMethodException, IllegalAccessException,
-      InvocationTargetException, JSONException {
-
-    Object returnValue = getRawPropertyValueFromDatastore(entityElement,
-        propertyName);
-    Class<?> proxyPropertyType = property.getType();
-    Class<?> elementType = property instanceof CollectionProperty<?, ?>
-        ? ((CollectionProperty<?, ?>) property).getLeafType()
-        : proxyPropertyType;
-    String encodedEntityId = isEntityReference(returnValue, proxyPropertyType);
-
-    if (returnValue == null) {
-      return JSONObject.NULL;
-    } else if (encodedEntityId != null) {
-      String keyRef = encodeRelated(proxyPropertyType, propertyName,
-          propertyContext, returnValue);
-      return keyRef;
-    } else if (property instanceof CollectionProperty<?, ?>) {
-      Class<?> colType = ((CollectionProperty<?, ?>) property).getType();
-      Collection<Object> col = createCollection(colType);
-      if (col != null) {
-        for (Object o : ((Collection<?>) returnValue)) {
-          String encodedValId = isEntityReference(o,
-              ((CollectionProperty<?, ?>) property).getLeafType());
-          if (encodedValId != null) {
-            col.add(encodeRelated(elementType, propertyName, propertyContext, o));
-          } else {
-            col.add(encodePropertyValue(o));
-          }
-        }
-        return col;
-      }
-      return null;
-    } else {
-      return encodePropertyValue(returnValue);
-    }
-  }
-
-  /**
-   * Find the entity in the server data store, apply its setters, capture any
-   * violations, and return a
-   * {@link com.google.gwt.requestfactory.server.JsonRequestProcessor.EntityData
-   * EntityData} encapsulating the results.
-   * <p>
-   * If a <i>set</i> method has side-effects, we will not notice.
-   */
-  // TODO - document parameters and return value
-  public EntityData getEntityDataForRecordWithSettersApplied(
-      EntityKey entityKey, JSONObject recordObject,
-      WriteOperation writeOperation) throws JSONException, SecurityException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException,
-      InstantiationException {
-
-    Class<?> entityType = getEntityTypeForProxyType(entityKey.proxyType);
-
-    Map<String, Property<?>> propertiesInProxy = getPropertiesFromRecordProxyType(entityKey.proxyType);
-    Map<String, Class<?>> propertiesInDomain = updatePropertyTypes(
-        propertiesInProxy, entityType);
-    validateKeys(recordObject, propertiesInDomain.keySet());
-
-    // get entityInstance
-    Class<?> idType = getIdMethodForEntity(entityType).getReturnType();
-    Object entityInstance = getEntityInstance(writeOperation, entityType,
-        entityKey.decodedId(idType), idType);
-    if (entityInstance == null) {
-      throw new DeadEntityException(
-          "The requested entity is not available on the server");
-    }
-    cachedEntityLookup.put(entityKey, entityInstance);
-
-    Iterator<?> keys = recordObject.keys();
-    while (keys.hasNext()) {
-      String key = (String) keys.next();
-      Property<?> dtoProperty = propertiesInProxy.get(key);
-      if (writeOperation == WriteOperation.PERSIST
-          && (Constants.ENTITY_ID_PROPERTY.equals(key))) {
-        // Don't allow the client to attempt to set the id
-        continue;
-      } else {
-        Object propertyValue = null;
-        if (recordObject.isNull(key)) {
-          // null
-        } else if (dtoProperty instanceof CollectionProperty<?, ?>) {
-          Class<?> cType = dtoProperty.getType();
-          Class<?> leafType = ((CollectionProperty<?, ?>) dtoProperty).getLeafType();
-          Collection<Object> col = createCollection(cType);
-          if (col != null) {
-            JSONArray array = recordObject.getJSONArray(key);
-            for (int i = 0; i < array.length(); i++) {
-              if (EntityProxy.class.isAssignableFrom(leafType)) {
-                propertyValue = getPropertyValueFromRequestCached(array, i,
-                    dtoProperty);
-              } else {
-                propertyValue = decodeParameterValue(leafType,
-                    array.getString(i));
-              }
-              col.add(propertyValue);
-            }
-            propertyValue = col;
-          }
-        } else {
-          propertyValue = getPropertyValueFromRequestCached(recordObject,
-              propertiesInProxy, key, dtoProperty);
-        }
-        entityType.getMethod(getMethodNameFromPropertyName(key, "set"),
-            propertiesInDomain.get(key)).invoke(entityInstance, propertyValue);
-      }
-    }
-
-    Set<ConstraintViolation<Object>> violations = Collections.emptySet();
-    // validations check..
-    Validator validator = null;
-    try {
-      ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
-      validator = validatorFactory.getValidator();
-    } catch (Exception e) {
-      /*
-       * This is JBoss's clumsy way of telling us that the system has not been
-       * configured.
-       */
-      log.info(String.format(
-          "Ignoring exception caught initializing bean validation framework. "
-              + "It is probably unconfigured or misconfigured. [%s] %s ",
-          e.getClass().getName(), e.getLocalizedMessage()));
-    }
-
-    if (validator != null) {
-      violations = validator.validate(entityInstance);
-    }
-    return new EntityData(entityInstance, (violations.isEmpty() ? null
-        : getViolationsAsJson(violations)));
-  }
-
-  // TODO - document
-  public Object getEntityInstance(EntityKey entityKey)
-      throws NoSuchMethodException, IllegalAccessException,
-      InvocationTargetException, JSONException, InstantiationException {
-    Class<?> entityClass = getEntityTypeForProxyType(entityKey.proxyType);
-    Class<?> idType = getIdMethodForEntity(entityClass).getReturnType();
-    Object entityInstance = entityClass.getMethod(
-        "find" + entityClass.getSimpleName(), idType).invoke(null,
-        entityKey.decodedId(idType));
-    return entityInstance;
-  }
-
-  // TODO - document
-  public Object getEntityInstance(WriteOperation writeOperation,
-      Class<?> entityType, Object idValue, Class<?> idType)
-      throws SecurityException, InstantiationException, IllegalAccessException,
-      InvocationTargetException, NoSuchMethodException,
-      IllegalArgumentException, JSONException {
-
-    if (writeOperation == WriteOperation.PERSIST) {
-      return entityType.getConstructor().newInstance();
-    }
-    // TODO: check "version" validity.
-    return entityType.getMethod("find" + entityType.getSimpleName(), idType).invoke(
-        null, decodeParameterValue(idType, idValue.toString()));
-  }
-
-  // TODO - document
-  @SuppressWarnings("unchecked")
-  public Class<Object> getEntityTypeForProxyType(
-      Class<? extends EntityProxy> record) {
-    ProxyFor dtoAnn = record.getAnnotation(ProxyFor.class);
-    if (dtoAnn != null) {
-      return (Class<Object>) dtoAnn.value();
-    }
-    ProxyForName name = record.getAnnotation(ProxyForName.class);
-    if (name != null) {
-      try {
-        return (Class<Object>) Class.forName(name.value(), false,
-            Thread.currentThread().getContextClassLoader());
-      } catch (ClassNotFoundException e) {
-        throw new IllegalArgumentException("Could not find ProxyForName class",
-            e);
-      }
-    }
-    throw new IllegalArgumentException("Proxy class " + record.getName()
-        + " missing ProxyFor annotation");
-  }
-
-  /**
-   * Converts the returnValue of a 'get' method to a JSONArray.
-   * 
-   * @param resultList object returned by a 'get' method, must be of type
-   *          List<?>
-   * @param entityKeyClass the class type of the contained value
-   * @return the JSONArray
-   */
-  public JSONArray getJsonArray(Collection<?> resultList,
-      Class<? extends EntityProxy> entityKeyClass)
-      throws IllegalArgumentException, SecurityException,
-      IllegalAccessException, JSONException, NoSuchMethodException,
-      InvocationTargetException {
-    JSONArray jsonArray = new JSONArray();
-    if (resultList.size() == 0) {
-      return jsonArray;
-    }
-
-    for (Object entityElement : resultList) {
-      if (entityElement instanceof Number || entityElement instanceof String
-          || entityElement instanceof Character
-          || entityElement instanceof Date || entityElement instanceof Boolean
-          || entityElement instanceof Enum<?>) {
-        jsonArray.put(encodePropertyValue(entityElement));
-      } else if (entityElement instanceof List<?>
-          || entityElement instanceof Set<?>) {
-        // TODO: unwrap nested type params?
-        jsonArray.put(getJsonArray((Collection<?>) entityElement,
-            entityKeyClass));
-      } else {
-        jsonArray.put(getJsonObject(entityElement, entityKeyClass, propertyRefs));
-      }
-    }
-    return jsonArray;
-  }
-
-  public Object getJsonObject(Object entityElement,
-      Class<? extends EntityProxy> entityKeyClass,
-      RequestProperty propertyContext) throws JSONException,
-      NoSuchMethodException, IllegalAccessException, InvocationTargetException {
-    if (entityElement == null
-        || !EntityProxy.class.isAssignableFrom(entityKeyClass)) {
-      // JSONObject.NULL isn't a JSONObject
-      return JSONObject.NULL;
-    }
-    JSONObject jsonObject = getJsonObjectWithIdAndVersion(
-        isEntityReference(entityElement, entityKeyClass), entityElement,
-        propertyContext);
-
-    for (Property<?> p : allProperties(entityKeyClass)) {
-      if (requestedProperty(p, propertyContext)) {
-        String propertyName = p.getName();
-        jsonObject.put(
-            propertyName,
-            encodePropertyValueFromDataStore(entityElement, p, propertyName,
-                propertyContext));
-      }
-    }
-    return jsonObject;
-  }
-
-  /**
-   * Returns methodName corresponding to the propertyName that can be invoked on
-   * an entity.
-   * 
-   * Example: "userName" returns prefix + "UserName". "version" returns prefix +
-   * "Version"
-   */
-  public String getMethodNameFromPropertyName(String propertyName, String prefix) {
-    if (propertyName == null) {
-      throw new NullPointerException("propertyName must not be null");
-    }
-
-    StringBuffer methodName = new StringBuffer(prefix);
-    methodName.append(propertyName.substring(0, 1).toUpperCase());
-    methodName.append(propertyName.substring(1));
-    return methodName.toString();
-  }
-
-  /**
-   * Returns Object[0][0] as the entityKey corresponding to the object instance
-   * or null if it is a static method. Returns Object[1] as the params array.
-   */
-  public Object[][] getObjectsFromParameterMap(boolean isInstanceMethod,
-      Map<String, String> parameterMap, Type parameterClasses[])
-      throws SecurityException, JSONException, IllegalAccessException,
-      InvocationTargetException, NoSuchMethodException, InstantiationException {
-    // TODO: create an EntityMethodCall (instance, args) instead.
-    assert parameterClasses != null;
-    Object args[][] = new Object[2][];
-    args[0] = new Object[1];
-    if (isInstanceMethod) {
-      EntityKey entityKey = getEntityKey(parameterMap.get(Constants.PARAM_TOKEN
-          + "0"));
-      involvedKeys.add(entityKey);
-      args[0][0] = entityKey;
-    } else {
-      args[0][0] = null;
-    }
-
-    // TODO: update the involvedKeys for other params
-    int offset = (isInstanceMethod ? 1 : 0);
-    args[1] = new Object[parameterClasses.length - offset];
-    for (int i = 0; i < parameterClasses.length - offset; i++) {
-      args[1][i] = decodeParameterValue(parameterClasses[i + offset],
-          parameterMap.get(Constants.PARAM_TOKEN + (i + offset)));
-    }
-    return args;
-  }
-
-  public RequestDefinition getOperation(String operationName) {
-    RequestDefinition operation;
-    operation = operationRegistry.getOperation(operationName);
-    if (null == operation) {
-      throw new IllegalArgumentException("Unknown operation " + operationName);
-    }
-    return operation;
-  }
-
-  public Map<String, String> getParameterMap(JSONObject jsonObject)
-      throws JSONException {
-    Map<String, String> parameterMap = new HashMap<String, String>();
-    Iterator<?> keys = jsonObject.keys();
-    while (keys.hasNext()) {
-      String key = keys.next().toString();
-      if (key.startsWith(Constants.PARAM_TOKEN)) {
-        parameterMap.put(key, jsonObject.getString(key));
-        String value = jsonObject.isNull(key) ? null
-            : jsonObject.getString(key);
-        parameterMap.put(key, value);
-      }
-    }
-    return parameterMap;
-  }
-
-  /**
-   * Returns the property fields (name => type) for a record.
-   */
-  public Map<String, Property<?>> getPropertiesFromRecordProxyType(
-      Class<? extends EntityProxy> record) throws SecurityException {
-    if (!EntityProxy.class.isAssignableFrom(record)) {
-      return Collections.emptyMap();
-    }
-
-    Map<String, Property<?>> properties = new LinkedHashMap<String, Property<?>>();
-    Method[] methods = record.getMethods();
-    for (Method method : methods) {
-      String methodName = method.getName();
-      String propertyName = null;
-      Property<?> newProperty = null;
-      if (methodName.startsWith("get")) {
-        propertyName = Introspector.decapitalize(methodName.substring(3));
-        if (propertyName.length() == 0) {
-          continue;
-        }
-        newProperty = getPropertyFromGenericType(propertyName,
-            method.getGenericReturnType());
-      } else if (methodName.startsWith("set")) {
-        propertyName = Introspector.decapitalize(methodName.substring(3));
-        if (propertyName.length() > 0) {
-          Type[] parameterTypes = method.getGenericParameterTypes();
-          if (parameterTypes.length > 0) {
-            newProperty = getPropertyFromGenericType(propertyName,
-                parameterTypes[parameterTypes.length - 1]);
-          }
-        }
-      }
-      if (newProperty == null) {
-        continue;
-      }
-      Property<?> existing = properties.put(propertyName, newProperty);
-      if (existing != null && !existing.equals(newProperty)) {
-        throw new IllegalStateException(String.format(
-            "In %s, mismatched getter and setter types for property %s, "
-                + "found %s and %s", record.getName(), propertyName,
-            existing.getName(), newProperty.getName()));
-      }
-    }
-    return properties;
-  }
-
-  @SuppressWarnings({"unchecked", "rawtypes"})
-  public Property<?> getPropertyFromGenericType(String propertyName, Type type) {
-    if (type instanceof ParameterizedType) {
-      ParameterizedType pType = (ParameterizedType) type;
-      Class<?> rawType = (Class<Object>) pType.getRawType();
-      Type[] typeArgs = pType.getActualTypeArguments();
-      if (Collection.class.isAssignableFrom(rawType)) {
-        if (typeArgs.length == 1) {
-          Type leafType = typeArgs[0];
-          if (leafType instanceof Class) {
-            return new CollectionProperty(propertyName, rawType,
-                (Class<?>) leafType);
-          }
-        }
-      }
-    } else {
-      return new Property<Object>(propertyName, (Class<Object>) type);
-    }
-    return null;
-  }
-
-  /**
-   * Returns the property value, in the specified type, from the request object.
-   * The value is put in the DataStore.
-   * 
-   * @throws InstantiationException
-   * @throws NoSuchMethodException
-   * @throws InvocationTargetException
-   * @throws IllegalAccessException
-   * @throws SecurityException
-   */
-  public Object getPropertyValueFromRequest(JSONArray recordArray, int index,
-      Class<?> propertyType) throws JSONException, SecurityException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException,
-      InstantiationException {
-    return decodeParameterValue(propertyType, recordArray.get(index).toString());
-  }
-
-  public Object getPropertyValueFromRequest(JSONObject recordObject,
-      String key, Class<?> propertyType) throws JSONException,
-      SecurityException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException, InstantiationException {
-    return decodeParameterValue(propertyType, recordObject.isNull(key) ? null
-        : recordObject.get(key).toString());
-  }
-
-  public JSONObject getViolationsAsJson(
-      Set<ConstraintViolation<Object>> violations) throws JSONException {
-    JSONObject violationsAsJson = new JSONObject();
-    for (ConstraintViolation<Object> violation : violations) {
-      violationsAsJson.put(violation.getPropertyPath().toString(),
-          violation.getMessage());
-    }
-    return violationsAsJson;
-  }
-
-  public Object invokeDomainMethod(Object domainObject, Method domainMethod,
-      Object args[]) throws IllegalAccessException, InvocationTargetException {
-    return domainMethod.invoke(domainObject, args);
-  }
-
-  @SuppressWarnings("unchecked")
-  public JSONObject processJsonRequest(String jsonRequestString)
-      throws JSONException, NoSuchMethodException, IllegalAccessException,
-      InvocationTargetException, ClassNotFoundException, SecurityException,
-      InstantiationException {
-    RequestDefinition operation;
-    JSONObject topLevelJsonObject = new JSONObject(jsonRequestString);
-
-    String operationName = topLevelJsonObject.getString(Constants.OPERATION_TOKEN);
-    String propertyRefsString = topLevelJsonObject.has(Constants.PROPERTY_REF_TOKEN)
-        ? topLevelJsonObject.getString(Constants.PROPERTY_REF_TOKEN) : "";
-    propertyRefs = RequestProperty.parse(propertyRefsString);
-
-    operation = getOperation(operationName);
-    Class<?> domainClass = Class.forName(operation.getDomainClassName());
-
-    /*
-     * The use of JRP's classloader mirrors the use of Class.forName elsewhere.
-     * It's wrong, but it's consistently wrong.
-     */
-    RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
-        log, new RequestFactoryInterfaceValidator.ClassLoaderLoader(
-            JsonRequestProcessor.class.getClassLoader()));
-    validator.validateRequestContext(operationName.substring(0,
-        operationName.indexOf("::")));
-    if (validator.isPoisoned()) {
-      log.severe("Unable to validate RequestContext");
-      throw new RuntimeException();
-    }
-
-    Method domainMethod = domainClass.getMethod(
-        operation.getDomainMethod().getName(), operation.getParameterTypes());
-    if (Modifier.isStatic(domainMethod.getModifiers()) == operation.isInstance()) {
-      throw new IllegalArgumentException("the " + domainMethod.getName()
-          + " should " + (operation.isInstance() ? "not " : "") + "be static");
-    }
-
-    if (topLevelJsonObject.has(Constants.CONTENT_TOKEN)) {
-      // updates involvedKeys and dvsDataMap.
-      decodeDVS(topLevelJsonObject.getString(Constants.CONTENT_TOKEN));
-    }
-    // get the domain object (for instance methods) and args.
-    Object args[][] = getObjectsFromParameterMap(operation.isInstance(),
-        getParameterMap(topLevelJsonObject),
-        operation.getRequestParameterTypes());
-    // Construct beforeDataMap
-    constructBeforeDataMap();
-    // Construct afterDvsDataMap.
-    constructAfterDvsDataMapAfterCallingSetters();
-
-    // violations are the only sideEffects at this point.
-    JSONArray violationsAsJson = getViolations();
-    if (violationsAsJson.length() > 0) {
-      JSONObject envelop = new JSONObject();
-      envelop.put(Constants.VIOLATIONS_TOKEN, violationsAsJson);
-      return envelop;
-    }
-
-    // resolve parameters that are so far just EntityKeys.
-    // TODO: resolve parameters other than the domainInstance
-    EntityKey domainEntityKey = null;
-    if (args[0][0] != null) {
-      // Instance method, replace the key with the actual receiver
-      domainEntityKey = (EntityKey) args[0][0];
-      EntityData domainEntityData = afterDvsDataMap.get(domainEntityKey);
-      if (domainEntityData != null) {
-        args[0][0] = domainEntityData.entityInstance;
-        assert args[0][0] != null;
-      }
-    }
-    Object result = invokeDomainMethod(args[0][0], domainMethod, args[1]);
-
-    JSONObject sideEffects = getSideEffects();
-
-    if (result != null
-        && (result instanceof List<?>) != List.class.isAssignableFrom(operation.getDomainMethod().getReturnType())) {
-      throw new IllegalArgumentException(String.format(
-          "Type mismatch, expected %s%s, but %s returns %s",
-          List.class.isAssignableFrom(operation.getReturnType()) ? "list of "
-              : "", operation.getReturnType(), domainMethod,
-          domainMethod.getReturnType()));
-    }
-
-    JSONObject envelop = new JSONObject();
-    if (result instanceof List<?> || result instanceof Set<?>) {
-      envelop.put(Constants.RESULT_TOKEN, toJsonArray(operation, result));
-    } else if (result instanceof Number || result instanceof Enum<?>
-        || result instanceof String || result instanceof Date
-        || result instanceof Character || result instanceof Boolean) {
-      envelop.put(Constants.RESULT_TOKEN, encodePropertyValue(result));
-    } else {
-      Class<? extends EntityProxy> returnType = null;
-      if (operation.getDomainClassName().equals(FindService.class.getName())) {
-        // HACK.
-        if (involvedKeys.size() == 1) {
-          returnType = involvedKeys.iterator().next().proxyType;
-        } else {
-          System.out.println("null find");
-        }
-      } else {
-        returnType = (Class<? extends EntityProxy>) operation.getReturnType();
-      }
-      Object jsonObject = getJsonObject(result, returnType, propertyRefs);
-      envelop.put(Constants.RESULT_TOKEN, jsonObject);
-    }
-    envelop.put(Constants.SIDE_EFFECTS_TOKEN, sideEffects);
-    envelop.put(Constants.RELATED_TOKEN, encodeRelatedObjectsToJson());
-    return envelop;
-  }
-
-  public void setExceptionHandler(ExceptionHandler exceptionHandler) {
-    this.exceptionHandler = exceptionHandler;
-  }
-
-  public void setOperationRegistry(OperationRegistry operationRegistry) {
-    this.operationRegistry = operationRegistry;
-  }
-
-  public void validateKeys(JSONObject recordObject,
-      Set<String> declaredProperties) {
-    /*
-     * We don't need it by the time we're here (it's in the EntityKey), and it
-     * gums up the works.
-     */
-    recordObject.remove(Constants.ENCODED_ID_PROPERTY);
-    recordObject.remove(Constants.ENCODED_VERSION_PROPERTY);
-
-    Iterator<?> keys = recordObject.keys();
-    while (keys.hasNext()) {
-      String key = (String) keys.next();
-      if (!declaredProperties.contains(key)) {
-        throw new IllegalArgumentException("key " + key
-            + " is not permitted to be set");
-      }
-    }
-  }
-
-  /**
-   * Returns true iff the after and before JSONArray are different.
-   */
-  boolean hasChanged(JSONArray beforeArray, JSONArray afterArray)
-      throws JSONException {
-    if (beforeArray.length() != afterArray.length()) {
-      return true;
-    } else {
-      for (int i = 0; i < beforeArray.length(); i++) {
-        Object bVal = beforeArray.get(i);
-        Object aVal = afterArray.get(i);
-        if (aVal != null && bVal != null) {
-          if (aVal == bVal || aVal.equals(bVal)) {
-            continue;
-          }
-          if (aVal.getClass() != bVal.getClass()) {
-            return true;
-          }
-          if (aVal instanceof JSONObject) {
-            if (hasChanged((JSONObject) bVal, (JSONObject) aVal)) {
-              return true;
-            }
-          }
-          if (aVal instanceof JSONArray) {
-            if (hasChanged((JSONArray) bVal, (JSONArray) aVal)) {
-              return true;
-            }
-          }
-        }
-        if (aVal != bVal) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Returns true iff the after and before JSONObjects are different.
-   */
-  boolean hasChanged(JSONObject before, JSONObject after) throws JSONException {
-    if (before == null) {
-      return after != null;
-    }
-    // before != null
-    if (after == null) {
-      return true;
-    }
-    // before != null && after != null
-    Iterator<?> keyIterator = before.keys();
-    while (keyIterator.hasNext()) {
-      String key = keyIterator.next().toString();
-      Object beforeValue = before.isNull(key) ? null : before.get(key);
-      Object afterValue = after.isNull(key) ? null : after.get(key);
-      if (beforeValue == null) {
-        if (afterValue == null) {
-          continue;
-        }
-        return true;
-      }
-      if (afterValue == null) {
-        return true;
-      }
-      // equals method on JSONArray doesn't consider contents
-      if (!beforeValue.equals(afterValue)) {
-        if (beforeValue instanceof JSONArray && afterValue instanceof JSONArray) {
-          JSONArray beforeArray = (JSONArray) beforeValue;
-          JSONArray afterArray = (JSONArray) afterValue;
-          if (hasChanged(beforeArray, afterArray)) {
-            return true;
-          }
-        } else {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  private void addRelatedObject(String keyRef, Object returnValue,
-      Class<? extends EntityProxy> propertyType, RequestProperty propertyContext)
-      throws JSONException, IllegalAccessException, NoSuchMethodException,
-      InvocationTargetException {
-    if (!relatedObjects.containsKey(keyRef)) {
-      // put temporary value to prevent infinite recursion
-      relatedObjects.put(keyRef, null);
-      Object jsonObject = getJsonObject(returnValue, propertyType,
-          propertyContext);
-      if (jsonObject != JSONObject.NULL) {
-        // put real value
-        relatedObjects.put(keyRef, (JSONObject) jsonObject);
-      }
-    }
-  }
-
-  private JSONObject buildExceptionResponse(Throwable throwable) {
-    JSONObject exceptionResponse = new JSONObject();
-    ServerFailure failure = exceptionHandler.createServerFailure(throwable);
-    try {
-      JSONObject exceptionMessage = new JSONObject();
-
-      String message = failure.getMessage();
-      String exceptionType = failure.getExceptionType();
-      String stackTraceString = failure.getStackTraceString();
-
-      if (message != null && message.length() != 0) {
-        exceptionMessage.put("message", message);
-      }
-      if (exceptionType != null && exceptionType.length() != 0) {
-        exceptionMessage.put("type", exceptionType);
-      }
-      if (stackTraceString != null && stackTraceString.length() != 0) {
-        exceptionMessage.put("trace", stackTraceString);
-      }
-      exceptionResponse.put("exception", exceptionMessage);
-    } catch (JSONException jsonException) {
-      throw new IllegalStateException(jsonException);
-    }
-    return exceptionResponse;
-  }
-
-  @SuppressWarnings("unchecked")
-  private Class<? extends EntityProxy> castToRecordClass(Class<?> propertyType) {
-    return (Class<? extends EntityProxy>) propertyType;
-  }
-
-  private void constructAfterDvsDataMapAfterCallingSetters()
-      throws SecurityException, JSONException, IllegalAccessException,
-      InvocationTargetException, NoSuchMethodException, InstantiationException {
-    /*
-     * EntityKeys can be added to involvedKeys if a setter sets an EntityType
-     * that was null before calling the setter, so we need to loop in a way that
-     * will protected against ConcurrentModificationExceptions.
-     */
-    afterDvsDataMap = new HashMap<EntityKey, EntityData>();
-    Set<EntityKey> done = new HashSet<EntityKey>();
-    Set<EntityKey> queue = new HashSet<EntityKey>(involvedKeys);
-    while (!queue.isEmpty()) {
-      for (EntityKey entityKey : queue) {
-        // use the beforeDataMap and dvsDataMap
-        DvsData dvsData = dvsDataMap.get(entityKey);
-        if (dvsData != null) {
-          EntityData entityData = getEntityDataForRecordWithSettersApplied(
-              entityKey, dvsData.jsonObject, dvsData.writeOperation);
-          if (entityKey.isFuture) {
-            // TODO: assert that the id is null for entityData.entityInstance
-          }
-          afterDvsDataMap.put(entityKey, entityData);
-        } else if (entityKey.isFuture) {
-          // The client-side DVS failed to include a CREATE operation.
-          throw new RuntimeException("Future object with no dvsData");
-        } else {
-          /*
-           * Involved, but not in the deltaValueStore -- param ref to an
-           * unedited, existing object.
-           */
-          SerializedEntity serializedEntity = beforeDataMap.get(entityKey);
-          Object entityInstance = (serializedEntity == null)
-              ? getEntityInstance(entityKey) : serializedEntity.entityInstance;
-          if (entityInstance != null) {
-            afterDvsDataMap.put(entityKey, new EntityData(entityInstance, null));
-          }
-        }
-      }
-
-      // Reset the queue.
-      done.addAll(queue);
-      queue.addAll(involvedKeys);
-      queue.removeAll(done);
-    }
-  }
-
-  /**
-   * Constructs the beforeDataMap.
-   * 
-   * <p>
-   * Algorithm: go through the involvedKeys, and find the entityData
-   * corresponding to each.
-   */
-  private void constructBeforeDataMap() throws IllegalArgumentException,
-      SecurityException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException, JSONException, InstantiationException {
-    for (EntityKey entityKey : involvedKeys) {
-      if (entityKey.isFuture) {
-        // the "before" is empty.
-        continue;
-      }
-      beforeDataMap.put(entityKey, getSerializedEntity(entityKey));
-    }
-  }
-
-  private Collection<Object> createCollection(Class<?> colType) {
-    return colType == List.class ? new ArrayList<Object>()
-        : colType == Set.class ? new HashSet<Object>() : null;
-  }
-
-  /**
-   * Decode deltaValueStore to populate involvedKeys and dvsDataMap.
-   */
-  private void decodeDVS(String content) throws SecurityException,
-      JSONException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException, InstantiationException {
-    JSONObject jsonObject = new JSONObject(content);
-    for (WriteOperation writeOperation : WriteOperation.values()) {
-      if (!jsonObject.has(writeOperation.name())) {
-        continue;
-      }
-      JSONArray reportArray = new JSONArray(
-          jsonObject.getString(writeOperation.name()));
-      int length = reportArray.length();
-      if (length == 0) {
-        throw new IllegalArgumentException("No json array for "
-            + writeOperation.name() + " should have been sent");
-      }
-      for (int i = 0; i < length; i++) {
-        JSONObject recordWithSchema = reportArray.getJSONObject(i);
-        Iterator<?> iterator = recordWithSchema.keys();
-        String recordToken = (String) iterator.next();
-        if (iterator.hasNext()) {
-          throw new IllegalArgumentException(
-              "There cannot be more than one record token");
-        }
-        JSONObject recordObject = recordWithSchema.getJSONObject(recordToken);
-        Class<? extends EntityProxy> record = getRecordFromClassToken(recordToken);
-        EntityKey entityKey = new EntityKey(
-            recordObject.getString(Constants.ENCODED_ID_PROPERTY),
-            (writeOperation == WriteOperation.PERSIST), record);
-        involvedKeys.add(entityKey);
-        dvsDataMap.put(entityKey, new DvsData(recordObject, writeOperation));
-      }
-    }
-  }
-
-  private String encodeId(Object id) {
-    if (id instanceof String) {
-      return base64Encode((String) id);
-    }
-    return encodePropertyValue(id).toString();
-  }
-
-  @SuppressWarnings("unchecked")
-  private String encodeRelated(Class<?> propertyType, String propertyName,
-      RequestProperty propertyContext, Object returnValue)
-      throws NoSuchMethodException, IllegalAccessException,
-      InvocationTargetException, JSONException {
-    String encodedId = isEntityReference(returnValue, propertyType);
-
-    String keyRef = new EntityKey(encodedId, false,
-        (Class<? extends EntityProxy>) propertyType).toString();
-
-    addRelatedObject(keyRef, returnValue, castToRecordClass(propertyType),
-        propertyContext.getProperty(propertyName));
-    return keyRef;
-  }
-
-  private JSONObject encodeRelatedObjectsToJson() throws JSONException {
-    JSONObject array = new JSONObject();
-    for (Map.Entry<String, JSONObject> entry : relatedObjects.entrySet()) {
-      array.put(entry.getKey(), entry.getValue());
-    }
-    return array;
-  }
-
-  private JSONObject getCreateReturnRecord(EntityKey originalEntityKey,
-      EntityData entityData) throws SecurityException, JSONException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException {
-    // id/futureId, the identifying field is sent back from the incoming record.
-    assert originalEntityKey.isFuture;
-    Object entityInstance = entityData.entityInstance;
-    assert entityInstance != null;
-    Object newId = getRawPropertyValueFromDatastore(entityInstance,
-        Constants.ENTITY_ID_PROPERTY);
-    if (newId == null) {
-      // no changeRecord for this CREATE.
-      return null; 
-    }
-
-    newId = encodeId(newId);
-    JSONObject returnObject = getJsonObjectWithIdAndVersion(
-        getSchemaAndId(originalEntityKey.proxyType, newId), entityInstance,
-        propertyRefs);
-    // violations have already been taken care of.
-    returnObject.put(Constants.ENCODED_FUTUREID_PROPERTY,
-        originalEntityKey.encodedId + "");
-    return returnObject;
-  }
-
-  /**
-   * Given param0, return the EntityKey. String is of the form
-   * "239@NO@com....EmployeeRecord" or "239@IS@com...EmployeeRecord".
-   */
-  private EntityKey getEntityKey(String string) {
-    String parts[] = string.split("@");
-    assert parts.length == 3;
-
-    String encodedId = parts[0];
-    return new EntityKey(encodedId, "IS".equals(parts[1]),
-        getRecordFromClassToken(parts[2]));
-  }
-
-  private Method getIdMethodForEntity(Class<?> entityType)
-      throws NoSuchMethodException {
-    Method idMethod = entityType.getMethod(getMethodNameFromPropertyName(
-        Constants.ENTITY_ID_PROPERTY, "get"));
-    return idMethod;
-  }
-
-  private JSONObject getJsonObjectWithIdAndVersion(String encodedId,
-      Object entityElement, RequestProperty propertyContext)
-      throws JSONException, SecurityException, NoSuchMethodException,
-      IllegalAccessException, InvocationTargetException {
-    JSONObject jsonObject = new JSONObject();
-    jsonObject.put(Constants.ENCODED_ID_PROPERTY, encodedId);
-    jsonObject.put(
-        Constants.ENCODED_VERSION_PROPERTY,
-        encodePropertyValueFromDataStore(entityElement,
-            Constants.ENTITY_VERSION_PROPERTY,
-            Constants.ENTITY_VERSION_PROPERTY.getName(), propertyContext));
-    return jsonObject;
-  }
-
-  private Object getPropertyValueFromRequestCached(JSONArray recordArray,
-      int index, Property<?> dtoProperty) throws JSONException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException,
-      InstantiationException {
-    Object propertyValue;
-    Class<?> leafType = dtoProperty instanceof CollectionProperty<?, ?>
-        ? ((CollectionProperty<?, ?>) dtoProperty).getLeafType()
-        : dtoProperty.getType();
-
-    // if the property type is a Proxy, we expect an encoded Key string
-    if (EntityProxy.class.isAssignableFrom(leafType)) {
-      // check to see if we've already decoded this object from JSON
-      EntityKey propKey = getEntityKey(recordArray.getString(index));
-      // containsKey is used here because an entity lookup can return null
-      Object cacheValue = cachedEntityLookup.get(propKey);
-      if (cachedEntityLookup.containsKey(propKey)) {
-        propertyValue = cacheValue;
-      } else {
-        propertyValue = getPropertyValueFromRequest(recordArray, index,
-            leafType);
-      }
-    } else {
-      propertyValue = getPropertyValueFromRequest(recordArray, index, leafType);
-    }
-    return propertyValue;
-  }
-
-  private Object getPropertyValueFromRequestCached(JSONObject recordObject,
-      Map<String, Property<?>> propertiesInProxy, String key,
-      Property<?> dtoProperty) throws JSONException, IllegalAccessException,
-      InvocationTargetException, NoSuchMethodException, InstantiationException {
-    Object propertyValue;
-    if (!recordObject.isNull(key)
-        && EntityProxy.class.isAssignableFrom(dtoProperty.getType())) {
-      // if the property type is a Proxy, we expect an encoded Key string
-      EntityKey propKey = getEntityKey(recordObject.getString(key));
-      // check to see if we've already decoded this object from JSON
-      Object cacheValue = cachedEntityLookup.get(propKey);
-      // containsKey is used here because an entity lookup can return null
-      if (cachedEntityLookup.containsKey(propKey)) {
-        propertyValue = cacheValue;
-      } else {
-        propertyValue = getPropertyValueFromRequest(recordObject, key,
-            propertiesInProxy.get(key).getType());
-      }
-    } else {
-      propertyValue = getPropertyValueFromRequest(recordObject, key,
-          propertiesInProxy.get(key).getType());
-    }
-    return propertyValue;
-  }
-
-  private Object getRawPropertyValueFromDatastore(Object entityElement,
-      String propertyName) throws SecurityException, NoSuchMethodException,
-      IllegalAccessException, InvocationTargetException {
-    String methodName = getMethodNameFromPropertyName(propertyName, "get");
-    Method method = entityElement.getClass().getMethod(methodName);
-    return method.invoke(entityElement);
-  }
-
-  private String getSchemaAndId(Class<? extends EntityProxy> proxyType,
-      Object newId) {
-    return proxyType.getName() + "@" + newId;
-  }
-
-  private SerializedEntity getSerializedEntity(EntityKey entityKey)
-      throws IllegalArgumentException, SecurityException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException,
-      JSONException, InstantiationException {
-
-    Object entityInstance = getEntityInstance(entityKey);
-    JSONObject serializedEntity = serializeEntity(entityInstance, entityKey);
-    return new SerializedEntity(entityInstance, serializedEntity);
-  }
-
-  /**
-   * Returns a JSONObject with at most three keys: CREATE, UPDATE, DELETE. Each
-   * value is a JSONArray of JSONObjects.
-   */
-  private JSONObject getSideEffects() throws SecurityException, JSONException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException,
-      IllegalArgumentException, InstantiationException {
-    JSONObject sideEffects = new JSONObject();
-    JSONArray createArray = new JSONArray();
-    JSONArray deleteArray = new JSONArray();
-    JSONArray updateArray = new JSONArray();
-    for (EntityKey entityKey : involvedKeys) {
-      EntityData entityData = afterDvsDataMap.get(entityKey);
-      if (entityData == null) {
-        continue;
-      }
-      // handle CREATE
-      if (entityKey.isFuture) {
-        JSONObject createRecord = getCreateReturnRecord(entityKey, entityData);
-        if (createRecord != null) {
-          createArray.put(createRecord);
-        }
-        continue;
-      }
-      // handle DELETE
-      Object entityInstanceAfterOperation = getEntityInstance(entityKey);
-      if (null == entityInstanceAfterOperation) {
-        JSONObject deleteRecord = new JSONObject();
-        deleteRecord.put(Constants.ENCODED_ID_PROPERTY,
-            getSchemaAndId(entityKey.proxyType, entityKey.encodedId));
-        deleteArray.put(deleteRecord);
-        continue;
-      }
-      /*
-       * Send an UPDATE if the client is at a version different than that of the
-       * server or if the server version has changed after invoking the domain
-       * method.
-       */
-      boolean clientNeedsUpdating = false;
-      DvsData dvsData = dvsDataMap.get(entityKey);
-      if (dvsData != null && dvsData.version != null) {
-        Integer serverVersion = (Integer) getRawPropertyValueFromDatastore(
-            entityInstanceAfterOperation,
-            Constants.ENTITY_VERSION_PROPERTY.getName());
-        if (!dvsData.version.equals(serverVersion)) {
-          clientNeedsUpdating = true;
-        }
-      }
-      if (clientNeedsUpdating
-          || hasServerVersionChanged(entityKey, entityInstanceAfterOperation)) {
-        updateArray.put(getJsonObjectWithIdAndVersion(
-            getSchemaAndId(entityKey.proxyType, entityKey.encodedId),
-            entityInstanceAfterOperation, propertyRefs));
-      }
-    }
-    if (createArray.length() > 0) {
-      sideEffects.put(WriteOperation.PERSIST.name(), createArray);
-    }
-    if (deleteArray.length() > 0) {
-      sideEffects.put(WriteOperation.DELETE.name(), deleteArray);
-    }
-    if (updateArray.length() > 0) {
-      sideEffects.put(WriteOperation.UPDATE.name(), updateArray);
-    }
-    return sideEffects;
-  }
-
-  private JSONArray getViolations() throws JSONException {
-    JSONArray violations = new JSONArray();
-    for (EntityKey entityKey : involvedKeys) {
-      EntityData entityData = afterDvsDataMap.get(entityKey);
-      if (entityData == null || entityData.violations == null
-          || entityData.violations.length() == 0) {
-        continue;
-      }
-      DvsData dvsData = dvsDataMap.get(entityKey);
-      if (dvsData != null) {
-        JSONObject returnObject = new JSONObject();
-        returnObject.put(Constants.VIOLATIONS_TOKEN, entityData.violations);
-        if (entityKey.isFuture) {
-          returnObject.put(Constants.ENCODED_FUTUREID_PROPERTY,
-              entityKey.encodedId);
-          returnObject.put(Constants.ENCODED_ID_PROPERTY,
-              getSchemaAndId(entityKey.proxyType, null));
-        } else {
-          returnObject.put(Constants.ENCODED_ID_PROPERTY,
-              getSchemaAndId(entityKey.proxyType, entityKey.encodedId));
-        }
-        violations.put(returnObject);
-      }
-    }
-    return violations;
-  }
-
-  private boolean hasServerVersionChanged(EntityKey entityKey,
-      Object entityInstanceAfterOperation) throws IllegalArgumentException,
-      SecurityException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException, JSONException {
-    SerializedEntity beforeEntity = beforeDataMap.get(entityKey);
-    if (beforeEntity != null
-        && hasChanged(beforeEntity.serializedEntity,
-            serializeEntity(entityInstanceAfterOperation, entityKey))) {
-      return true;
-    }
-    return false;
-  }
-
-  private String isEntityReference(Object entity, Class<?> proxyPropertyType)
-      throws SecurityException, NoSuchMethodException,
-      IllegalArgumentException, IllegalAccessException,
-      InvocationTargetException {
-    if (entity != null && EntityProxy.class.isAssignableFrom(proxyPropertyType)) {
-      Method idMethod = getIdMethodForEntity(entity.getClass());
-      return encodeId(idMethod.invoke(entity));
-    }
-    return null;
-  }
-
-  /**
-   * returns true if the property has been requested. TODO: use the properties
-   * that should be coming with the request.
-   * 
-   * @param p the field of entity ref
-   * @param propertyContext the root of the current dotted property reference
-   * @return has the property value been requested
-   */
-  private boolean requestedProperty(Property<?> p,
-      RequestProperty propertyContext) {
-    if (propertyContext == null) {
-      return false;
-    }
-    Class<?> leafType = p.getType();
-    if (p instanceof CollectionProperty<?, ?>) {
-      leafType = ((CollectionProperty<?, ?>) p).getLeafType();
-    }
-    if (EntityProxy.class.isAssignableFrom(leafType)) {
-      return propertyContext.hasProperty(p.getName());
-    }
-
-    return true;
-  }
-
-  /**
-   * Return the client-visible properties of an entityInstance as a JSONObject.
-   * <p>
-   * TODO: clean up the copy-paste from getJSONObject.
-   */
-  private JSONObject serializeEntity(Object entityInstance, EntityKey entityKey)
-      throws SecurityException, NoSuchMethodException,
-      IllegalArgumentException, IllegalAccessException,
-      InvocationTargetException, JSONException {
-    if (entityInstance == null) {
-      return null;
-    }
-    JSONObject jsonObject = new JSONObject();
-    jsonObject.put(Constants.ENCODED_ID_PROPERTY, entityKey.encodedId);
-    for (Property<?> p : allProperties(entityKey.proxyType)) {
-      String propertyName = p.getName();
-      String methodName = getMethodNameFromPropertyName(propertyName, "get");
-      Method method = entityInstance.getClass().getMethod(methodName);
-      Object returnValue = method.invoke(entityInstance);
-
-      Object propertyValue;
-      String encodedEntityId = isEntityReference(returnValue, p.getType());
-      if (returnValue == null) {
-        propertyValue = JSONObject.NULL;
-      } else if (encodedEntityId != null) {
-        propertyValue = encodedEntityId
-            + "@NO@"
-            + operationRegistry.getSecurityProvider().encodeClassType(
-                p.getType());
-      } else if (p instanceof CollectionProperty<?, ?>) {
-        JSONArray array = new JSONArray();
-        for (Object val : ((Collection<?>) returnValue)) {
-          String encodedIdVal = isEntityReference(val, p.getType());
-          if (encodedIdVal != null) {
-            propertyValue = encodedIdVal
-                + "@NO@"
-                + operationRegistry.getSecurityProvider().encodeClassType(
-                    p.getType());
-          } else {
-            propertyValue = encodePropertyValue(val);
-          }
-          array.put(propertyValue);
-        }
-        propertyValue = array;
-      } else {
-        propertyValue = encodePropertyValue(returnValue);
-      }
-      jsonObject.put(propertyName, propertyValue);
-    }
-    return jsonObject;
-  }
-
-  @SuppressWarnings("unchecked")
-  private Object toJsonArray(RequestDefinition operation, Object result)
-      throws IllegalAccessException, JSONException, NoSuchMethodException,
-      InvocationTargetException {
-    JSONArray jsonArray = getJsonArray((Collection<?>) result,
-        (Class<? extends EntityProxy>) operation.getReturnType());
-    return jsonArray;
-  }
-
-  /**
-   * Update propertiesInRecord based on the types of entity type.
-   */
-  private Map<String, Class<?>> updatePropertyTypes(
-      Map<String, Property<?>> propertiesInProxy, Class<?> entity) {
-    Map<String, Class<?>> toReturn = new HashMap<String, Class<?>>();
-
-    /*
-     * TODO: this logic fails if the field and getter/setter methods are
-     * differently named.
-     */
-    for (Field field : entity.getDeclaredFields()) {
-      Property<?> property = propertiesInProxy.get(field.getName());
-      if (property == null) {
-        continue;
-      }
-      Class<?> fieldType = property.getType();
-      if (property instanceof CollectionProperty<?, ?>) {
-        toReturn.put(field.getName(), fieldType);
-      } else if (fieldType != null) {
-        if (EntityProxy.class.isAssignableFrom(fieldType)) {
-          ProxyFor pFor = fieldType.getAnnotation(ProxyFor.class);
-          if (pFor != null) {
-            fieldType = pFor.value();
-          }
-          ProxyForName pFN = fieldType.getAnnotation(ProxyForName.class);
-          if (pFN != null) {
-            try {
-              fieldType = Class.forName(pFN.value(), false,
-                  Thread.currentThread().getContextClassLoader());
-            } catch (ClassNotFoundException ignored) {
-            }
-          }
-          // TODO: remove override declared method return type with field type
-          if (!fieldType.equals(field.getType())) {
-            fieldType = field.getType();
-          }
-        }
-        toReturn.put(field.getName(), fieldType);
-      }
-    }
-    return toReturn;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/Logging.java b/user/src/com/google/gwt/requestfactory/server/Logging.java
index 4bbef25..4c9e04e 100644
--- a/user/src/com/google/gwt/requestfactory/server/Logging.java
+++ b/user/src/com/google/gwt/requestfactory/server/Logging.java
@@ -27,30 +27,29 @@
  */
 public class Logging {
 
-  private static StackTraceDeobfuscator deobfuscator =
-    new StackTraceDeobfuscator("");
+  private static StackTraceDeobfuscator deobfuscator = new StackTraceDeobfuscator(
+      "");
 
   /**
    * Logs a message.
-   *
+   * 
    * @param logRecordJson a log record in JSON format.
    * @throws RemoteLoggingException if logging fails
    */
-  public static void logMessage(String logRecordJson) throws
-  RemoteLoggingException {
+  public static void logMessage(String logRecordJson)
+      throws RemoteLoggingException {
     // if the header does not exist, we pass null, which is handled gracefully
     // by the deobfuscation code.
-    String strongName =
-      RequestFactoryServlet.getThreadLocalRequest().getHeader(
-          RpcRequestBuilder.STRONG_NAME_HEADER);
-    RemoteLoggingServiceUtil.logOnServer(logRecordJson,
-        strongName, deobfuscator, null);
+    String strongName = RequestFactoryServlet.getThreadLocalRequest().getHeader(
+        RpcRequestBuilder.STRONG_NAME_HEADER);
+    RemoteLoggingServiceUtil.logOnServer(logRecordJson, strongName,
+        deobfuscator, null);
   }
 
   /**
    * This function is only for server side use which is why it's not in the
    * LoggingRequest interface.
-   *
+   * 
    * @param dir a directory, specified as a String
    */
   public static void setSymbolMapsDirectory(String dir) {
@@ -63,7 +62,7 @@
 
   /**
    * Returns the id of this instance.
-   *
+   * 
    * @return a String id
    * @see #setId(String)
    */
@@ -73,7 +72,7 @@
 
   /**
    * Returns the version of this instance.
-   *
+   * 
    * @return an Integer version number
    * @see #setVersion(Integer)
    */
@@ -83,7 +82,7 @@
 
   /**
    * Sets the id on this instance.
-   *
+   * 
    * @param id a String id
    * @see #getId()
    */
@@ -93,7 +92,7 @@
 
   /**
    * Sets the version of this instance.
-   *
+   * 
    * @param version an Integer version number
    * @see #getVersion()
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
deleted file mode 100644
index 014d7e3..0000000
--- a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-/**
- * Maps operation name to {RequestDefinition}.
- */
-interface OperationRegistry {
-
-  /**
-   * Returns the {@link RequestDefinition} associated with the given operation.
-   *
-   * @param operationName the operation name as a String
-   * @return a {@link RequestDefinition} instance
-   */
-  RequestDefinition getOperation(String operationName);
-
-  /**
-   * Returns the {@link RequestSecurityProvider} associated with this
-   * {@link OperationRegistry}.
-   *
-   * @return a {@link RequestSecurityProvider} instance
-   */
-  RequestSecurityProvider getSecurityProvider();
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
deleted file mode 100644
index 1ee6f8f..0000000
--- a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import com.google.gwt.requestfactory.shared.EntityProxy;
-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.Service;
-import com.google.gwt.requestfactory.shared.ServiceName;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.List;
-import java.util.Set;
-
-/**
- * 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.server.RequestDefinition}.
- */
-class ReflectionBasedOperationRegistry implements OperationRegistry {
-
-  class ReflectiveRequestDefinition implements RequestDefinition {
-
-    private Class<?> requestClass;
-
-    private Method requestMethod;
-
-    private Class<?> domainClass;
-
-    private Method domainMethod;
-
-    private boolean isInstance;
-
-    public ReflectiveRequestDefinition(Class<?> requestClass,
-        Method requestMethod, Class<?> domainClass, Method domainMethod,
-        boolean isInstance) {
-      this.requestClass = requestClass;
-      this.requestMethod = requestMethod;
-      this.domainClass = domainClass;
-      this.domainMethod = domainMethod;
-      this.isInstance = isInstance;
-    }
-
-    public String getDomainClassName() {
-      return domainClass.getCanonicalName();
-    }
-
-    public Method getDomainMethod() {
-      return domainMethod;
-    }
-
-    public String getDomainMethodName() {
-      return getDomainMethod().getName();
-    }
-
-    public Class<?>[] getParameterTypes() {
-      return domainMethod.getParameterTypes();
-    }
-
-    /**
-     * Treat instance invocations as though they were static implementations.
-     */
-    public Type[] getRequestParameterTypes() {
-      Type[] toReturn = requestMethod.getGenericParameterTypes();
-      if (isInstance()) {
-        // Instance method, add a "this" parameter at the beginning
-        Type[] newReturn = new Type[toReturn.length + 1];
-        newReturn[0] = domainMethod.getDeclaringClass();
-        System.arraycopy(toReturn, 0, newReturn, 1, toReturn.length);
-        toReturn = newReturn;
-      }
-      return toReturn;
-    }
-
-    public Class<?> getReturnType() {
-      Class<?> domainReturnType = getReturnTypeFromParameter(domainMethod,
-          domainMethod.getGenericReturnType());
-      Class<?> requestReturnType = getReturnTypeFromParameter(requestMethod,
-          requestMethod.getGenericReturnType());
-      if (EntityProxy.class.isAssignableFrom(requestReturnType)) {
-        ProxyFor annotation = requestReturnType.getAnnotation(ProxyFor.class);
-        ProxyForName nameAnnotation = requestReturnType.getAnnotation(ProxyForName.class);
-
-        Class<?> dtoClass = null;
-        if (annotation != null) {
-          dtoClass = annotation.value();
-        } else if (nameAnnotation != null) {
-          try {
-            dtoClass = Class.forName(nameAnnotation.value(), false,
-                Thread.currentThread().getContextClassLoader());
-          } catch (ClassNotFoundException e) {
-            throw new IllegalArgumentException(
-                "Unknown type specified in ProxyForName annotation", e);
-          }
-        } else {
-          throw new IllegalArgumentException(
-              "Missing ProxyFor annotation on proxy type " + requestReturnType);
-        }
-        if (!dtoClass.equals(domainReturnType)) {
-          throw new IllegalArgumentException("Type mismatch between "
-              + domainMethod + " return type, and " + requestReturnType
-              + "'s ProxyFor annotation " + dtoClass);
-        }
-        return requestReturnType;
-      }
-      // primitive ?
-      return requestReturnType;
-    }
-
-    public boolean isInstance() {
-      return isInstance;
-    }
-
-    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)
-            || Request.class.isAssignableFrom(rawType)
-            || InstanceRequest.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 if (Set.class.isAssignableFrom(rawType)
-            || Request.class.isAssignableFrom(rawType)
-            || InstanceRequest.class.isAssignableFrom(rawType)) {
-          Class<?> rType = getTypeArgument(pType);
-          if (rType != null) {
-            if (Set.class.isAssignableFrom(rType)) {
-              return getReturnTypeFromParameter(method, rType);
-            }
-            return rType;
-          }
-          throw new IllegalArgumentException(
-              "Bad or missing type arguments for "
-                  + "Set return type on method " + method);
-        }
-      } else {
-        // Primitive?
-        return (Class<?>) type;
-      }
-      return null;
-    }
-
-    @SuppressWarnings("unchecked")
-    private Class<?> getTypeArgument(ParameterizedType type) {
-      Type[] params = type.getActualTypeArguments();
-      Type toExamine;
-      if (params.length == 1) {
-        toExamine = params[0];
-      } else if (InstanceRequest.class.equals(type.getRawType())) {
-        toExamine = params[1];
-      } else {
-        return null;
-      }
-
-      if (toExamine instanceof ParameterizedType) {
-        // if type is for example, RequestObject<List<T>> we return T
-        return getTypeArgument((ParameterizedType) toExamine);
-      }
-      // else, it might be a case like List<T> in which case we return T
-      return (Class<Object>) toExamine;
-    }
-  }
-
-  /**
-   * The separator used to delimit scopes.
-   */
-  public static final String SCOPE_SEPARATOR = "::";
-
-  private RequestSecurityProvider securityProvider;
-
-  /**
-   * Constructs a {@link ReflectionBasedOperationRegistry} instance with a given
-   * {@link RequestSecurityProvider}.
-   * 
-   * @param securityProvider a {@link RequestSecurityProvider} instance.
-   */
-  public ReflectionBasedOperationRegistry(
-      RequestSecurityProvider securityProvider) {
-    this.securityProvider = securityProvider;
-  }
-
-  /**
-   * Turns an operation in the form of package.requestClass::method into a
-   * RequestDefinition via reflection.
-   * 
-   * @param operationName the operation name as a String
-   * @return a {@link RequestDefinition} instance
-   */
-  public 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);
-      ServiceName domainClassNameAnnotation = requestClass.getAnnotation(ServiceName.class);
-      Class<?> domainClass;
-      if (domainClassAnnotation != null) {
-        domainClass = domainClassAnnotation.value();
-      } else if (domainClassNameAnnotation != null) {
-        domainClass = Class.forName(domainClassNameAnnotation.value(), false,
-            Thread.currentThread().getContextClassLoader());
-      } else {
-        return null;
-      }
-
-      Method requestMethod = findMethod(requestClass, domainMethodName);
-      Method domainMethod = findMethod(domainClass, domainMethodName);
-      if (requestMethod != null && domainMethod != null) {
-        boolean isInstance = InstanceRequest.class.isAssignableFrom(requestMethod.getReturnType());
-        if (isInstance == Modifier.isStatic(domainMethod.getModifiers())) {
-          throw new IllegalArgumentException("domain method " + domainMethod
-              + " and interface method " + requestMethod
-              + " don't match wrt instance/static");
-        }
-        return new ReflectiveRequestDefinition(requestClass, requestMethod,
-            domainClass, domainMethod, isInstance);
-      }
-
-      return null;
-    } catch (ClassNotFoundException e) {
-      throw new SecurityException("Access to non-existent class "
-          + reqClassName);
-    }
-  }
-
-  public RequestSecurityProvider getSecurityProvider() {
-    return securityProvider;
-  }
-
-  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/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
new file mode 100644
index 0000000..c2bfc34
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -0,0 +1,405 @@
+/*
+ * 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.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+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.messages.EntityCodex;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+/**
+ * A reflection-based implementation of ServiceLayer.
+ */
+public class ReflectiveServiceLayer implements ServiceLayer {
+
+  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()));
+
+  static {
+    Validator found;
+    try {
+      ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
+      found = validatorFactory.getValidator();
+    } catch (ValidationException e) {
+      log.log(Level.INFO, "Unable to initialize a JSR 303 Bean Validator", e);
+      found = null;
+    }
+    jsr303Validator = found;
+  }
+
+  private static String capitalize(String name) {
+    return Character.toUpperCase(name.charAt(0))
+        + (name.length() >= 1 ? name.substring(1) : "");
+  }
+
+  public Object createDomainObject(Class<?> clazz) {
+    Throwable ex;
+    try {
+      return clazz.newInstance();
+    } catch (InstantiationException e) {
+      return report("Could not create a new instance of the requested type");
+    } catch (IllegalAccessException e) {
+      ex = e;
+    }
+    return die(ex, "Could not create a new instance of domain type %s",
+        clazz.getCanonicalName());
+  }
+
+  public Class<?> getClientType(Class<?> domainClass) {
+    String name;
+    synchronized (validator) {
+      name = validator.getEntityProxyTypeName(domainClass.getName());
+    }
+    if (name != null) {
+      return forName(name).asSubclass(EntityProxy.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 (EntityProxy.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());
+  }
+
+  public String getFlatId(EntitySource source, Object domainObject) {
+    Object id = getProperty(domainObject, "id");
+    if (id == null) {
+      report("Could not retrieve id property for domain object");
+    }
+    if (!isKeyType(id.getClass())) {
+      die(null, "The type %s is not a valid key type",
+          id.getClass().getCanonicalName());
+    }
+    return EntityCodex.encode(source, id).getPayload();
+  }
+
+  public Object getProperty(Object domainObject, String property) {
+    Throwable toReport;
+    try {
+      Method method = domainObject.getClass().getMethod(
+          "get" + capitalize(property));
+      Object value = method.invoke(domainObject);
+      return value;
+    } catch (SecurityException e) {
+      toReport = e;
+    } catch (NoSuchMethodException e) {
+      toReport = e;
+    } catch (IllegalArgumentException e) {
+      toReport = e;
+    } catch (IllegalAccessException e) {
+      toReport = e;
+    } catch (InvocationTargetException e) {
+      return report(e);
+    }
+    return die(toReport, "Could not retrieve property %s", property);
+  }
+
+  public String getTypeToken(Class<?> clazz) {
+    return clazz.getName();
+  }
+
+  public int getVersion(Object domainObject) {
+    // TODO: Make version any value type
+    Object version = getProperty(domainObject, "version");
+    if (version == null) {
+      report("Could not retrieve version property");
+    }
+    if (!(version instanceof Integer)) {
+      die(null, "The getVersion() method on type %s did not return"
+          + " int or Integer", domainObject.getClass().getCanonicalName());
+    }
+    return ((Integer) version).intValue();
+  }
+
+  public Object invoke(Method domainMethod, Object[] args) {
+    Throwable ex;
+    try {
+      if (Modifier.isStatic(domainMethod.getModifiers())) {
+        return domainMethod.invoke(null, args);
+      } else {
+        Object[] realArgs = new Object[args.length - 1];
+        System.arraycopy(args, 1, realArgs, 0, realArgs.length);
+        return domainMethod.invoke(args[0], realArgs);
+      }
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      return report(e);
+    }
+    return die(ex, "Could not invoke method %s", domainMethod.getName());
+  }
+
+  public Object loadDomainObject(EntitySource source, Class<?> clazz,
+      String flatId) {
+    String searchFor = "find" + clazz.getSimpleName();
+    Method found = null;
+    for (Method method : clazz.getMethods()) {
+      if (!Modifier.isStatic(method.getModifiers())) {
+        continue;
+      }
+      if (!searchFor.equals(method.getName())) {
+        continue;
+      }
+      if (method.getParameterTypes().length != 1) {
+        continue;
+      }
+      if (!isKeyType(method.getParameterTypes()[0])) {
+        continue;
+      }
+      found = method;
+      break;
+    }
+    if (found == null) {
+      die(null, "Could not find static method %s with a single"
+          + " parameter of a key type", searchFor);
+    }
+    Object id = EntityCodex.decode(source, found.getParameterTypes()[0], null,
+        flatId);
+    if (id == null) {
+      report("Cannot load a domain object with a null id");
+    }
+    Throwable ex;
+    try {
+      return found.invoke(null, id);
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      return report(e);
+    }
+    return die(ex, "Cauld not load domain object using id", id.toString());
+  }
+
+  public Class<? extends EntityProxy> resolveClass(String typeToken) {
+    Class<?> found = forName(typeToken);
+    if (!EntityProxy.class.isAssignableFrom(found)) {
+      die(null, "The requested type %s is not assignable to %s", typeToken,
+          EntityProxy.class.getName());
+    }
+    synchronized (validator) {
+      validator.antidote();
+      validator.validateEntityProxy(found.getName());
+      if (validator.isPoisoned()) {
+        die(null, "The type %s did not pass RequestFactory validation",
+            found.getCanonicalName());
+      }
+    }
+    return found.asSubclass(EntityProxy.class);
+  }
+
+  public Method resolveDomainMethod(Method requestContextMethod) {
+    Class<?> enclosing = requestContextMethod.getDeclaringClass();
+    synchronized (validator) {
+      validator.antidote();
+      validator.validateRequestContext(enclosing.getName());
+      if (validator.isPoisoned()) {
+        die(null, "The type %s did not pass RequestFactory validation",
+            enclosing.getCanonicalName());
+      }
+    }
+
+    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 (EntityProxy.class.isAssignableFrom(parameterTypes[i])) {
+        domainArgs[i] = getDomainClass(parameterTypes[i].asSubclass(EntityProxy.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) {
+    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);
+  }
+
+  public void setProperty(Object domainObject, String property,
+      Class<?> expectedType, Object value) {
+    Method getId;
+    Throwable ex;
+    try {
+      getId = domainObject.getClass().getMethod("set" + capitalize(property),
+          expectedType);
+      getId.invoke(domainObject, value);
+      return;
+    } catch (SecurityException e) {
+      ex = e;
+    } catch (NoSuchMethodException e) {
+      ex = e;
+    } catch (IllegalArgumentException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      report(e);
+      return;
+    }
+    die(ex, "Could not locate getter for property %s in type %s", property,
+        domainObject.getClass().getCanonicalName());
+  }
+
+  public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
+    if (jsr303Validator != null) {
+      return jsr303Validator.validate(domainObject);
+    }
+    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);
+  }
+
+  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 boolean isKeyType(Class<?> clazz) {
+    return ValueCodex.canDecode(clazz)
+        || EntityProxy.class.isAssignableFrom(clazz);
+  }
+
+  /**
+   * 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));
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ReportableException.java b/user/src/com/google/gwt/requestfactory/server/ReportableException.java
new file mode 100644
index 0000000..292b452
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ReportableException.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;
+
+/**
+ * Encapsulates exceptions that should be thrown back to the client.
+ */
+@SuppressWarnings("serial")
+class ReportableException extends RuntimeException {
+  public ReportableException(String msg) {
+    super(msg);
+  }
+
+  public ReportableException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
+
+  public ReportableException(Throwable cause) {
+    super("Server Error: " + cause.getMessage(), cause);
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java b/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
deleted file mode 100644
index 896d88b..0000000
--- a/user/src/com/google/gwt/requestfactory/server/RequestDefinition.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-
-/**
- * Implemented by enums that define the mapping between request objects and
- * service methods.
- */
-interface RequestDefinition {
-  /**
-   * Returns the name of the (domain) class that contains the method to be
-   * invoked on the server.
-   *
-   * @return the domain class name as a String
-   */
-  String getDomainClassName();
-
-  /**
-   * Returns the method to be invoked on the server.
-   *
-   * @return the domain Method
-   */
-  Method getDomainMethod();
-
-  /**
-   * Returns the parameter types of the method to be invoked on the server.
-   *
-   * @return an array of Class objects for each parameter type
-   */
-  Class<?>[] getParameterTypes();
-
-  /**
-   * Returns the request parameter types.
-   *
-   * @return an array of Type objects for each request parameter
-   */
-  Type[] getRequestParameterTypes();
-
-  /**
-   * Returns the return type of the method to be invoked on the server.
-   * 
-   * @return a Class object representing the return type
-   */
-  Class<?> getReturnType();
-
-  /**
-   * Returns whether the domain method is an instance method.
-   *
-   * @return {@code true} if the domain method is an instance method
-   */
-  boolean isInstance();
-
-  /**
-   * Returns the name.
-   * 
-   * @return the name as a String
-   */
-  String name();
-}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index 7195af0..bbe81e9 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -28,7 +28,6 @@
 import com.google.gwt.dev.util.Name;
 import com.google.gwt.dev.util.Name.BinaryName;
 import com.google.gwt.dev.util.Name.SourceOrBinaryName;
-import com.google.gwt.requestfactory.client.impl.FindRequest;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.InstanceRequest;
 import com.google.gwt.requestfactory.shared.ProxyFor;
@@ -61,6 +60,26 @@
  * interfaces match their domain counterparts. This implementation examines the
  * classfiles directly in order to avoid the need to load the types into the
  * JVM.
+ * <p>
+ * This class is amenable to being used as a unit test:
+ * 
+ * <pre>
+ * public void testRequestFactory() {
+ *   Logger logger = Logger.getLogger("");
+ *   RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
+ *     logger, new ClassLoaderLoader(Thread.currentThread().getContextClassLoader()));
+ *   v.validateRequestContext(MyRequestContext.class.getName());
+ *   assertFalse(v.isPoisoned());
+ * }
+ * </pre>
+ * This class also has a {@code main} method and can be used as a build-time
+ * tool:
+ * 
+ * <pre>
+ * java -cp gwt-servlet.jar:your-code.jar \
+ *   com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidator \
+ *   com.example.MyRequestFactory
+ * </pre>
  */
 public class RequestFactoryInterfaceValidator {
   /**
@@ -91,12 +110,16 @@
   public interface Loader {
     /**
      * Returns true if the specified resource can be loaded.
+     * 
+     * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
      */
     boolean exists(String resource);
 
     /**
      * Returns an InputStream to access the specified resource, or
      * <code>null</code> if no such resource exists.
+     * 
+     * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
      */
     InputStream getResourceAsStream(String resource);
   }
@@ -201,12 +224,14 @@
     }
 
     public void poison(String msg, Object... args) {
+      poison();
       logger.logp(Level.SEVERE, currentType(), currentMethod(),
           String.format(msg, args));
       poisoned = true;
     }
 
     public void poison(String msg, Throwable t) {
+      poison();
       logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
       poisoned = true;
     }
@@ -247,6 +272,19 @@
       }
       return null;
     }
+
+    /**
+     * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
+     * current context.
+     */
+    private void poison() {
+      if (currentType != null) {
+        badTypes.add(currentType.getClassName());
+      }
+      if (parent != null) {
+        parent.poison();
+      }
+    }
   }
 
   /**
@@ -396,9 +434,15 @@
   }
 
   /**
+   * A set of binary type names that are known to be bad.
+   */
+  private final Set<String> badTypes = new HashSet<String>();
+
+  /**
    * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
    */
   private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
+  private final Map<Type, Type> domainToClientType = new HashMap<Type, Type>();
   /**
    * The type {@link EntityProxy}.
    */
@@ -417,6 +461,10 @@
    * A cache of all methods defined in a type hierarchy.
    */
   private final Map<Type, Set<RFMethod>> methodsInHierarchy = new HashMap<Type, Set<RFMethod>>();
+  /**
+   * The type {@link Object}.
+   */
+  private final Type objectType = Type.getObjectType("java/lang/Object");
   private final ErrorContext parentLogger;
   private boolean poisoned;
   /**
@@ -450,6 +498,14 @@
   }
 
   /**
+   * Reset the poisoned status of the validator so that it may be reused without
+   * destroying cached state.
+   */
+  public void antidote() {
+    poisoned = false;
+  }
+
+  /**
    * Returns true if validation failed.
    */
   public boolean isPoisoned() {
@@ -481,13 +537,7 @@
    *          EntityProxy subtype
    */
   public void validateEntityProxy(String binaryName) {
-    if (!Name.isBinaryName(binaryName)) {
-      parentLogger.poison("%s is not a binary name", binaryName);
-      return;
-    }
-
-    // Don't revalidate the same type
-    if (!validatedTypes.add(binaryName)) {
+    if (fastFail(binaryName)) {
       return;
     }
 
@@ -555,18 +605,7 @@
    * @see #validateEntityProxy(String)
    */
   public void validateRequestContext(String binaryName) {
-    if (!Name.isBinaryName(binaryName)) {
-      parentLogger.poison("%s is not a binary name", binaryName);
-      return;
-    }
-
-    // Don't revalidate the same type
-    if (!validatedTypes.add(binaryName)) {
-      return;
-    }
-
-    if (FindRequest.class.getName().equals(binaryName)) {
-      // Ignore FindRequest, it's a huge hack
+    if (fastFail(binaryName)) {
       return;
     }
 
@@ -620,13 +659,7 @@
    * @see #validateRequestContext(String)
    */
   public void validateRequestFactory(String binaryName) {
-    if (!Name.isBinaryName(binaryName)) {
-      parentLogger.poison("%s is not a binary name", binaryName);
-      return;
-    }
-
-    // Don't revalidate the same type
-    if (!validatedTypes.add(binaryName)) {
+    if (fastFail(binaryName)) {
       return;
     }
 
@@ -652,6 +685,16 @@
   }
 
   /**
+   * Given the binary name of a domain type, return the EntityProxy type that
+   * has been seen to map to the domain type.
+   */
+  String getEntityProxyTypeName(String domainTypeNameBinaryName) {
+    Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeNameBinaryName));
+    Type found = domainToClientType.get(key);
+    return found == null ? null : found.getClassName();
+  }
+
+  /**
    * Check that a given method RequestContext method declaration can be mapped
    * to the server's domain type.
    */
@@ -687,6 +730,9 @@
    * <code>getVersion</code> methods.
    */
   private void checkIdAndVersion(ErrorContext logger, Type domainType) {
+    if (objectType.equals(domainType)) {
+      return;
+    }
     logger = logger.setType(domainType);
     Method getIdString = new Method("getId", "()Ljava/lang/String;");
     Method getIdLong = new Method("getId", "()Ljava/lang/Long;");
@@ -729,6 +775,29 @@
   }
 
   /**
+   * Common checks to quickly determine if a type needs to be checked.
+   */
+  private boolean fastFail(String binaryName) {
+    if (!Name.isBinaryName(binaryName)) {
+      parentLogger.poison("%s is not a binary name", binaryName);
+      return true;
+    }
+
+    // Allow the poisoned flag to be reset without losing data
+    if (badTypes.contains(binaryName)) {
+      parentLogger.poison("Type type %s was previously marked as bad",
+          binaryName);
+      return true;
+    }
+
+    // Don't revalidate the same type
+    if (!validatedTypes.add(binaryName)) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
    * Finds a compatible method declaration in <code>domainType</code>'s
    * hierarchy that is assignment-compatible with the given Method.
    */
@@ -855,6 +924,8 @@
     }
     if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
       toReturn = clientType;
+    } else if (entityProxyIntf.equals(clientType)) {
+      toReturn = objectType;
     } else {
       logger = logger.setType(clientType);
       DomainMapper pv = new DomainMapper(logger);
@@ -869,6 +940,15 @@
       }
     }
     clientToDomainType.put(clientType, toReturn);
+    if (isAssignable(logger, entityProxyIntf, clientType)) {
+      Type previousProxyType = domainToClientType.put(toReturn, clientType);
+      if (previousProxyType != null) {
+        logger.poison(
+            "The domain type %s has more than one proxy type: %s and %s",
+            toReturn.getClassName(), previousProxyType.getClassName(),
+            clientType.getClassName());
+      }
+    }
     return toReturn;
   }
 
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index 74ff36c..f977c5c 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -51,6 +51,7 @@
 @SuppressWarnings("serial")
 public class RequestFactoryServlet extends HttpServlet {
 
+  private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
   private static final String JSON_CHARSET = "UTF-8";
   private static final String JSON_CONTENT_TYPE = "application/json";
   private static final Logger log = Logger.getLogger(RequestFactoryServlet.class.getCanonicalName());
@@ -64,7 +65,7 @@
 
   /**
    * Returns the thread-local {@link HttpServletRequest}.
-   *
+   * 
    * @return an {@link HttpServletRequest} instance
    */
   public static HttpServletRequest getThreadLocalRequest() {
@@ -73,14 +74,14 @@
 
   /**
    * Returns the thread-local {@link HttpServletResponse}.
-   *
+   * 
    * @return an {@link HttpServletResponse} instance
    */
   public static HttpServletResponse getThreadLocalResponse() {
     return perThreadResponse.get();
   }
 
-  private final ExceptionHandler exceptionHandler;
+  private final SimpleRequestProcessor processor;
 
   /**
    * Constructs a new {@link RequestFactoryServlet} with a
@@ -93,11 +94,12 @@
   /**
    * Use this constructor in subclasses to provide a custom
    * {@link ExceptionHandler}.
-   *
+   * 
    * @param exceptionHandler an {@link ExceptionHandler} instance
    */
   public RequestFactoryServlet(ExceptionHandler exceptionHandler) {
-    this.exceptionHandler = exceptionHandler;
+    processor = new SimpleRequestProcessor(new ReflectiveServiceLayer());
+    processor.setExceptionHandler(exceptionHandler);
   }
 
   /**
@@ -114,13 +116,15 @@
 
     perThreadRequest.set(request);
     perThreadResponse.set(response);
-    
+
     // No new code should be placed outside of this try block.
     try {
       ensureConfig();
       String jsonRequestString = RPCServletUtils.readContent(request,
           JSON_CONTENT_TYPE, JSON_CHARSET);
-      response.setStatus(HttpServletResponse.SC_OK);
+      if (DUMP_PAYLOAD) {
+        System.out.println(">>> " + jsonRequestString);
+      }
       PrintWriter writer = response.getWriter();
 
       try {
@@ -130,19 +134,18 @@
           response.setHeader("login", userInfo.getLoginUrl());
           response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
         } else {
+          String payload = processor.process(jsonRequestString);
+          if (DUMP_PAYLOAD) {
+            System.out.println("<<< " + payload);
+          }
           response.setHeader("userId", String.format("%s", userInfo.getId()));
           response.setStatus(HttpServletResponse.SC_OK);
-          RequestProcessor<String> requestProcessor = new JsonRequestProcessor();
-          requestProcessor.setOperationRegistry(new ReflectionBasedOperationRegistry(
-              new DefaultSecurityProvider()));
-          requestProcessor.setExceptionHandler(exceptionHandler);
           response.setContentType(RequestFactory.JSON_CONTENT_TYPE_UTF8);
-          writer.print(requestProcessor.decodeAndInvokeRequest(jsonRequestString));
+          writer.print(payload);
           writer.flush();
         }
-      } catch (RequestProcessingException e) {
-        writer.print((String) e.getResponse());
-        writer.flush();
+      } catch (RuntimeException e) {
+        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
         log.log(Level.SEVERE, "Unexpected error", e);
       }
     } finally {
@@ -160,9 +163,9 @@
     if (userInfoClass != null) {
       UserInformation.setUserInformationImplClass(userInfoClass);
     }
-    
-    String symbolMapsDirectory =
-      getServletConfig().getInitParameter("symbolMapsDirectory");
+
+    String symbolMapsDirectory = getServletConfig().getInitParameter(
+        "symbolMapsDirectory");
     if (symbolMapsDirectory != null) {
       Logging.setSymbolMapsDirectory(symbolMapsDirectory);
     }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
deleted file mode 100644
index 4c216bd..0000000
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessingException.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-/**
- * Exception thrown during by a {@link RequestProcessor} when 
- * an unexpected exception is caught. Includes an appropriate
- * response of T to send to the client.
- */
-class RequestProcessingException extends Exception {
-  private final Object response;
-
-  /**
-   * Constructs a new {@link RequestProcessingException} with a given message,
-   * Throwable cause, and response Object.
-   *
-   * @param message a message to display
-   * @param t the Throwable cause
-   * @param response a response Object, may be cast to T by a
-   *     {@link RequestProcessor}&lt;T&gt;
-   */
-  public RequestProcessingException(String message, Throwable t, Object response) {
-    super(message, t);
-    this.response = response;
-  }
-  
-  Object getResponse() {
-    return response;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
deleted file mode 100644
index 0e5718a..0000000
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-/**
- * Simple interface to abstract the underlying RequestFactory RPC mechanism. A
- * serialized object of type T is provided, such as a String in the case of JSON
- * requests, and a serialized return value of the same type is returned.
- * @param <T> the type of encoding used to serialize the request (e.g. String)
- */
-interface RequestProcessor<T> {
-  /**
-   * Decodes request, invokes methods, and re-encoded resulting return values.
-   *
-   * @param encodedRequest an encoded request of type T
-   * @return a decoded instance of type T
-   * @throws RequestProcessingException if an error occurs
-   */
-  T decodeAndInvokeRequest(T encodedRequest) throws RequestProcessingException;
-
-  /**
-   * Sets the ExceptionHandler to use to convert exceptions caused by
-   * method invocations into failure messages sent back to the client.
-   * 
-   * @param exceptionHandler an implementation, such as
-   *        {@code DefaultExceptionHandler}
-   */
-  void setExceptionHandler(ExceptionHandler exceptionHandler);
-
-  /**
-   * Sets the OperationRegistry to be used for looking up invocation metadata.
-   * 
-   * @param registry an implementation, such as
-   *          {@link ReflectionBasedOperationRegistry}
-   */
-  void setOperationRegistry(OperationRegistry registry);
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
deleted file mode 100644
index 10ae950..0000000
--- a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Represents one piece in a property reference sequence.
- */
-class RequestProperty implements Iterable<RequestProperty> {
-
-  /**
-   * Merge two property chains.
-   * 
-   * @param properties a list of {@link RequestProperty} instances
-   * @return a new {@link RequestProperty} instance
-   */
-  public static RequestProperty coalesce(RequestProperty... properties) {
-    assert properties.length > 0;
-    RequestProperty root = new RequestProperty("");
-    for (RequestProperty prop : properties) {
-      if ("".equals(prop.getPropertyName())) {
-        for (RequestProperty p : prop) {
-          root.mergeProperty(p);
-        }
-      } else {
-        root.mergeProperty(prop);
-      }
-    }
-    return root;
-  }
-
-  /**
-   * Parse selectors to obtain a {@link RequestProperty}.
-   *
-   * @param selectors a String of selectors separated by commas
-   * @return a new {@link RequestProperty} instance
-   */
-  public static RequestProperty parse(String selectors) {
-    String parts[] = selectors.split("\\s*,\\s*");
-    RequestProperty props[] = new RequestProperty[parts.length];
-    for (int i = 0; i < parts.length; i++) {
-      RequestProperty newProp = new RequestProperty("");
-      newProp.parseInternal(parts[i]);
-      props[i] = newProp;
-    }
-    return props.length == 1 ? props[0] : coalesce(props);
-  }
-
-  private String propertyName;
-  private Map<String, RequestProperty> subProperties;
-
-  private RequestProperty(String propertyName) {
-    this.propertyName = propertyName;
-  }
-
-  /**
-   * Add a property reference to this {@link RequestProperty}.
-   *
-   * @param propertyRef a {@link RequestProperty} instance
-   * @return this instance
-   */
-  public RequestProperty add(RequestProperty propertyRef) {
-    if (subProperties == null) {
-      subProperties = new HashMap<String, RequestProperty>();
-    }
-    subProperties.put(propertyRef.getPropertyName(), propertyRef);
-    return this;
-  }
-
-  /**
-   * Returns the value of a property defined as a sub-property of
-   * this instance.
-   *
-   * @param propName the property name as a String
-   * @return a {@link RequestProperty} instance or null
-   */
-  public RequestProperty getProperty(String propName) {
-    return subProperties == null ? null : subProperties.get(propName);
-  }
-
-  /**
-   * Returns the top-level property name associated with this instance.
-   *
-   * @return the property name as a String
-   */
-  public String getPropertyName() {
-    return propertyName;
-  }
-
-  /**
-   * Returns whether a given property is defined as a sub-property of this
-   * instance.
-   * 
-   * @param name the property name as a String
-   * @return {@code} true if the property exists
-   */
-  public boolean hasProperty(String name) {
-    return subProperties == null ? false : subProperties.containsKey(name);
-  }
-
-  /**
-   * Returns an iterator over the properties of this instance.
-   *
-   * @return an Iterator over {@link RequestProperty} instances
-   */
-  public Iterator<RequestProperty> iterator() {
-    return subProperties == null ? emptyIterator()
-        : subProperties.values().iterator();
-  }
-
-  /**
-   * Merge a given property into this instance.
-   * 
-   * @param property a {@link RequestProperty} instance
-   * @return the {@link RequestProperty} from this instance corresponding to the
-   *         name of the given property, or {@code null}.
-   */
-  public RequestProperty mergeProperty(RequestProperty property) {
-    RequestProperty foundProp = getProperty(property.getPropertyName());
-    if (foundProp == null && !"".equals(property.getPropertyName())) {
-      add(property);
-    } else {
-      for (RequestProperty p : property) {
-        if (foundProp == null) {
-          add(p);
-        } else {
-          foundProp.mergeProperty(p);
-        }
-      }
-    }
-    return foundProp;
-  }
-
-  @SuppressWarnings({"cast", "unchecked"})
-  private Iterator<RequestProperty> emptyIterator() {
-    return (Iterator<RequestProperty>) Collections.EMPTY_MAP.values().iterator();
-  }
-
-  private RequestProperty getOrCreate(String part) {
-    RequestProperty prop = getProperty(part);
-    if (prop == null) {
-      prop = new RequestProperty(part);
-      add(prop);
-    }
-    return prop;
-  }
-
-  private RequestProperty parseInternal(String sequence) {
-    int dotIndex = sequence.indexOf('.');
-    String part = dotIndex > -1 ? sequence.substring(0, dotIndex) : sequence;
-    RequestProperty prop = getOrCreate(part);
-    add(prop);
-
-    if (dotIndex > -1) {
-      if (dotIndex < sequence.length() - 1) {
-        String next = sequence.substring(dotIndex + 1);
-        if ("".equals(next)) {
-          throw new IllegalArgumentException("Empty property name '..' not allowed in " + sequence);
-        }
-        if (next.length() > 0) {
-          return prop.parseInternal(next);
-        }
-      }
-      throw new IllegalArgumentException("Trailing '.' in with() call " + sequence);
-    }
-    return prop;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
deleted file mode 100644
index 8ff9ae8..0000000
--- a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-/**
- * Enforces security policy for operations and classes, as well as permitting
- * request obfuscation.
- */
-interface RequestSecurityProvider {
-
-  /**
-   * Throws exception if argument is not accessible via remote requests.
-   * 
-   * @param clazz a Class instance
-   * @throws SecurityException if the argument is not accessible via remote
-   *           requests
-   */
-  void checkClass(Class<?> clazz) throws SecurityException;
-
-  /**
-   * Encode a Class type into a String token.
-   *
-   * @param type a Class instance
-   * @return an encoded String token
-   */
-  String encodeClassType(Class<?> type);
-
-  /**
-   * Optionally decodes a previously encoded operation. Throws exception if
-   * argument is not a legal operation.
-   *
-   * @param operationName the operation name as a String
-   * @return a decoded operation name
-   * @throws SecurityException if the argument is not a legal operation
-   */
-  String mapOperation(String operationName) throws SecurityException;
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
new file mode 100644
index 0000000..e8dede4
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -0,0 +1,735 @@
+/*
+ * 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.AutoBeanFactoryMagic;
+import com.google.gwt.autobean.server.Configuration;
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanCodex;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+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.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.ServerFailure;
+import com.google.gwt.requestfactory.shared.WriteOperation;
+import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.gwt.requestfactory.shared.impl.IdFactory;
+import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
+import com.google.gwt.requestfactory.shared.messages.MessageFactory;
+import com.google.gwt.requestfactory.shared.messages.OperationMessage;
+import com.google.gwt.requestfactory.shared.messages.RequestMessage;
+import com.google.gwt.requestfactory.shared.messages.ResponseMessage;
+import com.google.gwt.requestfactory.shared.messages.ServerFailureMessage;
+import com.google.gwt.requestfactory.shared.messages.ViolationMessage;
+import com.google.gwt.user.server.Base64Utils;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Processes request payloads from a RequestFactory client. This implementation
+ * is stateless. A single instance may be reused and is thread-safe.
+ */
+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<?> getDomainClass(Class<?> clazz);
+
+    String getFlatId(EntitySource source, Object domainObject);
+
+    Object getProperty(Object domainObject, String property);
+
+    String getTypeToken(Class<?> domainClass);
+
+    int getVersion(Object domainObject);
+
+    Object invoke(Method domainMethod, Object[] args);
+
+    Object loadDomainObject(EntitySource source, Class<?> clazz, String flatId);
+
+    Class<? extends EntityProxy> 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.
+   */
+  @SuppressWarnings("serial")
+  private static class IdToEntityMap extends
+      HashMap<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> {
+  }
+
+  private class RequestState implements EntityCodex.EntitySource {
+    private final IdToEntityMap beans = new IdToEntityMap();
+    private final Map<Object, Integer> domainObjectsToClientId;
+    private final IdFactory idFactory;
+
+    public RequestState() {
+      idFactory = new IdFactory() {
+        @Override
+        @SuppressWarnings("unchecked")
+        protected <P extends EntityProxy> Class<P> getTypeFromToken(
+            String typeToken) {
+          return (Class<P>) service.resolveClass(typeToken);
+        }
+
+        @Override
+        protected String getTypeToken(Class<?> clazz) {
+          return service.getTypeToken(clazz);
+        }
+      };
+      domainObjectsToClientId = new IdentityHashMap<Object, Integer>();
+    }
+
+    public RequestState(RequestState parent) {
+      idFactory = parent.idFactory;
+      domainObjectsToClientId = parent.domainObjectsToClientId;
+    }
+
+    public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
+        SimpleEntityProxyId<Q> id) {
+      @SuppressWarnings("unchecked")
+      AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+      if (toReturn == null) {
+        toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
+            CONFIGURATION);
+        toReturn.setTag(STABLE_ID, id);
+
+        // Resolve the domain object
+        Class<?> domainClass = service.getDomainClass(id.getProxyClass());
+
+        Object domain;
+        if (id.isEphemeral()) {
+          domain = service.createDomainObject(domainClass);
+          // Don't call getFlatId here, resolve the ids after invocations
+          domainObjectsToClientId.put(domain, id.getClientId());
+        } else {
+          domain = service.loadDomainObject(this, domainClass,
+              fromBase64(id.getServerId()));
+        }
+        toReturn.setTag(DOMAIN_OBJECT, domain);
+
+        beans.put(id, toReturn);
+      }
+      return toReturn;
+    }
+
+    /**
+     * EntityCodex support.
+     */
+    public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
+        String serializedProxyId) {
+      String typeToken = IdUtil.getTypeToken(serializedProxyId);
+      SimpleEntityProxyId<Q> id;
+      if (IdUtil.isEphemeral(serializedProxyId)) {
+        id = idFactory.getId(typeToken, null,
+            IdUtil.getClientId(serializedProxyId));
+      } else {
+        id = idFactory.getId(typeToken, IdUtil.getServerId(serializedProxyId));
+      }
+      return getBeanForPayload(id);
+    }
+
+    /**
+     * If the domain object was sent with an ephemeral id, return the client id.
+     */
+    public Integer getClientId(Object domainEntity) {
+      return domainObjectsToClientId.get(domainEntity);
+    }
+
+    /**
+     * EntityCodex support.
+     */
+    public String getSerializedProxyId(EntityProxyId<?> stableId) {
+      SimpleEntityProxyId<?> impl = (SimpleEntityProxyId<?>) stableId;
+      if (impl.isEphemeral()) {
+        return IdUtil.ephemeralId(impl.getClientId(),
+            service.getTypeToken(stableId.getProxyClass()));
+      } else {
+        return IdUtil.persistedId(impl.getServerId(),
+            service.getTypeToken(stableId.getProxyClass()));
+      }
+    }
+
+    /**
+     * EntityCodex support.
+     */
+    public boolean isEntityType(Class<?> clazz) {
+      return EntityProxy.class.isAssignableFrom(clazz);
+    }
+  }
+
+  private static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
+      EntityProxyCategory.class).setNoWrap(EntityProxyId.class).build();
+  private static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
+
+  // Tag constants
+  private static final String DOMAIN_OBJECT = "domainObject";
+  private static final String IN_RESPONSE = "inResponse";
+  private static final String STABLE_ID = "stableId";
+
+  private static String fromBase64(String encoded) {
+    try {
+      return new String(Base64Utils.fromBase64(encoded), "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new UnexpectedException(e);
+    }
+  }
+
+  private static String toBase64(String data) {
+    try {
+      return Base64Utils.toBase64(data.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new UnexpectedException(e);
+    }
+  }
+
+  private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+  private final ServiceLayer service;
+
+  public SimpleRequestProcessor(ServiceLayer serviceLayer) {
+    this.service = serviceLayer;
+  }
+
+  public String process(String payload) {
+    RequestMessage req = AutoBeanCodex.decode(FACTORY, RequestMessage.class,
+        payload).as();
+    AutoBean<ResponseMessage> responseBean = FACTORY.response();
+    try {
+      process(req, responseBean.as());
+    } catch (ReportableException e) {
+      e.printStackTrace();
+      // Create a new response envelope, since the state is unknown
+      responseBean = FACTORY.response();
+      responseBean.as().setGeneralFailure(createFailureMessage(e).as());
+    }
+    // Return a JSON-formatted payload
+    return AutoBeanCodex.encode(responseBean).getPayload();
+  }
+
+  public void setExceptionHandler(ExceptionHandler exceptionHandler) {
+    this.exceptionHandler = exceptionHandler;
+  }
+
+  /**
+   * Main processing method.
+   */
+  void process(RequestMessage req, ResponseMessage resp) {
+    final RequestState source = new RequestState();
+    // Apply operations
+    processOperationMessages(source, req);
+
+    // Validate entities
+    List<ViolationMessage> errorMessages = validateEntities(source);
+
+    if (!errorMessages.isEmpty()) {
+      resp.setViolations(errorMessages);
+      return;
+    }
+
+    RequestState returnState = new RequestState(source);
+
+    // Invoke methods
+    List<Splittable> invocationResults = new ArrayList<Splittable>();
+    List<Boolean> invocationSuccess = new ArrayList<Boolean>();
+    List<InvocationMessage> invlist = req.getInvocations();
+    processInvocationMessages(source, invlist, invocationResults,
+        invocationSuccess, returnState);
+
+    // Store return objects
+    List<OperationMessage> operations = new ArrayList<OperationMessage>();
+    IdToEntityMap toProcess = new IdToEntityMap();
+    toProcess.putAll(source.beans);
+    toProcess.putAll(returnState.beans);
+    createReturnOperations(operations, returnState, toProcess);
+
+    resp.setInvocationResults(invocationResults);
+    resp.setStatusCodes(invocationSuccess);
+    if (!operations.isEmpty()) {
+      resp.setOperations(operations);
+    }
+  }
+
+  private AutoBean<ServerFailureMessage> createFailureMessage(
+      ReportableException e) {
+    ServerFailure failure = exceptionHandler.createServerFailure(e.getCause());
+    AutoBean<ServerFailureMessage> bean = FACTORY.failure();
+    ServerFailureMessage msg = bean.as();
+    msg.setExceptionType(failure.getExceptionType());
+    msg.setMessage(failure.getMessage());
+    msg.setStackTrace(failure.getStackTraceString());
+    return bean;
+  }
+
+  private void createReturnOperations(List<OperationMessage> operations,
+      RequestState returnState, IdToEntityMap toProcess) {
+    for (Map.Entry<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> entry : toProcess.entrySet()) {
+      SimpleEntityProxyId<?> id = entry.getKey();
+
+      AutoBean<? extends EntityProxy> bean = entry.getValue();
+      Object domainObject = bean.getTag(DOMAIN_OBJECT);
+      WriteOperation writeOperation;
+
+      if (id.isEphemeral()) {
+        resolveClientEntityProxy(returnState, domainObject,
+            Collections.<String> emptySet(), "");
+        if (id.isEphemeral()) {
+          throw new ReportableException("Could not persist entity "
+              + service.getFlatId(returnState, domainObject.toString()));
+        }
+      }
+
+      if (service.loadDomainObject(returnState,
+          service.getDomainClass(id.getProxyClass()),
+          fromBase64(id.getServerId())) == null) {
+        writeOperation = WriteOperation.DELETE;
+      } else if (id.wasEphemeral()) {
+        writeOperation = WriteOperation.PERSIST;
+      } else {
+        writeOperation = WriteOperation.UPDATE;
+      }
+
+      boolean inResponse = bean.getTag(IN_RESPONSE) != null;
+      int version = domainObject == null ? 0 : service.getVersion(domainObject);
+
+      /*
+       * Don't send any data back to the client for an update on an object that
+       * isn't part of the response payload when the client's version matches
+       * the domain version.
+       */
+      if (writeOperation.equals(WriteOperation.UPDATE) && !inResponse) {
+        if (Integer.valueOf(version).equals(
+            bean.getTag(Constants.ENCODED_VERSION_PROPERTY))) {
+          continue;
+        }
+      }
+
+      OperationMessage op = FACTORY.operation().as();
+      if (writeOperation == WriteOperation.PERSIST) {
+        op.setClientId(id.getClientId());
+      }
+      op.setOperation(writeOperation);
+
+      // Only send properties for entities that are part of the return graph
+      if (inResponse) {
+        Map<String, Splittable> propertyMap = new LinkedHashMap<String, Splittable>();
+        // Add all non-null properties to the serialized form
+        Map<String, Object> diff = AutoBeanUtils.getAllProperties(bean);
+        for (Map.Entry<String, Object> d : diff.entrySet()) {
+          Object value = d.getValue();
+          if (value != null) {
+            propertyMap.put(d.getKey(), EntityCodex.encode(returnState, value));
+          }
+        }
+        op.setPropertyMap(propertyMap);
+      }
+
+      op.setServerId(id.getServerId());
+      op.setTypeToken(service.getTypeToken(id.getProxyClass()));
+      op.setVersion(version);
+
+      operations.add(op);
+    }
+  }
+
+  /**
+   * Handles instance invocations as the instance at the 0th parameter.
+   */
+  private List<Object> decodeInvocationArguments(RequestState source,
+      InvocationMessage invocation, Class<?>[] contextArgs, Type[] genericArgs) {
+    if (invocation.getParameters() == null) {
+      return Collections.emptyList();
+    }
+
+    assert invocation.getParameters().size() == contextArgs.length;
+    List<Object> args = new ArrayList<Object>(contextArgs.length);
+    for (int i = 0, j = contextArgs.length; i < j; i++) {
+      Class<?> type = contextArgs[i];
+      Class<?> elementType = null;
+      Splittable split;
+      if (Collection.class.isAssignableFrom(type)) {
+        elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+            Collection.class, genericArgs[i]));
+        split = invocation.getParameters().get(i);
+      } else {
+        split = invocation.getParameters().get(i);
+      }
+      Object arg = EntityCodex.decode(source, type, elementType, split);
+      arg = resolveDomainValue(arg, !EntityProxyId.class.equals(contextArgs[i]));
+      args.add(arg);
+    }
+
+    return args;
+  }
+
+  /**
+   * Decode the arguments to pass into the domain method. If the domain method
+   * is not static, the instance object will be in the 0th position.
+   */
+  private List<Object> decodeInvocationArguments(RequestState source,
+      InvocationMessage invocation, Method contextMethod, Method domainMethod) {
+    boolean isStatic = Modifier.isStatic(domainMethod.getModifiers());
+    int baseLength = contextMethod.getParameterTypes().length;
+    int length = baseLength + (isStatic ? 0 : 1);
+    int offset = isStatic ? 0 : 1;
+    Class<?>[] contextArgs = new Class<?>[length];
+    Type[] genericArgs = new Type[length];
+
+    if (!isStatic) {
+      genericArgs[0] = TypeUtils.getSingleParameterization(
+          InstanceRequest.class, contextMethod.getGenericReturnType());
+      contextArgs[0] = TypeUtils.ensureBaseType(genericArgs[0]);
+    }
+    System.arraycopy(contextMethod.getParameterTypes(), 0, contextArgs, offset,
+        baseLength);
+    System.arraycopy(contextMethod.getGenericParameterTypes(), 0, genericArgs,
+        offset, baseLength);
+
+    List<Object> args = decodeInvocationArguments(source, invocation,
+        contextArgs, genericArgs);
+    return args;
+  }
+
+  /**
+   * Expand the property references in an InvocationMessage into a
+   * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
+   * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
+   */
+  private Set<String> getPropertyRefs(InvocationMessage invocation) {
+    Set<String> refs = invocation.getPropertyRefs();
+    if (refs == null) {
+      return Collections.emptySet();
+    }
+
+    Set<String> toReturn = new TreeSet<String>();
+    for (String raw : refs) {
+      for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
+        toReturn.add(raw.substring(0, idx));
+      }
+    }
+    return toReturn;
+  }
+
+  private void processInvocationMessages(RequestState state,
+      List<InvocationMessage> invlist, List<Splittable> results,
+      List<Boolean> success, RequestState returnState) {
+    for (InvocationMessage invocation : invlist) {
+      try {
+        // Find the Method
+        String[] operation = invocation.getOperation().split("::");
+        Method contextMethod = service.resolveRequestContextMethod(
+            operation[0], operation[1]);
+        Method domainMethod = service.resolveDomainMethod(contextMethod);
+
+        // Invoke it
+        List<Object> args = decodeInvocationArguments(state, invocation,
+            contextMethod, domainMethod);
+        Object returnValue = service.invoke(domainMethod, args.toArray());
+
+        // Convert domain object to client object
+        returnValue = resolveClientValue(returnState, returnValue,
+            getPropertyRefs(invocation), "");
+
+        // Convert the client object to a string
+        results.add(EntityCodex.encode(returnState, returnValue));
+        success.add(true);
+      } catch (ReportableException e) {
+        results.add(AutoBeanCodex.encode(createFailureMessage(e)));
+        success.add(false);
+      }
+    }
+  }
+
+  private void processOperationMessages(final RequestState state,
+      RequestMessage req) {
+    List<OperationMessage> operations = req.getOperations();
+    if (operations == null) {
+      return;
+    }
+
+    for (final OperationMessage operation : operations) {
+      // Unflatten properties
+      String payloadId = operation.getOperation().equals(WriteOperation.PERSIST)
+          ? IdUtil.ephemeralId(operation.getClientId(),
+              operation.getTypeToken()) : IdUtil.persistedId(
+              operation.getServerId(), operation.getTypeToken());
+      AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(payloadId);
+      // Use the version later to know which objects need to be sent back
+      bean.setTag(Constants.ENCODED_ID_PROPERTY, operation.getVersion());
+
+      // Load the domain object with properties, if it exists
+      final Object domain = bean.getTag(DOMAIN_OBJECT);
+      if (domain != null) {
+        // Apply any property updates
+        final Map<String, Splittable> flatValueMap = operation.getPropertyMap();
+        if (flatValueMap != null) {
+          bean.accept(new AutoBeanVisitor() {
+            @Override
+            public boolean visitReferenceProperty(String propertyName,
+                AutoBean<?> value, PropertyContext ctx) {
+              // containsKey to distinguish null from unknown
+              if (flatValueMap.containsKey(propertyName)) {
+                Class<?> elementType = ctx instanceof CollectionPropertyContext
+                    ? ((CollectionPropertyContext) ctx).getElementType() : null;
+                Object newValue = EntityCodex.decode(state, ctx.getType(),
+                    elementType, flatValueMap.get(propertyName));
+                Object resolved = resolveDomainValue(newValue, false);
+                service.setProperty(domain, propertyName,
+                    service.getDomainClass(ctx.getType()), resolved);
+              }
+              return false;
+            }
+
+            @Override
+            public boolean visitValueProperty(String propertyName,
+                Object value, PropertyContext ctx) {
+              if (flatValueMap.containsKey(propertyName)) {
+                Splittable split = flatValueMap.get(propertyName);
+                Object newValue = ValueCodex.decode(ctx.getType(), split);
+                Object resolved = resolveDomainValue(newValue, false);
+                service.setProperty(domain, propertyName, ctx.getType(),
+                    resolved);
+              }
+              return false;
+            }
+          });
+        }
+      }
+    }
+  }
+
+  /**
+   * Converts a domain entity into an EntityProxy that will be sent to the
+   * client.
+   */
+  private EntityProxy resolveClientEntityProxy(final RequestState state,
+      final Object domainEntity, final Set<String> propertyRefs,
+      final String prefix) {
+    if (domainEntity == null) {
+      return null;
+    }
+
+    // Compute data needed to return id to the client
+    String flatId = toBase64(service.getFlatId(state, domainEntity));
+    Class<? extends EntityProxy> proxyType = service.getClientType(
+        domainEntity.getClass()).asSubclass(EntityProxy.class);
+
+    // Retrieve the id, possibly setting its persisted value
+    SimpleEntityProxyId<? extends EntityProxy> id = state.idFactory.getId(
+        proxyType, flatId, state.getClientId(domainEntity));
+
+    AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(id);
+    bean.setTag(IN_RESPONSE, true);
+    bean.accept(new AutoBeanVisitor() {
+      @Override
+      public boolean visitReferenceProperty(String propertyName,
+          AutoBean<?> value, PropertyContext ctx) {
+        // Does the user care about the property?
+        String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
+            + propertyName;
+
+        /*
+         * Send if the user cares about the property or if it's a collection of
+         * values.
+         */
+        Class<?> elementType = ctx instanceof CollectionPropertyContext
+            ? ((CollectionPropertyContext) ctx).getElementType() : null;
+        boolean shouldSend = propertyRefs.contains(newPrefix)
+            || elementType != null && !state.isEntityType(elementType);
+
+        if (!shouldSend) {
+          return false;
+        }
+
+        // Call the getter
+        Object domainValue = service.getProperty(domainEntity, propertyName);
+        if (domainValue == null) {
+          return false;
+        }
+
+        // Turn the domain object into something usable on the client side
+        Object resolved = resolveClientValue(state, domainValue, propertyRefs,
+            newPrefix);
+
+        ctx.set(ctx.getType().cast(resolved));
+        return false;
+      }
+
+      @Override
+      public boolean visitValueProperty(String propertyName, Object value,
+          PropertyContext ctx) {
+        // Limit unrequested value properties?
+        value = service.getProperty(domainEntity, propertyName);
+        ctx.set(ctx.getType().cast(value));
+        return false;
+      }
+    });
+    bean.setTag(Constants.ENCODED_VERSION_PROPERTY,
+        service.getVersion(domainEntity));
+    return proxyType.cast(bean.as());
+  }
+
+  /**
+   * Given a method a domain object, return a value that can be encoded by the
+   * client.
+   */
+  private Object resolveClientValue(RequestState source, Object domainValue,
+      Set<String> propertyRefs, String prefix) {
+    if (domainValue == null) {
+      return null;
+    }
+
+    Class<?> returnClass = service.getClientType(domainValue.getClass());
+
+    // Pass simple values through
+    if (ValueCodex.canDecode(returnClass)) {
+      return domainValue;
+    }
+
+    // Convert entities to EntityProxies
+    if (EntityProxy.class.isAssignableFrom(returnClass)) {
+      return resolveClientEntityProxy(source, domainValue, propertyRefs, prefix);
+    }
+
+    // Convert collections
+    if (Collection.class.isAssignableFrom(returnClass)) {
+      Collection<Object> accumulator;
+      if (List.class.isAssignableFrom(returnClass)) {
+        accumulator = new ArrayList<Object>();
+      } else if (Set.class.isAssignableFrom(returnClass)) {
+        accumulator = new HashSet<Object>();
+      } else {
+        throw new ReportableException("Unsupported collection type"
+            + returnClass.getName());
+      }
+
+      for (Object o : (Collection<?>) domainValue) {
+        accumulator.add(resolveClientValue(source, o, propertyRefs, prefix));
+      }
+      return accumulator;
+    }
+
+    throw new ReportableException("Unsupported domain type "
+        + returnClass.getCanonicalName());
+  }
+
+  /**
+   * Convert a client-side value into a domain value.
+   * 
+   * @param the client object to resolve
+   * @param detectDeadEntities if <code>true</code> this method will throw a
+   *          ReportableException containing a {@link DeadEntityException} if an
+   *          EntityProxy cannot be resolved
+   */
+  private Object resolveDomainValue(Object maybeEntityProxy,
+      boolean detectDeadEntities) {
+    if (maybeEntityProxy instanceof EntityProxy) {
+      AutoBean<EntityProxy> bean = AutoBeanUtils.getAutoBean((EntityProxy) maybeEntityProxy);
+      Object domain = bean.getTag(DOMAIN_OBJECT);
+      if (domain == null && detectDeadEntities) {
+        throw new ReportableException(new DeadEntityException(
+            "The requested entity is not available on the server"));
+      }
+      return domain;
+    } else if (maybeEntityProxy instanceof Collection<?>) {
+      Collection<Object> accumulator;
+      if (maybeEntityProxy instanceof List) {
+        accumulator = new ArrayList<Object>();
+      } else if (maybeEntityProxy instanceof Set) {
+        accumulator = new HashSet<Object>();
+      } else {
+        throw new ReportableException("Unsupported collection type "
+            + maybeEntityProxy.getClass().getName());
+      }
+      for (Object o : (Collection<?>) maybeEntityProxy) {
+        accumulator.add(resolveDomainValue(o, detectDeadEntities));
+      }
+      return accumulator;
+    }
+    return maybeEntityProxy;
+  }
+
+  /**
+   * Validate all of the entities referenced in a RequestState.
+   */
+  private List<ViolationMessage> validateEntities(RequestState source) {
+    List<ViolationMessage> errorMessages = new ArrayList<ViolationMessage>();
+    for (Map.Entry<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> entry : source.beans.entrySet()) {
+      Object domainObject = entry.getValue().getTag(DOMAIN_OBJECT);
+
+      // The object could have been deleted
+      if (domainObject != null) {
+        Set<ConstraintViolation<Object>> errors = service.validate(domainObject);
+        if (errors != null && !errors.isEmpty()) {
+          SimpleEntityProxyId<?> id = entry.getKey();
+          for (ConstraintViolation<Object> error : errors) {
+            ViolationMessage message = FACTORY.violation().as();
+            message.setClientId(id.getClientId());
+            message.setMessage(error.getMessage());
+            message.setPath(error.getPropertyPath().toString());
+            message.setServerId(id.getServerId());
+            message.setTypeToken(service.getTypeToken(id.getProxyClass()));
+            errorMessages.add(message);
+          }
+        }
+      }
+    }
+    return errorMessages;
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Version.java b/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
similarity index 62%
copy from user/src/com/google/gwt/requestfactory/shared/Version.java
copy to user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
index 5e66314..f6cac79 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Version.java
+++ b/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
@@ -1,23 +1,30 @@
 /*
  * 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;
+package com.google.gwt.requestfactory.server;
 
 /**
- * Marks the version property of an entity.
+ * Encapsulates exceptions that indicate something went wrong in RequestFactory
+ * code.
  */
-public @interface Version {
-  // TODO prove the servlet will use this info
+class UnexpectedException extends RuntimeException {
+  public UnexpectedException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
+
+  public UnexpectedException(Throwable cause) {
+    super(cause);
+  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformation.java b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
index 27d4c4d..c16554b 100644
--- a/user/src/com/google/gwt/requestfactory/server/UserInformation.java
+++ b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
@@ -17,8 +17,8 @@
 package com.google.gwt.requestfactory.server;
 
 /**
- * A base class for providing authentication related information about the
- * user. Services that want real authentication should subclass this class.
+ * A base class for providing authentication related information about the user.
+ * Services that want real authentication should subclass this class.
  */
 public abstract class UserInformation {
 
@@ -38,9 +38,8 @@
     UserInformation userInfo = null;
     if (!"".equals(userInformationImplClass)) {
       try {
-        userInfo = (UserInformation) Class.forName(
-            userInformationImplClass).getConstructor(
-                String.class).newInstance(redirectUrl);
+        userInfo = (UserInformation) Class.forName(userInformationImplClass).getConstructor(
+            String.class).newInstance(redirectUrl);
       } catch (Exception e) {
         e.printStackTrace();
       }
@@ -52,9 +51,9 @@
   }
 
   /**
-   * Sets the implementation class to be used to gather user information
-   * in {@link #getCurrentUserInformation(String)}.
-   *
+   * Sets the implementation class to be used to gather user information in
+   * {@link #getCurrentUserInformation(String)}.
+   * 
    * @param clazz a class name
    */
   public static void setUserInformationImplClass(String clazz) {
@@ -66,10 +65,10 @@
    */
   protected String redirectUrl = "";
   private Integer version = 0;
-  
+
   /**
    * Constructs a new {@link UserInformation} instance.
-   *
+   * 
    * @param redirectUrl the redirect URL as a String
    */
   public UserInformation(String redirectUrl) {
@@ -80,68 +79,68 @@
 
   /**
    * Returns the user's email address.
-   *
+   * 
    * @return the user's email address as a String
    */
   public abstract String getEmail();
-  
+
   /**
    * Returns the user's id.
-   *
+   * 
    * @return the user's id as a Long
    * @see #setId(Long)
    */
   public abstract Long getId();
-  
+
   /**
    * Returns the user's login URL.
-   *
+   * 
    * @return the user's login URL as a String
    */
   public abstract String getLoginUrl();
-  
+
   /**
    * Returns the user's logout URL.
-   *
+   * 
    * @return the user's logout URL as a String
    */
   public abstract String getLogoutUrl();
-  
+
   /**
    * Returns the user's name.
-   *
+   * 
    * @return the user's name as a String
    */
   public abstract String getName();
 
   /**
    * Returns the version of this instance.
-   *
+   * 
    * @return an Integer version number
    * @see #setVersion(Integer)
    */
   public Integer getVersion() {
     return this.version;
   }
-  
+
   /**
    * Returns whether the user is logged in.
-   *
+   * 
    * @return {@code true} if the user is logged in
    */
   public abstract boolean isUserLoggedIn();
 
   /**
    * Sets the id for this user.
-   *
+   * 
    * @param id a String id
    * @see #getId()
    */
   public abstract void setId(Long id);
-  
+
   /**
    * Sets the version of this instance.
-   *
+   * 
    * @param version an Integer version number
    * @see #getVersion()
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java b/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java
index 46ef176..c9a66eb 100644
--- a/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java
+++ b/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -16,8 +16,8 @@
 package com.google.gwt.requestfactory.server;
 
 /**
- * This implementation treats the user as constantly signed in, and
- * without any information.
+ * This implementation treats the user as constantly signed in, and without any
+ * information.
  */
 public class UserInformationSimpleImpl extends UserInformation {
 
@@ -25,7 +25,7 @@
 
   /**
    * Constructs an UserInformationSimpleImpl object.
-   *
+   * 
    * @param redirectUrl the redirect URL as a String
    */
   public UserInformationSimpleImpl(String redirectUrl) {
diff --git a/user/src/com/google/gwt/requestfactory/server/package-info.java b/user/src/com/google/gwt/requestfactory/server/package-info.java
index f1e2481..6d9ddab 100644
--- a/user/src/com/google/gwt/requestfactory/server/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/server/package-info.java
@@ -15,11 +15,15 @@
  */
 
 /**
- * Server side classes for mediating between the client side and the persistent datastore.
- *
- * This package contains classes that can receive client side read and write requests in the JSON format, perform the necessary operations on the persistent datastore, and return the results in JSON format.
- *
+ * Server side classes for mediating between the client side and the persistent
+ * datastore.
+ * 
+ * This package contains classes that can receive client side read and write
+ * requests in the JSON format, perform the necessary operations on the
+ * persistent datastore, and return the results in JSON format.
+ * 
  * @since GWT 2.1
  */
 @com.google.gwt.util.PreventSpuriousRebuilds
 package com.google.gwt.requestfactory.server;
+
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
new file mode 100644
index 0000000..2f02c01
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
@@ -0,0 +1,110 @@
+/*
+ * 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.testing;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequest;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequestContext;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.RequestData;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+/**
+ * An in-process implementation of RequestContext
+ */
+class InProcessRequestContext extends AbstractRequestContext {
+  static final Object[] NO_ARGS = new Object[0];
+
+  class RequestContextHandler implements InvocationHandler {
+    public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+      // Maybe delegate to superclass
+      Class<?> owner = method.getDeclaringClass();
+      if (Object.class.equals(owner) || RequestContext.class.equals(owner)
+          || AbstractRequestContext.class.equals(owner)) {
+        try {
+          return method.invoke(InProcessRequestContext.this, args);
+        } catch (InvocationTargetException e) {
+          throw e.getCause();
+        }
+      }
+
+      /*
+       * Instance methods treat the 0-th argument as the instance on which to
+       * invoke the method.
+       */
+      final Object[] actualArgs;
+      Type returnGenericType;
+      boolean isInstance = InstanceRequest.class.isAssignableFrom(method.getReturnType());
+      if (isInstance) {
+        returnGenericType = TypeUtils.getParameterization(
+            InstanceRequest.class, method.getGenericReturnType(),
+            method.getReturnType())[1];
+        if (args == null) {
+          actualArgs = new Object[1];
+        } else {
+          // Save a slot for the this argument
+          actualArgs = new Object[args.length + 1];
+          System.arraycopy(args, 0, actualArgs, 1, args.length);
+        }
+      } else {
+        returnGenericType = TypeUtils.getSingleParameterization(Request.class,
+            method.getGenericReturnType(), method.getReturnType());
+        if (args == null) {
+          actualArgs = NO_ARGS;
+        } else {
+          actualArgs = args;
+        }
+      }
+
+      // Calculate request metadata
+      final String operation = method.getDeclaringClass().getName() + "::"
+          + method.getName();
+      final Class<?> returnType = TypeUtils.ensureBaseType(returnGenericType);
+      final Class<?> elementType = Collection.class.isAssignableFrom(returnType)
+          ? TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+              Collection.class, returnGenericType)) : null;
+
+      // Create the request, just filling in the RequestData details
+      AbstractRequest<Object> req = new AbstractRequest<Object>(
+          InProcessRequestContext.this) {
+        @Override
+        protected RequestData makeRequestData() {
+          return new RequestData(operation, actualArgs, propertyRefs,
+              returnType, elementType);
+        }
+      };
+
+      if (!isInstance) {
+        // Instance invocations are enqueued when using() is called
+        addInvocation(req);
+      }
+      return req;
+    }
+  };
+
+  protected InProcessRequestContext(AbstractRequestFactory factory) {
+    super(factory);
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
new file mode 100644
index 0000000..e6c412b
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
@@ -0,0 +1,95 @@
+/*
+ * 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.testing;
+
+import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
+import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.requestfactory.server.testing.InProcessRequestContext.RequestContextHandler;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * An JRE-compatible implementation of RequestFactory.
+ */
+class InProcessRequestFactory extends AbstractRequestFactory {
+  @Category(EntityProxyCategory.class)
+  @NoWrap(EntityProxyId.class)
+  interface Factory extends AutoBeanFactory {
+  }
+
+  class RequestFactoryHandler implements InvocationHandler {
+    public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+      if (Object.class.equals(method.getDeclaringClass())
+          || RequestFactory.class.equals(method.getDeclaringClass())) {
+        try {
+          return method.invoke(InProcessRequestFactory.this, args);
+        } catch (InvocationTargetException e) {
+          throw e.getCause();
+        }
+      }
+
+      Class<? extends RequestContext> context = method.getReturnType().asSubclass(
+          RequestContext.class);
+      RequestContextHandler handler = new InProcessRequestContext(
+          InProcessRequestFactory.this).new RequestContextHandler();
+      return context.cast(Proxy.newProxyInstance(
+          Thread.currentThread().getContextClassLoader(),
+          new Class<?>[]{context}, handler));
+    }
+  }
+
+  @Override
+  public void initialize(EventBus eventBus) {
+    throw new UnsupportedOperationException(
+        "An explicit RequestTransport must be provided");
+  }
+
+  @Override
+  protected AutoBeanFactory getAutoBeanFactory() {
+    return AutoBeanFactoryMagic.create(Factory.class);
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  protected <P extends EntityProxy> Class<P> getTypeFromToken(String typeToken) {
+    try {
+      Class<? extends EntityProxy> found = Class.forName(typeToken, false,
+          Thread.currentThread().getContextClassLoader()).asSubclass(
+          EntityProxy.class);
+      return (Class<P>) found;
+    } catch (ClassNotFoundException e) {
+      return null;
+    }
+  }
+
+  @Override
+  protected String getTypeToken(Class<?> clazz) {
+    return EntityProxy.class.isAssignableFrom(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
new file mode 100644
index 0000000..fa32efb
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
@@ -0,0 +1,49 @@
+/*
+ * 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.testing;
+
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor;
+import com.google.gwt.requestfactory.shared.RequestTransport;
+
+/**
+ * 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.
+ */
+public class InProcessRequestTransport implements RequestTransport {
+  private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
+  private final SimpleRequestProcessor processor;
+
+  public InProcessRequestTransport(SimpleRequestProcessor processor) {
+    this.processor = processor;
+  }
+
+  public void send(String payload, TransportReceiver receiver) {
+    try {
+      if (DUMP_PAYLOAD) {
+        System.out.println(">>> " + payload);
+      }
+      String result = processor.process(payload);
+      if (DUMP_PAYLOAD) {
+        System.out.println("<<< " + result);
+      }
+      receiver.onTransportSuccess(result);
+    } catch (RuntimeException e) {
+      e.printStackTrace();
+      receiver.onTransportFailure(e.getMessage());
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
new file mode 100644
index 0000000..0b84c27
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
@@ -0,0 +1,50 @@
+/*
+ * 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.testing;
+
+import com.google.gwt.requestfactory.server.testing.InProcessRequestFactory.RequestFactoryHandler;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+
+import java.lang.reflect.Proxy;
+
+/**
+ * Create JRE-compatible instances of a RequestFactory interface.
+ */
+public class RequestFactoryMagic {
+  /**
+   * Create an instance of a RequestFactory. The returned RequestFactory must be
+   * initialized with an explicit
+   * {@link com.google.gwt.requestfactory.shared.RequestTransport
+   * RequestTransport} via the
+   * {@link RequestFactory#initialize(com.google.gwt.event.shared.EventBus, com.google.gwt.requestfactory.shared.RequestTransport)
+   * initialize(EventBus, RequestTransport} method.
+   * 
+   * @param <T> the RequestFactory type
+   * @param requestFactory the RequestFactory type
+   * @param processor
+   * @return an instance of the RequestFactory type
+   * @see InProcessRequestTransport
+   */
+  public static <T extends RequestFactory> T create(Class<T> requestFactory) {
+    RequestFactoryHandler handler = new InProcessRequestFactory().new RequestFactoryHandler();
+    return requestFactory.cast(Proxy.newProxyInstance(
+        Thread.currentThread().getContextClassLoader(),
+        new Class<?>[]{requestFactory}, handler));
+  }
+
+  private RequestFactoryMagic() {
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Version.java b/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
similarity index 75%
rename from user/src/com/google/gwt/requestfactory/shared/Version.java
rename to user/src/com/google/gwt/requestfactory/server/testing/package-info.java
index 5e66314..23f9f88 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Version.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
@@ -1,23 +1,23 @@
 /*
  * 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;
 
 /**
- * Marks the version property of an entity.
+ * Classes used for testing the request factory service.
+ *
+ * @since GWT 2.1.1
  */
-public @interface Version {
-  // TODO prove the servlet will use this info
-}
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.requestfactory.server.testing;
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
index a4ca1ce..1e5cecb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
@@ -30,7 +30,7 @@
    * Subtypes should override to declare they return a stable id of their own
    * type, to allow type safe use of the request objects returned by
    * {@link RequestFactory#find(EntityProxyId)}.
-   *
+   * 
    * @return an {@link EntityProxyId} instance
    */
   EntityProxyId<?> stableId();
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
index af81fa7..a64e390 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
@@ -21,14 +21,15 @@
  * <p>
  * In particular, an {@link EntityProxy} foo that is yet to be persisted and a
  * copy of foo after being persisted have equal {@link EntityProxyId}.
- *
+ * 
  * @param <P> the entity type
  */
+@ProxyFor(Object.class)
 public interface EntityProxyId<P extends EntityProxy> {
 
   /**
    * Returns the class of the proxy identified.
-   *
+   * 
    * @return a Class object of type P
    */
   Class<P> getProxyClass();
diff --git a/user/src/com/google/gwt/requestfactory/shared/Id.java b/user/src/com/google/gwt/requestfactory/shared/Id.java
deleted file mode 100644
index 6340eff..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/Id.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.shared;
-
-/**
- * Marks the id property of an entity.
- */
-public @interface Id {
-  // TODO prove the servlet will use this info
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index e06ace1..48d1233 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -22,13 +22,13 @@
  * <p>
  * <b>Limitations on the transferrability of types.</b> <br>
  * RequestFactory currently supports the transfer of basic value types, entity
- * types, and collections, with limitations. The basic value types supported
- * are {@link String}, {@link Enum}, {@link Boolean}, {@link Character},
- * subtypes of {@link Number}, and {@link java.util.Date}. Any value type not
- * included in this list may not be declared in the type signature of a service
- * method, or {@link EntityProxy}. {@link java.util.Collection} types supported
- * are {@link java.util.List} and {@link java.util.Set} with the restriction
- * that a collection must be homogeneous and only hold one type of value.
+ * types, and collections, with limitations. The basic value types supported are
+ * {@link String}, {@link Enum}, {@link Boolean}, {@link Character}, subtypes of
+ * {@link Number}, and {@link java.util.Date}. Any value type not included in
+ * this list may not be declared in the type signature of a service method, or
+ * {@link EntityProxy}. {@link java.util.Collection} types supported are
+ * {@link java.util.List} and {@link java.util.Set} with the restriction that a
+ * collection must be homogeneous and only hold one type of value.
  * </p>
  * <p>
  * Polymorphism is not supported at this time. RequestFactory encoding and
@@ -48,16 +48,16 @@
 
   /**
    * Return a request to find a fresh instance of the referenced proxy.
-   *
+   * 
    * @param proxyId an {@link EntityProxyId} instance of type P
    * @return a {@link Request} object
    */
   <P extends EntityProxy> Request<P> find(EntityProxyId<P> proxyId);
 
   /**
-   * Returns the event bus this factory's events are posted on, which was set via
-   * {@link #initialize}.
-   *
+   * Returns the event bus this factory's events are posted on, which was set
+   * via {@link #initialize}.
+   * 
    * @return the {@link EventBus} associated with this instance
    */
   EventBus getEventBus();
@@ -96,7 +96,7 @@
    * type of this token, via {@link RequestContext#create}. The token may
    * represent either a proxy instance (see {@link #getHistoryToken}) or a proxy
    * class (see {@link #getProxyClass}).
-   *
+   * 
    * @param historyToken a String token
    * @return a Class object for an {@link EntityProxy} subclass
    */
@@ -105,7 +105,7 @@
   /**
    * Return the appropriate {@link EntityProxyId} using a string returned from
    * {@link #getHistoryToken(EntityProxyId)}.
-   *
+   * 
    * @param historyToken a String token
    * @return an {@link EntityProxyId}
    */
@@ -114,14 +114,14 @@
   /**
    * Start this request factory with a
    * {@link com.google.gwt.requestfactory.client.DefaultRequestTransport}.
-   *
+   * 
    * @param eventBus an {@link EventBus}
    */
   void initialize(EventBus eventBus);
 
   /**
    * Start this request factory with a user-provided transport.
-   *
+   * 
    * @param eventBus an {@link EventBus}
    * @param transport a {@link RequestTransport} instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/Violation.java b/user/src/com/google/gwt/requestfactory/shared/Violation.java
index c4ddb96..3698740 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Violation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Violation.java
@@ -22,21 +22,21 @@
 public interface Violation {
   /**
    * Returns the message associated with this {@link Violation}.
-   *
+   * 
    * @return a String message
    */
   String getMessage();
 
   /**
    * Returns the path associated with this {@link Violation}.
-   *
+   * 
    * @return a String path
    */
   String getPath();
 
   /**
    * Returns the proxy id associated with this {@link Violation}.
-   *
+   * 
    * @return an {@link EntityProxyId} instance
    */
   EntityProxyId<?> getProxyId();
diff --git a/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java b/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
index b1fa575..89b5d9c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -21,29 +21,26 @@
  * <li>A PERSIST event is fired after a proxy that was created on the client has
  * been persisted on the server.
  * <li>An UPDATE event is fired whenever a client encounters a proxy for the
- * first time, or encounters a proxy with new fields, or encounters a proxy
- * whose fields have been updated.
+ * first time, or encounters a proxy whose version number has changed.
  * <li>A DELETE event is fired after a proxy that was deleted on the client is
  * deleted on the server as well.
  * </ul>
  */
 public enum WriteOperation {
-  PERSIST("PERSIST"), 
-  UPDATE("UPDATE"), 
-  DELETE("DELETE");
-  
-  // use an unObfuscatedEnumName field to bypass the implicit name() method, 
+  PERSIST("PERSIST"), UPDATE("UPDATE"), DELETE("DELETE");
+
+  // use an unObfuscatedEnumName field to bypass the implicit name() method,
   // to be safe in the case enum name obfuscation is enabled.
   private final String unObfuscatedEnumName;
-  
+
   private WriteOperation(String unObfuscatedEnumName) {
     this.unObfuscatedEnumName = unObfuscatedEnumName;
   }
-  
+
   /**
    * Returns the unobfuscated name of the event associated with this
    * {@link WriteOperation}.
-   *
+   * 
    * @return one of "PERSIST", "UPDATE", or "DELETE"
    */
   public String getUnObfuscatedEnumName() {
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
new file mode 100644
index 0000000..f8b7ff1
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.impl;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.ServerFailure;
+import com.google.gwt.requestfactory.shared.Violation;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Abstract implementation of {@link Request}. Each request stores a
+ * {@link DeltaValueStoreJsonImpl}.
+ * 
+ * @param <T> return type
+ */
+public abstract class AbstractRequest<T> implements Request<T>,
+    InstanceRequest<EntityProxy, T> {
+
+  /**
+   * Used by generated subtypes.
+   */
+  protected final Set<String> propertyRefs = new HashSet<String>();
+  protected final AbstractRequestContext requestContext;
+  private Receiver<? super T> receiver;
+  private RequestData requestData;
+
+  protected AbstractRequest(AbstractRequestContext requestContext) {
+    this.requestContext = requestContext;
+  }
+
+  public void fire() {
+    requestContext.fire();
+  }
+
+  public void fire(Receiver<? super T> receiver) {
+    to(receiver);
+    fire();
+  }
+
+  /**
+   * Returns the properties.
+   */
+  public Set<String> getPropertyRefs() {
+    return Collections.unmodifiableSet(propertyRefs);
+  }
+
+  public RequestData getRequestData() {
+    if (requestData == null) {
+      requestData = makeRequestData();
+    }
+    return requestData;
+  }
+
+  public RequestContext to(Receiver<? super T> receiver) {
+    this.receiver = receiver;
+    return requestContext;
+  }
+
+  /**
+   * This method comes from the {@link InstanceRequest} interface. Instance
+   * methods place the instance in the first parameter slot.
+   */
+  public Request<T> using(EntityProxy instanceObject) {
+    getRequestData().getParameters()[0] = instanceObject;
+    /*
+     * Instance methods enqueue themselves when their using() method is called.
+     * This ensures that the instance parameter will have been set when
+     * AbstractRequestContext.retainArg() is called.
+     */
+    requestContext.addInvocation(this);
+    return this;
+  }
+
+  public Request<T> with(String... propertyRefs) {
+    this.propertyRefs.addAll(Arrays.asList(propertyRefs));
+    return this;
+  }
+
+  protected abstract RequestData makeRequestData();
+
+  boolean hasReceiver() {
+    return receiver != null;
+  }
+
+  void onFail(ServerFailure failure) {
+    if (receiver != null) {
+      receiver.onFailure(failure);
+    }
+  }
+
+  void onSuccess(Splittable split) {
+    // The user may not have called to()
+    if (receiver != null) {
+      @SuppressWarnings("unchecked")
+      T result = (T) EntityCodex.decode(requestContext,
+          requestData.getReturnType(), requestData.getElementType(), split);
+      receiver.onSuccess(result);
+    }
+  }
+
+  void onViolation(Set<Violation> errors) {
+    // The user may not have called to()
+    if (receiver != null) {
+      receiver.onViolation(errors);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
new file mode 100644
index 0000000..680286b
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -0,0 +1,652 @@
+/*
+ * 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.impl;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanCodex;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+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.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyChange;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver;
+import com.google.gwt.requestfactory.shared.ServerFailure;
+import com.google.gwt.requestfactory.shared.Violation;
+import com.google.gwt.requestfactory.shared.WriteOperation;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex;
+import com.google.gwt.requestfactory.shared.messages.IdMessage;
+import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
+import com.google.gwt.requestfactory.shared.messages.MessageFactory;
+import com.google.gwt.requestfactory.shared.messages.OperationMessage;
+import com.google.gwt.requestfactory.shared.messages.RequestMessage;
+import com.google.gwt.requestfactory.shared.messages.ResponseMessage;
+import com.google.gwt.requestfactory.shared.messages.ServerFailureMessage;
+import com.google.gwt.requestfactory.shared.messages.ViolationMessage;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base implementations for RequestContext services.
+ */
+public class AbstractRequestContext implements RequestContext,
+    EntityCodex.EntitySource {
+  private class MyViolation implements Violation {
+
+    private final EntityProxyId<?> id;
+    private final String path;
+    private final String message;
+
+    public MyViolation(ViolationMessage message) {
+      id = getId(message);
+      path = message.getPath();
+      this.message = message.getMessage();
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public String getPath() {
+      return path;
+    }
+
+    public EntityProxyId<?> getProxyId() {
+      return id;
+    }
+  }
+
+  private static final String PARENT_OBJECT = "parentObject";
+
+  private final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
+  private boolean locked;
+  private final AbstractRequestFactory requestFactory;
+  /**
+   * A map of all EntityProxies that the RequestContext has interacted with.
+   * Objects are placed into this map by being passed into {@link #edit} or as
+   * an invocation argument.
+   */
+  private final Map<SimpleEntityProxyId<?>, AutoBean<?>> editedProxies = new LinkedHashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
+  /**
+   * A map that contains the canonical instance of an entity to return in the
+   * return graph, since this is built from scratch.
+   */
+  private final Map<SimpleEntityProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
+
+  protected AbstractRequestContext(AbstractRequestFactory factory) {
+    this.requestFactory = factory;
+  }
+
+  /**
+   * Create a new object, with an ephemeral id.
+   */
+  public <T extends EntityProxy> T create(Class<T> clazz) {
+    checkLocked();
+
+    AutoBean<T> created = requestFactory.createEntityProxy(clazz,
+        requestFactory.allocateId(clazz));
+    return takeOwnership(created);
+  }
+
+  public <T extends EntityProxy> T edit(T object) {
+    AutoBean<T> bean = checkStreamsNotCrossed(object);
+
+    checkLocked();
+
+    @SuppressWarnings("unchecked")
+    AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(object.stableId());
+    if (previouslySeen != null && !previouslySeen.isFrozen()) {
+      /*
+       * If we've seen the object before, it might be because it was passed in
+       * as a method argument. This does not guarantee its mutability, so check
+       * that here before returning the cached object.
+       */
+      return previouslySeen.as();
+    }
+
+    // Create editable copies
+    AutoBean<T> parent = bean;
+    bean = cloneBeanAndCollections(bean);
+    bean.setTag(PARENT_OBJECT, parent);
+    return takeOwnership(bean);
+  }
+
+  /**
+   * Make sure there's a default receiver so errors don't get dropped. This
+   * behavior should be revisited when chaining is supported, depending on
+   * whether or not chained invocations can fail independently.
+   */
+  public void fire() {
+    boolean needsReceiver = true;
+    for (AbstractRequest<?> request : invocations) {
+      if (request.hasReceiver()) {
+        needsReceiver = false;
+        break;
+      }
+    }
+
+    if (needsReceiver) {
+      doFire(new Receiver<Void>() {
+        @Override
+        public void onSuccess(Void response) {
+          // Don't care
+        }
+      });
+    } else {
+      doFire(null);
+    }
+  }
+
+  public void fire(final Receiver<Void> receiver) {
+    if (receiver == null) {
+      throw new IllegalArgumentException();
+    }
+    doFire(receiver);
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
+      String serializedProxyId) {
+    SimpleEntityProxyId<Q> id = requestFactory.getProxyId(serializedProxyId);
+    return getProxyForReturnPayloadGraph(id);
+  }
+
+  public AbstractRequestFactory getRequestFactory() {
+    return requestFactory;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public String getSerializedProxyId(EntityProxyId<?> stableId) {
+    return requestFactory.getHistoryToken(stableId);
+  }
+
+  public boolean isChanged() {
+    /*
+     * NB: Don't use the presence of ephemeral objects for this test.
+     * 
+     * Diff the objects until one is found to be different. It's not just a
+     * simple flag-check because of the possibility of "unmaking" a change, per
+     * the JavaDoc.
+     */
+    for (AutoBean<?> bean : editedProxies.values()) {
+      AutoBean<?> previous = bean.getTag(PARENT_OBJECT);
+      if (previous == null) {
+        // Compare to empty object
+        Class<?> proxyClass = ((EntityProxy) bean.as()).stableId().getProxyClass();
+        previous = getRequestFactory().getAutoBeanFactory().create(proxyClass);
+      }
+      if (!AutoBeanUtils.diff(previous, bean).isEmpty()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * EntityCodex support.
+   */
+  public boolean isEntityType(Class<?> clazz) {
+    return requestFactory.getTypeToken(clazz) != null;
+  }
+
+  public boolean isLocked() {
+    return locked;
+  }
+
+  /**
+   * Called by generated subclasses to enqueue a method invocation.
+   */
+  protected void addInvocation(AbstractRequest<?> request) {
+    invocations.add(request);
+    for (Object arg : request.getRequestData().getParameters()) {
+      retainArg(arg);
+    }
+  }
+
+  private void checkLocked() {
+    if (locked) {
+      throw new IllegalStateException("A request is already in progress");
+    }
+  }
+
+  /**
+   * This method checks that a proxy object is either immutable, or already
+   * edited by this context.
+   */
+  private <T> AutoBean<T> checkStreamsNotCrossed(T object) {
+    AutoBean<T> bean = AutoBeanUtils.getAutoBean(object);
+    if (bean == null) {
+      // Unexpected; some kind of foreign implementation?
+      throw new IllegalArgumentException(object.getClass().getName());
+    }
+
+    RequestContext context = bean.getTag(EntityProxyCategory.REQUEST_CONTEXT);
+    if (!bean.isFrozen() && context != this) {
+      /*
+       * This means something is way off in the weeds. If a bean is editable,
+       * it's supposed to be associated with a RequestContext.
+       */
+      assert context != null : "Unfrozen bean with null RequestContext";
+
+      /*
+       * Already editing the object in another context or it would have been in
+       * the editing map.
+       */
+      throw new IllegalArgumentException("Attempting to edit an EntityProxy"
+          + " previously edited by another RequestContext");
+    }
+    return bean;
+  }
+
+  /**
+   * Shallow-clones an autobean and makes duplicates of the collection types. A
+   * regular {@link AutoBean#clone} won't duplicate reference properties.
+   */
+  private <T> AutoBean<T> cloneBeanAndCollections(AutoBean<T> toClone) {
+    AutoBean<T> clone = toClone.clone(false);
+    clone.accept(new AutoBeanVisitor() {
+      @Override
+      public boolean visitReferenceProperty(String propertyName,
+          AutoBean<?> value, PropertyContext ctx) {
+        if (value != null) {
+          if (List.class == ctx.getType()) {
+            ctx.set(new ArrayList<Object>((List<?>) value.as()));
+          } else if (Set.class == ctx.getType()) {
+            ctx.set(new HashSet<Object>((Set<?>) value.as()));
+          }
+        }
+        return false;
+      }
+    });
+    return clone;
+  }
+
+  private void doFire(final Receiver<Void> receiver) {
+    checkLocked();
+    locked = true;
+
+    freezeEntities(true);
+
+    String payload = makePayload();
+    requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
+      public void onTransportFailure(String message) {
+        ServerFailure failure = new ServerFailure(message, null, null);
+        reuse();
+        for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(
+            invocations)) {
+          request.onFail(failure);
+        }
+        if (receiver != null) {
+          receiver.onFailure(failure);
+        }
+      }
+
+      public void onTransportSuccess(String payload) {
+        ResponseMessage response = AutoBeanCodex.decode(
+            MessageFactoryHolder.FACTORY, ResponseMessage.class, payload).as();
+        if (response.getGeneralFailure() != null) {
+          ServerFailureMessage failure = response.getGeneralFailure();
+          ServerFailure fail = new ServerFailure(failure.getMessage(),
+              failure.getExceptionType(), failure.getStackTrace());
+
+          reuse();
+          for (AbstractRequest<?> invocation : new ArrayList<AbstractRequest<?>>(
+              invocations)) {
+            invocation.onFail(fail);
+          }
+          if (receiver != null) {
+            receiver.onFailure(fail);
+          }
+          return;
+        }
+
+        // Process violations and then stop
+        if (response.getViolations() != null) {
+          Set<Violation> errors = new HashSet<Violation>();
+          for (ViolationMessage message : response.getViolations()) {
+            errors.add(new MyViolation(message));
+          }
+
+          reuse();
+          for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(
+              invocations)) {
+            request.onViolation(errors);
+          }
+          if (receiver != null) {
+            receiver.onViolation(errors);
+          }
+          return;
+        }
+
+        // Process operations
+        processReturnOperations(response);
+
+        // Send return values
+        for (int i = 0, j = invocations.size(); i < j; i++) {
+          if (response.getStatusCodes().get(i)) {
+            invocations.get(i).onSuccess(response.getInvocationResults().get(i));
+          } else {
+            ServerFailureMessage failure = AutoBeanCodex.decode(
+                MessageFactoryHolder.FACTORY, ServerFailureMessage.class,
+                response.getInvocationResults().get(i)).as();
+            invocations.get(i).onFail(
+                new ServerFailure(failure.getMessage(),
+                    failure.getExceptionType(), failure.getStackTrace()));
+          }
+        }
+
+        if (receiver != null) {
+          receiver.onSuccess(null);
+        }
+        // After success, shut down the context
+        editedProxies.clear();
+        invocations.clear();
+        returnedProxies.clear();
+      }
+    });
+  }
+
+  /**
+   * Set the frozen status of all EntityProxies owned by this context.
+   */
+  private void freezeEntities(boolean frozen) {
+    for (AutoBean<?> bean : editedProxies.values()) {
+      bean.setFrozen(frozen);
+    }
+  }
+
+  /**
+   * Resolves an IdMessage into an EntityProxyId.
+   */
+  private SimpleEntityProxyId<EntityProxy> getId(IdMessage op) {
+    SimpleEntityProxyId<EntityProxy> id;
+    if (op.getClientId() > 0) {
+      id = requestFactory.getId(op.getTypeToken(), op.getServerId(),
+          op.getClientId());
+    } else {
+      id = requestFactory.getId(op.getTypeToken(), op.getServerId());
+    }
+    return id;
+  }
+
+  /**
+   * Creates or retrieves a new canonical AutoBean to represent the given id in
+   * the returned payload.
+   */
+  private <Q extends EntityProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
+      SimpleEntityProxyId<Q> id) {
+    assert !id.isEphemeral();
+
+    @SuppressWarnings("unchecked")
+    AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
+    if (bean == null) {
+      Class<Q> proxyClass = id.getProxyClass();
+      bean = requestFactory.createEntityProxy(proxyClass, id);
+      returnedProxies.put(id, bean);
+    }
+
+    return bean;
+  }
+
+  /**
+   * Make an EntityProxy immutable.
+   */
+  private void makeImmutable(final AutoBean<? extends EntityProxy> toMutate) {
+    // Always diff'ed against itself, producing a no-op
+    toMutate.setTag(PARENT_OBJECT, toMutate);
+    // Act with entity-identity semantics
+    toMutate.setTag(EntityProxyCategory.REQUEST_CONTEXT, null);
+    toMutate.setFrozen(true);
+  }
+
+  /**
+   * Assemble all of the state that has been accumulated in this context. This
+   * includes:
+   * <ul>
+   * <li>Diffs accumulated on objects passed to {@link #edit}.
+   * <li>Invocations accumulated as Request subtypes passed to
+   * {@link #addInvocation}.
+   * </ul>
+   */
+  private String makePayload() {
+    // Get the factory from the runtime-specific holder.
+    MessageFactory f = MessageFactoryHolder.FACTORY;
+
+    List<OperationMessage> operations = new ArrayList<OperationMessage>();
+    // Compute deltas for each entity seen by the context
+    for (AutoBean<?> currentView : editedProxies.values()) {
+      @SuppressWarnings("unchecked")
+      SimpleEntityProxyId<EntityProxy> stableId = EntityProxyCategory.stableId((AutoBean<EntityProxy>) currentView);
+
+      // The OperationMessages describes operations on exactly one entity
+      OperationMessage operation = f.operation().as();
+      operation.setTypeToken(requestFactory.getTypeToken(stableId.getProxyClass()));
+
+      // Find the object to compare against
+      AutoBean<?> parent = currentView.getTag(PARENT_OBJECT);
+      if (parent == null) {
+        // Newly-created object, use a blank object to compare against
+        parent = requestFactory.createEntityProxy(stableId.getProxyClass(),
+            stableId);
+
+        // Newly-created objects go into the persist operation bucket
+        operation.setOperation(WriteOperation.PERSIST);
+        // The ephemeral id is passed to the server
+        operation.setClientId(stableId.getClientId());
+      } else {
+        // Requests involving existing objects use the persisted id
+        operation.setServerId(stableId.getServerId());
+        operation.setOperation(WriteOperation.UPDATE);
+      }
+
+      // Send our version number to the server to cut down on payload size
+      Integer version = currentView.getTag(Constants.ENCODED_VERSION_PROPERTY);
+      if (version != null) {
+        operation.setVersion(version);
+      }
+
+      // Compute what's changed on the client
+      Map<String, Object> diff = AutoBeanUtils.diff(parent, currentView);
+      if (!diff.isEmpty()) {
+        Map<String, Splittable> propertyMap = new HashMap<String, Splittable>();
+        for (Map.Entry<String, Object> entry : diff.entrySet()) {
+          propertyMap.put(entry.getKey(),
+              EntityCodex.encode(this, entry.getValue()));
+        }
+        operation.setPropertyMap(propertyMap);
+      }
+      operations.add(operation);
+    }
+
+    List<InvocationMessage> invocationMessages = new ArrayList<InvocationMessage>();
+    for (AbstractRequest<?> invocation : invocations) {
+      RequestData data = invocation.getRequestData();
+      InvocationMessage message = f.invocation().as();
+
+      String opsToSend = data.getOperation();
+      if (!opsToSend.isEmpty()) {
+        message.setOperation(opsToSend);
+      }
+
+      Set<String> refsToSend = data.getPropertyRefs();
+      if (!refsToSend.isEmpty()) {
+        message.setPropertyRefs(refsToSend);
+      }
+
+      List<Splittable> parameters = new ArrayList<Splittable>(
+          data.getParameters().length);
+      for (Object param : data.getParameters()) {
+        parameters.add(EntityCodex.encode(this, param));
+      }
+      if (!parameters.isEmpty()) {
+        message.setParameters(parameters);
+      }
+
+      invocationMessages.add(message);
+    }
+
+    // Create the outer envelope message
+    AutoBean<RequestMessage> bean = f.request();
+    RequestMessage requestMessage = bean.as();
+    if (!invocationMessages.isEmpty()) {
+      requestMessage.setInvocations(invocationMessages);
+    }
+    if (!operations.isEmpty()) {
+      requestMessage.setOperations(operations);
+    }
+    return AutoBeanCodex.encode(bean).getPayload();
+  }
+
+  /**
+   * Create a new EntityProxy from a snapshot in the return payload.
+   * 
+   * @param id the EntityProxyId of the object
+   * @param returnRecord the JSON map containing property/value pairs
+   * @param operations the WriteOperation eventns to broadcast over the EventBus
+   */
+  private <Q extends EntityProxy> Q processReturnOperation(
+      SimpleEntityProxyId<Q> id, OperationMessage op,
+      WriteOperation... operations) {
+
+    AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
+    toMutate.setTag(Constants.ENCODED_VERSION_PROPERTY, op.getVersion());
+
+    final Map<String, Splittable> properties = op.getPropertyMap();
+    if (properties != null) {
+      // Apply updates
+      toMutate.accept(new AutoBeanVisitor() {
+        @Override
+        public boolean visitReferenceProperty(String propertyName,
+            AutoBean<?> value, PropertyContext ctx) {
+          if (ctx.canSet()) {
+            if (properties.containsKey(propertyName)) {
+              Splittable raw = properties.get(propertyName);
+              Class<?> elementType = ctx instanceof CollectionPropertyContext
+                  ? ((CollectionPropertyContext) ctx).getElementType() : null;
+              Object decoded = EntityCodex.decode(AbstractRequestContext.this,
+                  ctx.getType(), elementType, raw);
+              ctx.set(decoded);
+            }
+          }
+          return false;
+        }
+
+        @Override
+        public boolean visitValueProperty(String propertyName, Object value,
+            PropertyContext ctx) {
+          if (ctx.canSet()) {
+            if (properties.containsKey(propertyName)) {
+              Splittable raw = properties.get(propertyName);
+              Object decoded = ValueCodex.decode(ctx.getType(), raw);
+              ctx.set(decoded);
+            }
+          }
+          return false;
+        }
+      });
+    }
+
+    // Finished applying updates, freeze the bean
+    makeImmutable(toMutate);
+    Q proxy = toMutate.as();
+
+    /*
+     * Notify subscribers if the object differs from when it first came into the
+     * RequestContext.
+     */
+    if (operations != null) {
+      for (WriteOperation writeOperation : operations) {
+        if (writeOperation.equals(WriteOperation.UPDATE)
+            && !requestFactory.hasVersionChanged(id, op.getVersion())) {
+          // No updates if the server reports no change
+          continue;
+        }
+        requestFactory.getEventBus().fireEventFromSource(
+            new EntityProxyChange<EntityProxy>(proxy, writeOperation),
+            id.getProxyClass());
+      }
+    }
+    return proxy;
+  }
+
+  /**
+   * Process an array of OperationMessages.
+   */
+  private void processReturnOperations(ResponseMessage response) {
+    List<OperationMessage> records = response.getOperations();
+
+    // If there are no observable effects, this will be null
+    if (records == null) {
+      return;
+    }
+
+    for (OperationMessage op : records) {
+      SimpleEntityProxyId<EntityProxy> id = getId(op);
+      if (id.wasEphemeral()) {
+        processReturnOperation(id, op, WriteOperation.PERSIST,
+            WriteOperation.UPDATE);
+      } else {
+        processReturnOperation(id, op, WriteOperation.UPDATE);
+      }
+    }
+  }
+
+  /**
+   * Ensures that any method arguments are retained in the context's sphere of
+   * influence.
+   */
+  private void retainArg(Object arg) {
+    if (arg instanceof Iterable<?>) {
+      for (Object o : (Iterable<?>) arg) {
+        retainArg(o);
+      }
+    } else if (arg instanceof EntityProxy) {
+      // Calling edit will validate and set up the tracking we need
+      edit((EntityProxy) arg);
+    }
+  }
+
+  /**
+   * Returns the requests that were dequeued as part of reusing the context.
+   */
+  private void reuse() {
+    freezeEntities(false);
+    locked = false;
+  }
+
+  /**
+   * Make the EnityProxy bean edited and owned by this RequestContext.
+   */
+  private <T extends EntityProxy> T takeOwnership(AutoBean<T> bean) {
+    editedProxies.put(EntityProxyCategory.stableId(bean), bean);
+    bean.setTag(EntityProxyCategory.REQUEST_CONTEXT,
+        AbstractRequestContext.this);
+    return bean.as();
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
new file mode 100644
index 0000000..3689365
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -0,0 +1,135 @@
+/*
+ * 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.impl;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.RequestTransport;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * 
+ */
+public abstract class AbstractRequestFactory extends IdFactory implements
+    RequestFactory {
+  private static final int MAX_VERSION_ENTRIES = 10000;
+
+  private EventBus eventBus;
+
+  @SuppressWarnings("serial")
+  private final Map<String, Integer> version = new LinkedHashMap<String, Integer>(
+      16, 0.75f, true) {
+    @Override
+    protected boolean removeEldestEntry(Entry<String, Integer> eldest) {
+      return size() > MAX_VERSION_ENTRIES;
+    }
+  };
+  private RequestTransport transport;
+
+  /**
+   * Creates a new EntityProxy with an assigned ID.
+   */
+  public <T extends EntityProxy> AutoBean<T> createEntityProxy(Class<T> clazz,
+      SimpleEntityProxyId<T> id) {
+    AutoBean<T> created = getAutoBeanFactory().create(clazz);
+    if (created == null) {
+      throw new IllegalArgumentException("Unknown EntityProxy type "
+          + clazz.getName());
+    }
+    created.setTag(EntityProxyCategory.REQUEST_FACTORY, this);
+    created.setTag(EntityProxyCategory.STABLE_ID, id);
+    return created;
+  }
+
+  public <P extends EntityProxy> Request<P> find(final EntityProxyId<P> proxyId) {
+    if (((SimpleEntityProxyId<P>) proxyId).isEphemeral()) {
+      throw new IllegalArgumentException("Cannot fetch unpersisted entity");
+    }
+
+    AbstractRequestContext context = new AbstractRequestContext(
+        AbstractRequestFactory.this);
+    return new AbstractRequest<P>(context) {
+      {
+        requestContext.addInvocation(this);
+      }
+
+      @Override
+      protected RequestData makeRequestData() {
+        return new RequestData(
+            "com.google.gwt.requestfactory.shared.impl.FindRequest::find",
+            new Object[]{proxyId}, propertyRefs, proxyId.getProxyClass(), null);
+      }
+    };
+  }
+
+  public EventBus getEventBus() {
+    return eventBus;
+  }
+
+  public Class<? extends EntityProxy> getProxyClass(String historyToken) {
+    String typeToken = IdUtil.getTypeToken(historyToken);
+    if (typeToken != null) {
+      return getTypeFromToken(typeToken);
+    }
+    return getTypeFromToken(historyToken);
+  }
+
+  public RequestTransport getRequestTransport() {
+    return transport;
+  }
+
+  /**
+   * The choice of a default request transport is runtime-specific.
+   */
+  public abstract void initialize(EventBus eventBus);
+
+  public void initialize(EventBus eventBus, RequestTransport transport) {
+    this.eventBus = eventBus;
+    this.transport = transport;
+  }
+
+  /**
+   * Implementations of EntityProxies are provided by an AutoBeanFactory, which
+   * is itself a generated type.
+   */
+  protected abstract AutoBeanFactory getAutoBeanFactory();
+
+  /**
+   * Used by {@link AbstractRequestContext} to quiesce update events for objects
+   * that haven't truly changed.
+   */
+  protected boolean hasVersionChanged(SimpleEntityProxyId<?> id,
+      int observedVersion) {
+    String key = getHistoryToken(id);
+    Integer existingVersion = version.get(key);
+    // Return true if we haven't seen this before or the versions differ
+    boolean toReturn = existingVersion == null
+        || !existingVersion.equals(observedVersion);
+    if (toReturn) {
+      version.put(key, observedVersion);
+    }
+    return toReturn;
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
index e2d1a38..9ebc01a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -20,18 +20,6 @@
  */
 public interface Constants {
 
-  String CONTENT_TOKEN = "contentData";
-  String OPERATION_TOKEN = "operation";
-  String PARAM_TOKEN = "param";
-  String PROPERTY_REF_TOKEN = "propertyRefs";
-  String RESULT_TOKEN = "result";
-  String RELATED_TOKEN = "related";
-  String SIDE_EFFECTS_TOKEN = "sideEffects";
-  String VIOLATIONS_TOKEN = "violations";
-  /**
-   * Property on a proxy JSO that holds its futureId.
-   */
-  String ENCODED_FUTUREID_PROPERTY = "!futureId";
   /**
    * Property on a proxy JSO that holds its encoded server side data store id.
    */
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
similarity index 91%
rename from user/src/com/google/gwt/requestfactory/client/impl/EntityProxyCategory.java
rename to user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
index 561de38..6fe67c6 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
@@ -13,11 +13,12 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.client.impl;
+package com.google.gwt.requestfactory.shared.impl;
 
-import com.google.gwt.editor.client.AutoBean;
-import com.google.gwt.editor.client.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
 import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.RequestFactory;
 
 /**
  * Contains static implementation methods used by the AutoBean generator.
@@ -53,9 +54,9 @@
     return (AbstractRequestContext) bean.getTag(REQUEST_CONTEXT);
   }
 
-  public static AbstractRequestFactory requestFactory(
+  public static RequestFactory requestFactory(
       AutoBean<? extends EntityProxy> bean) {
-    return (AbstractRequestFactory) bean.getTag(REQUEST_FACTORY);
+    return (RequestFactory) bean.getTag(REQUEST_FACTORY);
   }
 
   @SuppressWarnings("unchecked")
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/FindRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
similarity index 86%
rename from user/src/com/google/gwt/requestfactory/client/impl/FindRequest.java
rename to user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
index 7bbee53..b314775 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/FindRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
@@ -14,19 +14,20 @@
  * the License.
  */
 
-package com.google.gwt.requestfactory.client.impl;
+package com.google.gwt.requestfactory.shared.impl;
 
 import com.google.gwt.requestfactory.server.FindService;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
 import com.google.gwt.requestfactory.shared.Service;
 
 /**
  * Request selector interface for implementing a find method.
  */
 @Service(FindService.class)
-public interface FindRequest {
+public interface FindRequest extends RequestContext {
   /**
    * Use the implicit lookup in passing EntityProxy types to service methods.
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
new file mode 100644
index 0000000..cc106e4
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -0,0 +1,193 @@
+/*
+ * 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.impl;
+
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handles common code for creating SimpleEntityProxyIds.
+ */
+public abstract class IdFactory {
+  /**
+   * Maps ephemeral history tokens to an id object. This canonicalizing mapping
+   * resolves the problem of EntityProxyIds hashcodes changing after persist.
+   * Only ids that are created in the RequestFactory are stored here.
+   */
+  private final Map<String, SimpleEntityProxyId<?>> ephemeralIds = new HashMap<String, SimpleEntityProxyId<?>>();
+
+  /**
+   * Allocates an ephemeral proxy id. This object is only valid for the lifetime
+   * of the RequestFactory.
+   */
+  public <P extends EntityProxy> SimpleEntityProxyId<P> allocateId(
+      Class<P> clazz) {
+    SimpleEntityProxyId<P> toReturn = new SimpleEntityProxyId<P>(clazz,
+        ephemeralIds.size() + 1);
+    ephemeralIds.put(getHistoryToken(toReturn), toReturn);
+    return toReturn;
+  }
+
+  public String getHistoryToken(Class<? extends EntityProxy> clazz) {
+    return getTypeToken(clazz);
+  }
+
+  public String getHistoryToken(EntityProxyId<?> proxy) {
+    SimpleEntityProxyId<?> id = (SimpleEntityProxyId<?>) proxy;
+    if (id.isEphemeral()) {
+      return IdUtil.ephemeralId(id.getClientId(),
+          getHistoryToken(proxy.getProxyClass()));
+    } else {
+      return IdUtil.persistedId(id.getServerId(),
+          getHistoryToken(proxy.getProxyClass()));
+    }
+  }
+
+  /**
+   * Create or retrieve a SimpleEntityProxyId.
+   */
+  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(Class<P> clazz,
+      String serverId) {
+    return getId(getTypeToken(clazz), serverId);
+  }
+
+  /**
+   * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
+   * are specified and the id is ephemeral, it will be updated with the server
+   * id.
+   */
+  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(Class<P> clazz,
+      String serverId, Integer clientId) {
+    return getId(getTypeToken(clazz), serverId, clientId);
+  }
+
+  /**
+   * Create or retrieve a SimpleEntityProxyId.
+   */
+  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(String typeToken,
+      String serverId) {
+    return getId(typeToken, serverId, null);
+  }
+
+  /**
+   * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
+   * are specified and the id is ephemeral, it will be updated with the server
+   * id.
+   */
+  public <P extends EntityProxy> SimpleEntityProxyId<P> getId(String typeToken,
+      String serverId, Integer clientId) {
+    /*
+     * If there's a clientId, that probably means we've just created a brand-new
+     * EntityProxy or have just persisted something on the server.
+     */
+    if (clientId != null) {
+      // Try a cache lookup for the ephemeral key
+      String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
+      @SuppressWarnings("unchecked")
+      SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(ephemeralKey);
+
+      // Do we need to allocate an ephemeral id?
+      if (toReturn == null) {
+        Class<P> clazz = getTypeFromToken(typeToken);
+        toReturn = new SimpleEntityProxyId<P>(clazz, clientId);
+        ephemeralIds.put(ephemeralKey, toReturn);
+      }
+
+      // If it's ephemeral, see if we have a serverId and save it
+      if (toReturn.isEphemeral()) {
+        // Sanity check
+        assert toReturn.getProxyClass().equals(getTypeFromToken(typeToken));
+
+        if (serverId != null) {
+          /*
+           * Record the server id so a later "find" operation will have an equal
+           * stableId.
+           */
+          toReturn.setServerId(serverId);
+          String serverKey = IdUtil.persistedId(serverId, typeToken);
+          ephemeralIds.put(serverKey, toReturn);
+        }
+      }
+      return toReturn;
+    }
+
+    // Should never get this far without a server id
+    assert serverId != null : "serverId";
+
+    String serverKey = IdUtil.persistedId(serverId, typeToken);
+    @SuppressWarnings("unchecked")
+    SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(serverKey);
+    if (toReturn != null) {
+      // A cache hit for a locally-created object that has been persisted
+      return toReturn;
+    }
+
+    /*
+     * No existing id, so it was never an ephemeral id created by this
+     * RequestFactory, so we don't need to record it. This should be the normal
+     * case for read-dominated applications.
+     */
+    Class<P> clazz = getTypeFromToken(typeToken);
+    return new SimpleEntityProxyId<P>(clazz, serverId);
+  }
+
+  public <P extends EntityProxy> SimpleEntityProxyId<P> getProxyId(
+      String historyToken) {
+    if (IdUtil.isPersisted(historyToken)) {
+      return getId(IdUtil.getTypeToken(historyToken),
+          IdUtil.getServerId(historyToken));
+    }
+    if (IdUtil.isEphemeral(historyToken)) {
+      @SuppressWarnings("unchecked")
+      SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(historyToken);
+
+      /*
+       * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
+       * order to get here, the user would have to get an unpersisted history
+       * token and attempt to use it with a different RequestFactory instance.
+       * This could occur if an ephemeral token was bookmarked. In this case,
+       * we'll create a token, however it will never match anything.
+       */
+      if (toReturn == null) {
+        Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
+        toReturn = new SimpleEntityProxyId<P>(clazz, -1 * ephemeralIds.size());
+        ephemeralIds.put(historyToken, toReturn);
+      }
+
+      return toReturn;
+    }
+    throw new IllegalArgumentException(historyToken);
+  }
+
+  protected abstract <P extends EntityProxy> Class<P> getTypeFromToken(
+      String typeToken);
+
+  protected abstract String getTypeToken(Class<?> clazz);
+
+  private <P> Class<P> checkTypeToken(String token) {
+    @SuppressWarnings("unchecked")
+    Class<P> clazz = (Class<P>) getTypeFromToken(token);
+    if (clazz == null) {
+      throw new IllegalArgumentException("Unknnown type");
+    }
+    return clazz;
+  }
+
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/Version.java b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
similarity index 60%
copy from user/src/com/google/gwt/requestfactory/shared/Version.java
copy to user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
index 5e66314..069509d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Version.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -1,23 +1,26 @@
 /*
  * 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;
+package com.google.gwt.requestfactory.shared.impl;
+
+import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
+import com.google.gwt.requestfactory.shared.messages.MessageFactory;
 
 /**
- * Marks the version property of an entity.
+ * This class has a super-source version with a client-only implementation.
  */
-public @interface Version {
-  // TODO prove the servlet will use this info
+interface MessageFactoryHolder {
+  MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java b/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
new file mode 100644
index 0000000..cb9c659
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
@@ -0,0 +1,69 @@
+/*
+ * 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.impl;
+
+import java.util.Set;
+
+/**
+ * A class that encapsulates the parameters and method name to be invoked on the
+ * server.
+ */
+public class RequestData {
+  private final Class<?> elementType;
+  private final String operation;
+  private final Object[] parameters;
+  private final Set<String> propertyRefs;
+  private final Class<?> returnType;
+
+  public RequestData(String operation, Object[] parameters,
+      Set<String> propertyRefs, Class<?> returnType, Class<?> elementType) {
+    this.operation = operation;
+    this.parameters = parameters;
+    this.propertyRefs = propertyRefs;
+    this.returnType = returnType;
+    this.elementType = elementType;
+  }
+
+  /**
+   * Used to interpret the returned payload.
+   */
+  public Class<?> getElementType() {
+    return elementType;
+  }
+
+  public String getOperation() {
+    return operation;
+  }
+
+  /**
+   * Used by InstanceRequest subtypes to reset the instance object in the
+   * <code>using</code> method.
+   */
+  public Object[] getParameters() {
+    return parameters;
+  }
+
+  public Set<String> getPropertyRefs() {
+    return propertyRefs;
+  }
+
+  /**
+   * Used to interpret the returned payload.
+   */
+  public Class<?> getReturnType() {
+    return returnType;
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/SimpleEntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
similarity index 79%
rename from user/src/com/google/gwt/requestfactory/client/impl/SimpleEntityProxyId.java
rename to user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
index fd24028..5a98ae6 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/SimpleEntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -13,10 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.client.impl;
+package com.google.gwt.requestfactory.shared.impl;
 
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
 
 /**
  * Nothing fancy.
@@ -29,7 +30,7 @@
    * A placeholder value for {@link #clientId} to indicate the id was not
    * created locally.
    */
-  private static final int NEVER_EPHEMERAL = -1;
+  public static final int NEVER_EPHEMERAL = -1;
 
   /**
    * The client-side id is ephemeral, and is valid only during the lifetime of a
@@ -55,26 +56,29 @@
   private String serverId;
 
   /**
-   * Construct a stable id. May only be called from
-   * {@link AbstractRequestFactory#getId}.
+   * Construct an ephemeral id. May be called only from
+   * {@link IdFactory#getId()}.
+   */
+  SimpleEntityProxyId(Class<P> proxyClass, int clientId) {
+    assert proxyClass != null;
+    this.clientId = clientId;
+    this.proxyClass = proxyClass;
+    hashCode = clientId;
+  }
+
+  /**
+   * Construct a stable id. May only be called from {@link IdFactory#getId()}
    */
   SimpleEntityProxyId(Class<P> proxyClass, String serverId) {
+    assert proxyClass != null;
+    assert serverId != null && !serverId.contains("@")
+        && !"null".equals(serverId);
     setServerId(serverId);
     clientId = NEVER_EPHEMERAL;
     hashCode = serverId.hashCode();
     this.proxyClass = proxyClass;
   }
 
-  /**
-   * Construct an ephemeral id. May be called only from
-   * {@link AbstractRequestFactory#allocateId}.
-   */
-  SimpleEntityProxyId(Class<P> proxyClass, int clientId) {
-    this.clientId = clientId;
-    this.proxyClass = proxyClass;
-    hashCode = clientId;
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -131,6 +135,26 @@
     if (this.serverId != null) {
       throw new IllegalStateException();
     }
+    assert !"null".equals(serverId);
     this.serverId = serverId;
   }
+
+  /**
+   * For debugging use only.
+   */
+  @Override
+  public String toString() {
+    if (isEphemeral()) {
+      return IdUtil.ephemeralId(clientId, proxyClass.getName());
+    } else {
+      return IdUtil.persistedId(serverId, proxyClass.getName());
+    }
+  }
+
+  /**
+   * Returns <code>true</code> if the id was created as an ephemeral id.
+   */
+  public boolean wasEphemeral() {
+    return clientId != NEVER_EPHEMERAL;
+  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
new file mode 100644
index 0000000..09b8aac
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
@@ -0,0 +1,146 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.autobean.shared.impl.LazySplittable;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Analogous to {@link ValueCodex}, but for object types.
+ */
+public class EntityCodex {
+  /**
+   * Abstracts the process by which EntityProxies are created.
+   */
+  public interface EntitySource {
+    <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
+        String serializedProxyId);
+
+    String getSerializedProxyId(EntityProxyId<?> stableId);
+
+    boolean isEntityType(Class<?> clazz);
+  }
+
+  /**
+   * Collection support is limited to value types and resolving ids.
+   */
+  public static Object decode(EntitySource source, Class<?> type,
+      Class<?> elementType, Splittable split) {
+    if (split == null || split == LazySplittable.NULL) {
+      return null;
+    }
+
+    // Collection support
+    if (elementType != null) {
+      Collection<Object> collection = null;
+      if (List.class.equals(type)) {
+        collection = new ArrayList<Object>();
+      } else if (Set.class.equals(type)) {
+        collection = new HashSet<Object>();
+      } else {
+        throw new UnsupportedOperationException();
+      }
+
+      // Decode values
+      if (ValueCodex.canDecode(elementType)) {
+        for (int i = 0, j = split.size(); i < j; i++) {
+          if (split.isNull(i)) {
+            collection.add(null);
+          } else {
+            Object element = ValueCodex.decode(elementType, split.get(i));
+            collection.add(element);
+          }
+        }
+      } else {
+        for (int i = 0, j = split.size(); i < j; i++) {
+          if (split.isNull(i)) {
+            collection.add(null);
+          } else {
+            Object element = decode(source, elementType, null, split.get(i));
+            collection.add(element);
+          }
+        }
+      }
+      return collection;
+    }
+
+    if (source.isEntityType(type) || EntityProxyId.class.equals(type)) {
+      return source.getBeanForPayload(split.asString()).as();
+    }
+
+    // Fall back to values
+    return ValueCodex.decode(type, split);
+  }
+
+  /**
+   * Collection support is limited to value types and resolving ids.
+   */
+  public static Object decode(EntitySource source, Class<?> type,
+      Class<?> elementType, String jsonPayload) {
+    Splittable split = StringQuoter.split(jsonPayload);
+    return decode(source, type, elementType, split);
+  }
+
+  /**
+   * Create a wire-format representation of an object.
+   */
+  public static Splittable encode(EntitySource source, Object value) {
+    if (value == null) {
+      return LazySplittable.NULL;
+    }
+
+    if (value instanceof Iterable<?>) {
+      StringBuffer toReturn = new StringBuffer();
+      toReturn.append('[');
+      boolean first = true;
+      for (Object val : ((Iterable<?>) value)) {
+        if (!first) {
+          toReturn.append(',');
+        } else {
+          first = false;
+        }
+        if (val == null) {
+          toReturn.append("null");
+        } else {
+          toReturn.append(encode(source, val).getPayload());
+        }
+      }
+      toReturn.append(']');
+      return new LazySplittable(toReturn.toString());
+    }
+
+    if (value instanceof EntityProxy) {
+      value = source.getSerializedProxyId(((EntityProxy) value).stableId());
+    }
+
+    if (value instanceof EntityProxyId<?>) {
+      value = source.getSerializedProxyId((EntityProxyId<?>) value);
+    }
+
+    return ValueCodex.encode(value);
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
new file mode 100644
index 0000000..11c758f
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
@@ -0,0 +1,45 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Used as a base type for messages that are about a particular id.
+ */
+public interface IdMessage {
+  String CLIENT_ID = "C";
+  String SERVER_ID = "S";
+  String TYPE_TOKEN = "T";
+
+  @PropertyName(CLIENT_ID)
+  int getClientId();
+
+  @PropertyName(SERVER_ID)
+  String getServerId();
+
+  @PropertyName(TYPE_TOKEN)
+  String getTypeToken();
+
+  @PropertyName(CLIENT_ID)
+  void setClientId(int value);
+
+  @PropertyName(SERVER_ID)
+  void setServerId(String value);
+
+  @PropertyName(TYPE_TOKEN)
+  void setTypeToken(String value);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
new file mode 100644
index 0000000..de8c906
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
@@ -0,0 +1,77 @@
+/*
+ * 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.messages;
+
+/**
+ * Common functions for slicing and dicing EntityProxy ids.
+ */
+public class IdUtil {
+  private static final String ANY_SEPARATOR_PATTERN = "@[01]@";
+  private static final String EPHEMERAL_SEPARATOR = "@1@";
+  private static final String TOKEN_SEPARATOR = "@0@";
+  private static final int ID_TOKEN_INDEX = 0;
+  private static final int TYPE_TOKEN_INDEX = 1;
+
+  public static String ephemeralId(int clientId, String typeToken) {
+    return clientId + EPHEMERAL_SEPARATOR + typeToken;
+  }
+
+  public static int getClientId(String encodedId) {
+    return Integer.valueOf(asEphemeral(encodedId)[ID_TOKEN_INDEX]);
+  }
+
+  public static String getServerId(String encodedId) {
+    return asPersisted(encodedId)[ID_TOKEN_INDEX];
+  }
+
+  public static String getTypeToken(String encodedId) {
+    String[] split = asAny(encodedId);
+    if (split.length == 2) {
+      return split[TYPE_TOKEN_INDEX];
+    }
+    return null;
+  }
+
+  public static boolean isEphemeral(String encodedId) {
+    return encodedId.contains(EPHEMERAL_SEPARATOR);
+  }
+
+  public static boolean isPersisted(String encodedId) {
+    return encodedId.contains(TOKEN_SEPARATOR);
+  }
+
+  public static String persistedId(String serverId, String typeToken) {
+    return serverId + TOKEN_SEPARATOR + typeToken;
+  }
+
+  private static String[] asAny(String encodedId) {
+    return encodedId.split(ANY_SEPARATOR_PATTERN);
+  }
+
+  private static String[] asEphemeral(String encodedId) {
+    return encodedId.split(EPHEMERAL_SEPARATOR);
+  }
+
+  private static String[] asPersisted(String encodedId) {
+    return encodedId.split(TOKEN_SEPARATOR);
+  }
+
+  /**
+   * Utility class
+   */
+  private IdUtil() {
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
new file mode 100644
index 0000000..0bd07e2
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
@@ -0,0 +1,49 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.gwt.autobean.shared.Splittable;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Describes a method invocation.
+ */
+public interface InvocationMessage {
+  String OPERATIONS = "O";
+  String PARAMETERS = "P";
+  String PROPERTY_REFS = "R";
+
+  @PropertyName(OPERATIONS)
+  String getOperation();
+
+  @PropertyName(PARAMETERS)
+  List<Splittable> getParameters();
+
+  @PropertyName(PROPERTY_REFS)
+  Set<String> getPropertyRefs();
+
+  @PropertyName(OPERATIONS)
+  void setOperation(String value);
+
+  @PropertyName(PARAMETERS)
+  void setParameters(List<Splittable> value);
+
+  @PropertyName(PROPERTY_REFS)
+  void setPropertyRefs(Set<String> value);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
new file mode 100644
index 0000000..cde822c
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+
+/**
+ * The factory for creating RequestFactory wire messages.
+ */
+public interface MessageFactory extends AutoBeanFactory {
+  AutoBean<InvocationMessage> invocation();
+
+  AutoBean<ServerFailureMessage> failure();
+
+  AutoBean<OperationMessage> operation();
+
+  AutoBean<RequestMessage> request();
+
+  AutoBean<ResponseMessage> response();
+
+  AutoBean<ViolationMessage> violation();
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
new file mode 100644
index 0000000..c57c934
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
@@ -0,0 +1,42 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.gwt.requestfactory.shared.WriteOperation;
+
+import java.util.Map;
+
+/**
+ * Represents an operation to be carried out on a single entity on the server.
+ */
+public interface OperationMessage extends IdMessage, VersionedMessage {
+  String OPERATION = "O";
+  String PROPERTY_MAP = "P";
+
+  @PropertyName(OPERATION)
+  WriteOperation getOperation();
+
+  @PropertyName(PROPERTY_MAP)
+  Map<String, Splittable> getPropertyMap();
+
+  @PropertyName(OPERATION)
+  void setOperation(WriteOperation value);
+
+  @PropertyName(PROPERTY_MAP)
+  void setPropertyMap(Map<String, Splittable> map);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
new file mode 100644
index 0000000..660f50c
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.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.shared.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+
+import java.util.List;
+
+/**
+ * The message sent from the client to the server.
+ */
+public interface RequestMessage extends VersionedMessage {
+  String INVOCATION = "I";
+  String OPERATIONS = "O";
+
+  @PropertyName(INVOCATION)
+  List<InvocationMessage> getInvocations();
+
+  @PropertyName(OPERATIONS)
+  List<OperationMessage> getOperations();
+
+  @PropertyName(INVOCATION)
+  void setInvocations(List<InvocationMessage> value);
+
+  @PropertyName(OPERATIONS)
+  void setOperations(List<OperationMessage> value);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
new file mode 100644
index 0000000..e4135c1
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
@@ -0,0 +1,63 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+import com.google.gwt.autobean.shared.Splittable;
+
+import java.util.List;
+
+/**
+ * The result of fulfilling a request on the server;
+ */
+public interface ResponseMessage extends VersionedMessage {
+  String GENERAL_FAILURE = "F";
+  String INVOCATION_RESULTS = "I";
+  String OPERATIONS = "O";
+  String STATUS_CODES = "S";
+  // V would conflict with versionedMessage
+  String VIOLATIONS = "X";
+
+  @PropertyName(GENERAL_FAILURE)
+  ServerFailureMessage getGeneralFailure();
+
+  @PropertyName(INVOCATION_RESULTS)
+  List<Splittable> getInvocationResults();
+
+  @PropertyName(OPERATIONS)
+  List<OperationMessage> getOperations();
+
+  @PropertyName(STATUS_CODES)
+  List<Boolean> getStatusCodes();
+
+  @PropertyName(VIOLATIONS)
+  List<ViolationMessage> getViolations();
+
+  @PropertyName(GENERAL_FAILURE)
+  void setGeneralFailure(ServerFailureMessage failure);
+
+  @PropertyName(INVOCATION_RESULTS)
+  void setInvocationResults(List<Splittable> value);
+
+  @PropertyName(OPERATIONS)
+  void setOperations(List<OperationMessage> value);
+
+  @PropertyName(STATUS_CODES)
+  void setStatusCodes(List<Boolean> value);
+
+  @PropertyName(VIOLATIONS)
+  void setViolations(List<ViolationMessage> value);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
new file mode 100644
index 0000000..f196adf
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
@@ -0,0 +1,45 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Encapsulates a ServerFailure object.
+ */
+public interface ServerFailureMessage {
+  String EXCEPTION_TYPE = "X";
+  String MESSAGE = "M";
+  String STACK_TRACE = "S";
+
+  @PropertyName(EXCEPTION_TYPE)
+  String getExceptionType();
+
+  @PropertyName(MESSAGE)
+  String getMessage();
+
+  @PropertyName(STACK_TRACE)
+  String getStackTrace();
+
+  @PropertyName(EXCEPTION_TYPE)
+  void setExceptionType(String exceptionType);
+
+  @PropertyName(MESSAGE)
+  void setMessage(String message);
+
+  @PropertyName(STACK_TRACE)
+  void setStackTrace(String stackTrace);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Version.java b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
similarity index 62%
copy from user/src/com/google/gwt/requestfactory/shared/Version.java
copy to user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
index 5e66314..6563ec1 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Version.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
@@ -1,23 +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.
  */
-package com.google.gwt.requestfactory.shared;
+package com.google.gwt.requestfactory.shared.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
 
 /**
- * Marks the version property of an entity.
+ * Describes a message that contains version information.
  */
-public @interface Version {
-  // TODO prove the servlet will use this info
+public interface VersionedMessage {
+  String VERSION = "V";
+
+  @PropertyName(VERSION)
+  int getVersion();
+
+  @PropertyName(VERSION)
+  void setVersion(int version);
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
new file mode 100644
index 0000000..ea57a59
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
@@ -0,0 +1,38 @@
+/*
+ * 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.messages;
+
+import com.google.gwt.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Represents a ConstraintViolation
+ */
+public interface ViolationMessage extends IdMessage {
+  String MESSAGE = "M";
+  String PATH = "P";
+
+  @PropertyName(MESSAGE)
+  String getMessage();
+
+  @PropertyName(PATH)
+  String getPath();
+
+  @PropertyName(MESSAGE)
+  void setMessage(String value);
+
+  @PropertyName(PATH)
+  void setPath(String value);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Version.java b/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
similarity index 74%
copy from user/src/com/google/gwt/requestfactory/shared/Version.java
copy to user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
index 5e66314..b396dfc 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Version.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
@@ -1,23 +1,24 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.shared;
 
 /**
- * Marks the version property of an entity.
+ * Contains classes that define the RequestFactory wire format.
+ * 
+ * @since GWT 2.1.1
  */
-public @interface Version {
-  // TODO prove the servlet will use this info
-}
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.requestfactory.shared.messages;
+
diff --git a/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.java b/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.java
new file mode 100644
index 0000000..7346bec
--- /dev/null
+++ b/user/super/com/google/gwt/autobean/super/com/google/gwt/autobean/shared/impl/StringQuoter.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.autobean.shared.impl;
+
+import com.google.gwt.core.client.JsonUtils;
+import com.google.gwt.autobean.client.impl.JsoSplittable;
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
+
+/**
+ * This a super-source version with a client-only implementation.
+ */
+public class StringQuoter {
+  public static String quote(String raw) {
+    return JsonUtils.escapeValue(raw);
+  }
+
+  public static Splittable split(String payload) {
+    return JsoSplittable.create(JsonUtils.safeEval(payload));
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Version.java b/user/super/com/google/gwt/requestfactory/super/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
similarity index 63%
copy from user/src/com/google/gwt/requestfactory/shared/Version.java
copy to user/super/com/google/gwt/requestfactory/super/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
index 5e66314..3e5d3ec 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Version.java
+++ b/user/super/com/google/gwt/requestfactory/super/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -1,23 +1,26 @@
 /*
  * 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;
+package com.google.gwt.requestfactory.shared.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.requestfactory.shared.messages.MessageFactory;
 
 /**
- * Marks the version property of an entity.
+ * This a super-source version with a client-only implementation.
  */
-public @interface Version {
-  // TODO prove the servlet will use this info
+interface MessageFactoryHolder {
+  MessageFactory FACTORY = GWT.create(MessageFactory.class);
 }
diff --git a/user/test/com/google/gwt/autobean/AutoBeanSuite.java b/user/test/com/google/gwt/autobean/AutoBeanSuite.java
new file mode 100644
index 0000000..491e91c
--- /dev/null
+++ b/user/test/com/google/gwt/autobean/AutoBeanSuite.java
@@ -0,0 +1,39 @@
+/*
+ * 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.autobean;
+
+import com.google.gwt.autobean.client.AutoBeanTest;
+import com.google.gwt.autobean.server.AutoBeanCodexJreTest;
+import com.google.gwt.autobean.server.AutoBeanJreTest;
+import com.google.gwt.autobean.shared.AutoBeanCodexTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests of the Editor framework. These tests focus on core Editor behaviors,
+ * rather than on integration with backing stores.
+ */
+public class AutoBeanSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("Test suite for AutoBean functions");
+    suite.addTestSuite(AutoBeanCodexJreTest.class);
+    suite.addTestSuite(AutoBeanCodexTest.class);
+    suite.addTestSuite(AutoBeanJreTest.class);
+    suite.addTestSuite(AutoBeanTest.class);
+    return suite;
+  }
+}
diff --git a/user/test/com/google/gwt/editor/client/AutoBeanTest.java b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
similarity index 90%
rename from user/test/com/google/gwt/editor/client/AutoBeanTest.java
rename to user/test/com/google/gwt/autobean/client/AutoBeanTest.java
index 048e8a5..5fa00ce 100644
--- a/user/test/com/google/gwt/editor/client/AutoBeanTest.java
+++ b/user/test/com/google/gwt/autobean/client/AutoBeanTest.java
@@ -13,10 +13,14 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.editor.client;
+package com.google.gwt.autobean.client;
 
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanFactory;
+import com.google.gwt.autobean.shared.AutoBeanFactory.Category;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.editor.client.AutoBeanFactory.Category;
 import com.google.gwt.junit.client.GWTTestCase;
 
 import java.util.ArrayList;
@@ -45,8 +49,11 @@
     }
   }
 
+  /**
+   * The factory being tested.
+   */
   @Category(CallImpl.class)
-  interface Factory extends AutoBeanFactory {
+  protected interface Factory extends AutoBeanFactory {
     AutoBean<HasCall> hasCall();
 
     AutoBean<HasList> hasList();
@@ -115,16 +122,20 @@
     public void setString(String value) {
       this.string = value;
     }
+
+    public String toString() {
+      return "toString";
+    }
   }
 
   interface UnreferencedInFactory {
   }
 
-  private Factory factory;
+  protected Factory factory;
 
   @Override
   public String getModuleName() {
-    return "com.google.gwt.editor.Editor";
+    return "com.google.gwt.autobean.AutoBean";
   }
 
   public void testCategory() {
@@ -251,9 +262,10 @@
     AutoBean<Intf> w = factory.intf(real);
     // AutoBean interface never equals wrapped object
     assertFalse(w.equals(real));
-    // Wrapper interface should delegate hashCode() and equals()
+    // Wrapper interface should delegate hashCode(), equals(), and toString()
     assertEquals(real.hashCode(), w.as().hashCode());
     assertEquals(real, w.as());
+    assertEquals(real.toString(), w.as().toString());
     assertEquals(w.as(), real);
   }
 
@@ -323,8 +335,10 @@
           AutoBean<?> value, PropertyContext ctx) {
         if ("intf".equals(propertyName)) {
           assertSame(intf, value);
+          assertEquals(Intf.class, ctx.getType());
         } else if ("unreferenced".equals(propertyName)) {
           assertNull(value);
+          assertEquals(UnreferencedInFactory.class, ctx.getType());
         } else {
           fail("Unexpecetd property " + propertyName);
         }
@@ -335,8 +349,10 @@
           PropertyContext ctx) {
         if ("int".equals(propertyName)) {
           assertEquals(42, value);
+          assertEquals(int.class, ctx.getType());
         } else if ("string".equals(propertyName)) {
           assertNull(value);
+          assertEquals(String.class, ctx.getType());
         } else {
           fail("Unknown value property " + propertyName);
         }
@@ -364,6 +380,10 @@
     c.check();
   }
 
+  public void testType() {
+    assertEquals(Intf.class, factory.intf().getType());
+  }
+
   /**
    * Ensure that a totally automatic bean can't be unwrapped, since the
    * generated mapper depends on the AutoBean.
diff --git a/user/test/com/google/gwt/autobean/server/AutoBeanCodexJreTest.java b/user/test/com/google/gwt/autobean/server/AutoBeanCodexJreTest.java
new file mode 100644
index 0000000..940331f
--- /dev/null
+++ b/user/test/com/google/gwt/autobean/server/AutoBeanCodexJreTest.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.autobean.server;
+
+import com.google.gwt.autobean.shared.AutoBeanCodexTest;
+
+/**
+ * Runs AutoBeanCodexTest in pure-JRE mode.
+ */
+public class AutoBeanCodexJreTest extends AutoBeanCodexTest {
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    f = AutoBeanFactoryMagic.create(Factory.class);
+  }
+
+}
diff --git a/user/test/com/google/gwt/autobean/server/AutoBeanJreTest.java b/user/test/com/google/gwt/autobean/server/AutoBeanJreTest.java
new file mode 100644
index 0000000..2f35a1a
--- /dev/null
+++ b/user/test/com/google/gwt/autobean/server/AutoBeanJreTest.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.autobean.server;
+
+import com.google.gwt.autobean.client.AutoBeanTest;
+
+/**
+ * Runs the AutoBeanTests against the JRE implementation.
+ */
+public class AutoBeanJreTest extends AutoBeanTest {
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    factory = AutoBeanFactoryMagic.create(Factory.class);
+  }
+}
diff --git a/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
new file mode 100644
index 0000000..3d110a5
--- /dev/null
+++ b/user/test/com/google/gwt/autobean/shared/AutoBeanCodexTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.autobean.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Simple encoding / decoding tests for the AutoBeanCodex.
+ */
+public class AutoBeanCodexTest extends GWTTestCase {
+  /**
+   * Protected so that the JRE-only test can instantiate instances.
+   */
+  protected interface Factory extends AutoBeanFactory {
+    AutoBean<HasAutoBean> hasAutoBean();
+
+    /**
+     * @return
+     */
+    AutoBean<HasCycle> hasCycle();
+
+    AutoBean<HasList> hasList();
+
+    /**
+     * @return
+     */
+    AutoBean<HasMap> hasMap();
+
+    AutoBean<HasSimple> hasSimple();
+
+    AutoBean<Simple> simple();
+  }
+
+  interface HasAutoBean {
+    Splittable getSimple();
+
+    List<Splittable> getSimpleList();
+
+    Splittable getString();
+
+    void setSimple(Splittable simple);
+
+    void setSimpleList(List<Splittable> simple);
+
+    void setString(Splittable s);
+  }
+
+  /**
+   * Used to test that cycles are detected.
+   */
+  interface HasCycle {
+    List<HasCycle> getCycle();
+
+    void setCycle(List<HasCycle> cycle);
+  }
+
+  interface HasList {
+    List<Integer> getIntList();
+
+    List<Simple> getList();
+
+    void setIntList(List<Integer> list);
+
+    void setList(List<Simple> list);
+  }
+
+  interface HasMap {
+    Map<Simple, Simple> getComplexMap();
+
+    Map<String, Simple> getSimpleMap();
+
+    void setComplexMap(Map<Simple, Simple> map);
+
+    void setSimpleMap(Map<String, Simple> map);
+  }
+
+  interface HasSimple {
+    Simple getSimple();
+
+    void setSimple(Simple s);
+  }
+
+  interface Simple {
+    int getInt();
+
+    String getString();
+
+    void setInt(int i);
+
+    void setString(String s);
+  }
+
+  protected Factory f;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.autobean.AutoBean";
+  }
+
+  public void testCycle() {
+    AutoBean<HasCycle> bean = f.hasCycle();
+    bean.as().setCycle(Arrays.asList(bean.as()));
+    try {
+      AutoBeanCodex.encode(bean);
+      fail("Should not have encoded");
+    } catch (UnsupportedOperationException expected) {
+    }
+  }
+
+  public void testEmptyList() {
+    AutoBean<HasList> bean = f.hasList();
+    bean.as().setList(Collections.<Simple> emptyList());
+    Splittable split = AutoBeanCodex.encode(bean);
+    AutoBean<HasList> decodedBean = AutoBeanCodex.decode(f, HasList.class,
+        split);
+    assertNotNull(decodedBean.as().getList());
+    assertTrue(decodedBean.as().getList().isEmpty());
+  }
+
+  public void testMap() {
+    AutoBean<HasMap> bean = f.hasMap();
+    Map<String, Simple> map = new HashMap<String, Simple>();
+    Map<Simple, Simple> complex = new HashMap<Simple, Simple>();
+    bean.as().setSimpleMap(map);
+    bean.as().setComplexMap(complex);
+
+    for (int i = 0, j = 5; i < j; i++) {
+      Simple s = f.simple().as();
+      s.setInt(i);
+      map.put(String.valueOf(i), s);
+
+      Simple key = f.simple().as();
+      key.setString(String.valueOf(i));
+      complex.put(key, s);
+    }
+
+    Splittable split = AutoBeanCodex.encode(bean);
+    AutoBean<HasMap> decoded = AutoBeanCodex.decode(f, HasMap.class, split);
+    map = decoded.as().getSimpleMap();
+    complex = decoded.as().getComplexMap();
+    assertEquals(5, map.size());
+    for (int i = 0, j = 5; i < j; i++) {
+      Simple s = map.get(String.valueOf(i));
+      assertNotNull(s);
+      assertEquals(i, s.getInt());
+    }
+    assertEquals(5, complex.size());
+    for (Map.Entry<Simple, Simple> entry : complex.entrySet()) {
+      assertEquals(entry.getKey().getString(),
+          String.valueOf(entry.getValue().getInt()));
+    }
+  }
+
+  public void testNull() {
+    AutoBean<Simple> bean = f.simple();
+    Splittable split = AutoBeanCodex.encode(bean);
+    AutoBean<Simple> decodedBean = AutoBeanCodex.decode(f, Simple.class, split);
+    assertNull(decodedBean.as().getString());
+  }
+
+  public void testSimple() {
+    AutoBean<Simple> bean = f.simple();
+    Simple simple = bean.as();
+    simple.setInt(42);
+    simple.setString("Hello World!");
+
+    Splittable split = AutoBeanCodex.encode(bean);
+
+    AutoBean<Simple> decodedBean = AutoBeanCodex.decode(f, Simple.class, split);
+    assertTrue(AutoBeanUtils.diff(bean, decodedBean).isEmpty());
+
+    AutoBean<HasSimple> bean2 = f.hasSimple();
+    bean2.as().setSimple(simple);
+    split = AutoBeanCodex.encode(bean2);
+
+    AutoBean<HasSimple> decodedBean2 = AutoBeanCodex.decode(f, HasSimple.class,
+        split);
+    assertNotNull(decodedBean2.as().getSimple());
+    assertTrue(AutoBeanUtils.diff(bean,
+        AutoBeanUtils.getAutoBean(decodedBean2.as().getSimple())).isEmpty());
+
+    AutoBean<HasList> bean3 = f.hasList();
+    bean3.as().setIntList(Arrays.asList(1, 2, 3, null, 4, 5));
+    bean3.as().setList(Arrays.asList(simple));
+    split = AutoBeanCodex.encode(bean3);
+
+    AutoBean<HasList> decodedBean3 = AutoBeanCodex.decode(f, HasList.class,
+        split);
+    assertNotNull(decodedBean3.as().getIntList());
+    assertEquals(Arrays.asList(1, 2, 3, null, 4, 5),
+        decodedBean3.as().getIntList());
+    assertNotNull(decodedBean3.as().getList());
+    assertEquals(1, decodedBean3.as().getList().size());
+    assertTrue(AutoBeanUtils.diff(bean,
+        AutoBeanUtils.getAutoBean(decodedBean3.as().getList().get(0))).isEmpty());
+  }
+
+  public void testSplittable() {
+    AutoBean<Simple> simple = f.simple();
+    simple.as().setString("Simple");
+    AutoBean<HasAutoBean> bean = f.hasAutoBean();
+    bean.as().setSimple(AutoBeanCodex.encode(simple));
+    bean.as().setString(ValueCodex.encode("Hello ['\"] world"));
+    List<Splittable> testList = Arrays.asList(AutoBeanCodex.encode(simple),
+        null, AutoBeanCodex.encode(simple));
+    bean.as().setSimpleList(testList);
+    Splittable split = AutoBeanCodex.encode(bean);
+
+    AutoBean<HasAutoBean> decoded = AutoBeanCodex.decode(f, HasAutoBean.class,
+        split);
+    Splittable toDecode = decoded.as().getSimple();
+    AutoBean<Simple> decodedSimple = AutoBeanCodex.decode(f, Simple.class,
+        toDecode);
+    assertEquals("Simple", decodedSimple.as().getString());
+    assertEquals("Hello ['\"] world",
+        ValueCodex.decode(String.class, decoded.as().getString()));
+
+    List<Splittable> list = decoded.as().getSimpleList();
+    assertEquals(3, list.size());
+    assertNull(list.get(1));
+    assertEquals("Simple",
+        AutoBeanCodex.decode(f, Simple.class, list.get(2)).as().getString());
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    f = GWT.create(Factory.class);
+  }
+}
diff --git a/user/test/com/google/gwt/editor/EditorSuite.java b/user/test/com/google/gwt/editor/EditorSuite.java
index aab8247..e69456e 100644
--- a/user/test/com/google/gwt/editor/EditorSuite.java
+++ b/user/test/com/google/gwt/editor/EditorSuite.java
@@ -15,7 +15,8 @@
  */
 package com.google.gwt.editor;
 
-import com.google.gwt.editor.client.AutoBeanTest;
+import com.google.gwt.autobean.client.AutoBeanTest;
+import com.google.gwt.autobean.server.AutoBeanJreTest;
 import com.google.gwt.editor.client.EditorErrorTest;
 import com.google.gwt.editor.client.SimpleBeanEditorTest;
 import com.google.gwt.editor.client.adapters.ListEditorWrapperTest;
@@ -33,6 +34,7 @@
   public static Test suite() {
     GWTTestSuite suite = new GWTTestSuite(
         "Test suite for core Editor functions");
+    suite.addTestSuite(AutoBeanJreTest.class);
     suite.addTestSuite(AutoBeanTest.class);
     suite.addTestSuite(DelegateMapTest.class);
     suite.addTestSuite(EditorModelTest.class);
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
index 3aa7f1b..19ea814 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -15,12 +15,11 @@
  */
 package com.google.gwt.requestfactory;
 
-import com.google.gwt.requestfactory.client.impl.SimpleEntityProxyIdTest;
 import com.google.gwt.requestfactory.rebind.model.RequestFactoryModelTest;
-import com.google.gwt.requestfactory.server.JsonRequestProcessorTest;
-import com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistryTest;
+import com.google.gwt.requestfactory.server.FindServiceJreTest;
 import com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest;
-import com.google.gwt.requestfactory.server.RequestPropertyTest;
+import com.google.gwt.requestfactory.server.RequestFactoryJreTest;
+import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyIdTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -32,12 +31,11 @@
   public static Test suite() {
     TestSuite suite = new TestSuite(
         "requestfactory package tests that require the JRE");
+    suite.addTestSuite(FindServiceJreTest.class);
+    suite.addTestSuite(RequestFactoryJreTest.class);
     suite.addTestSuite(SimpleEntityProxyIdTest.class);
-    suite.addTestSuite(JsonRequestProcessorTest.class);
-    suite.addTestSuite(ReflectionBasedOperationRegistryTest.class);
     suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
     suite.addTestSuite(RequestFactoryModelTest.class);
-    suite.addTestSuite(RequestPropertyTest.class);
     return suite;
   }
 }
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
index d4ec7e4..2efef63 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
@@ -19,7 +19,6 @@
 import com.google.gwt.requestfactory.client.FindServiceTest;
 import com.google.gwt.requestfactory.client.RequestFactoryExceptionHandlerTest;
 import com.google.gwt.requestfactory.client.RequestFactoryPolymorphicTest;
-import com.google.gwt.requestfactory.client.RequestFactoryStringTest;
 import com.google.gwt.requestfactory.client.RequestFactoryTest;
 import com.google.gwt.requestfactory.client.ui.EditorTest;
 
@@ -34,7 +33,6 @@
         "Test suite for requestfactory gwt code.");
     suite.addTestSuite(EditorTest.class);
     suite.addTestSuite(RequestFactoryTest.class);
-    suite.addTestSuite(RequestFactoryStringTest.class);
     suite.addTestSuite(RequestFactoryExceptionHandlerTest.class);
     suite.addTestSuite(RequestFactoryPolymorphicTest.class);
     suite.addTestSuite(FindServiceTest.class);
diff --git a/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java b/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java
index 95b31dc..6285434 100644
--- a/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java
@@ -15,9 +15,7 @@
  */
 package com.google.gwt.requestfactory.client;
 
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.SimpleEventBus;
 import com.google.gwt.requestfactory.shared.EntityProxyChange;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.Receiver;
@@ -150,8 +148,7 @@
 
     // Here's the factory from the "previous invocation" of the client
     {
-      SimpleRequestFactory oldFactory = GWT.create(SimpleRequestFactory.class);
-      oldFactory.initialize(new SimpleEventBus());
+      SimpleRequestFactory oldFactory = createFactory();
       EntityProxyId<SimpleBarProxy> id = oldFactory.simpleBarRequest().create(
           SimpleBarProxy.class).stableId();
       historyToken = oldFactory.getHistoryToken(id);
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryStringTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryStringTest.java
deleted file mode 100644
index 4f7c75f..0000000
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryStringTest.java
+++ /dev/null
@@ -1,770 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client;
-
-import com.google.gwt.requestfactory.shared.EntityProxyChange;
-import com.google.gwt.requestfactory.shared.Receiver;
-import com.google.gwt.requestfactory.shared.Request;
-import com.google.gwt.requestfactory.shared.RequestContext;
-import com.google.gwt.requestfactory.shared.ServerFailure;
-import com.google.gwt.requestfactory.shared.SimpleBarProxy;
-import com.google.gwt.requestfactory.shared.SimpleBarRequest;
-import com.google.gwt.requestfactory.shared.SimpleFooStringProxy;
-import com.google.gwt.requestfactory.shared.SimpleFooStringRequest;
-import com.google.gwt.requestfactory.shared.Violation;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests for {@link com.google.gwt.requestfactory.shared.RequestFactory}.
- */
-public class RequestFactoryStringTest extends RequestFactoryTestBase {
-  /*
-   * DO NOT USE finishTest(). Instead, call finishTestAndReset();
-   */
-
-  private class FailFixAndRefire<T> extends Receiver<T> {
-
-    private final SimpleFooStringProxy proxy;
-    private final Request<T> request;
-    private boolean voidReturnExpected;
-
-    FailFixAndRefire(SimpleFooStringProxy proxy, RequestContext context,
-        Request<T> request) {
-      this.proxy = context.edit(proxy);
-      this.request = request;
-    }
-
-    @Override
-    public void onSuccess(T response) {
-      /*
-       * Make sure your class path includes:
-       * 
-       * tools/lib/apache/log4j/log4j-1.2.16.jar
-       * tools/lib/hibernate/validator/hibernate-validator-4.1.0.Final.jar
-       * tools/lib/slf4j/slf4j-api/slf4j-api-1.6.1.jar
-       * tools/lib/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
-       */
-      fail("Violations expected (you might be missing some jars, "
-          + "see the comment above this line)");
-    }
-
-    @Override
-    public void onViolation(Set<Violation> errors) {
-
-      // size violation expected
-
-      assertEquals(1, errors.size());
-      Violation error = errors.iterator().next();
-      assertEquals("userName", error.getPath());
-      assertEquals("size must be between 3 and 30", error.getMessage());
-      assertEquals(proxy.stableId(), error.getProxyId());
-
-      // Now re-used the request to fix the edit
-
-      proxy.setUserName("long enough");
-      request.fire(new Receiver<T>() {
-        @Override
-        public void onSuccess(T response) {
-          if (voidReturnExpected) {
-            assertNull(response);
-          } else {
-            assertEquals(proxy.stableId(),
-                ((SimpleFooStringProxy) response).stableId());
-          }
-          finishTestAndReset();
-        }
-      });
-    }
-
-    void doVoidTest() {
-      voidReturnExpected = true;
-      doTest();
-    }
-
-    void doTest() {
-      proxy.setUserName("a"); // too short
-      request.fire(this);
-    }
-  }
-
-  @Override
-  public String getModuleName() {
-    return "com.google.gwt.requestfactory.RequestFactorySuite";
-  }
-
-  public void testDummyCreate() {
-    delayTestFinish(5000);
-
-    final SimpleFooEventHandler<SimpleFooStringProxy> handler = new SimpleFooEventHandler<SimpleFooStringProxy>();
-    EntityProxyChange.registerForProxyType(req.getEventBus(),
-        SimpleFooStringProxy.class, handler);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    final SimpleFooStringProxy foo = context.create(SimpleFooStringProxy.class);
-    Object futureId = foo.getId();
-    assertEquals(futureId, foo.getId());
-    Request<SimpleFooStringProxy> fooReq = context.persistAndReturnSelf().using(
-        foo);
-    fooReq.fire(new Receiver<SimpleFooStringProxy>() {
-
-      @Override
-      public void onSuccess(final SimpleFooStringProxy returned) {
-        Object futureId = foo.getId();
-        assertEquals(futureId, foo.getId());
-
-        /*
-         * Two events are fired: (i) PERSIST event fired from
-         * DeltaValueStoreJsonImpl because the proxy was persisted, and (ii)
-         * UPDATE event fired from ValueStoreJsonImpl because the new proxy was
-         * part of the return value.
-         */
-        assertEquals(1, handler.persistEventCount);
-        assertEquals(1, handler.updateEventCount);
-        assertEquals(2, handler.totalEventCount);
-
-        checkStableIdEquals(foo, returned);
-        finishTestAndReset();
-      }
-    });
-  }
-
-  public void testDummyCreateBar() {
-    delayTestFinish(5000);
-
-    SimpleBarRequest context = req.simpleBarRequest();
-    final SimpleBarProxy foo = context.create(SimpleBarProxy.class);
-    Request<SimpleBarProxy> fooReq = context.persistAndReturnSelf().using(foo);
-    fooReq.fire(new Receiver<SimpleBarProxy>() {
-
-      @Override
-      public void onSuccess(final SimpleBarProxy returned) {
-        checkStableIdEquals(foo, returned);
-        finishTestAndReset();
-      }
-    });
-  }
-
-  public void testFindFindEdit() {
-    delayTestFinish(5000);
-
-    final SimpleFooEventHandler<SimpleFooStringProxy> handler = new SimpleFooEventHandler<SimpleFooStringProxy>();
-    EntityProxyChange.registerForProxyType(req.getEventBus(),
-        SimpleFooStringProxy.class, handler);
-
-    req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-        new Receiver<SimpleFooStringProxy>() {
-
-          @Override
-          public void onSuccess(SimpleFooStringProxy newFoo) {
-            assertEquals(1, handler.updateEventCount);
-            assertEquals(1, handler.totalEventCount);
-
-            req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-                new Receiver<SimpleFooStringProxy>() {
-
-                  @Override
-                  public void onSuccess(SimpleFooStringProxy newFoo) {
-                    // no events are fired second time.
-                    assertEquals(1, handler.updateEventCount);
-                    assertEquals(1, handler.totalEventCount);
-                    SimpleFooStringRequest context = req.simpleFooStringRequest();
-                    final Request<Void> mutateRequest = context.persist().using(
-                        newFoo);
-                    newFoo = context.edit(newFoo);
-                    newFoo.setUserName("Ray");
-                    mutateRequest.fire(new Receiver<Void>() {
-                      @Override
-                      public void onSuccess(Void response) {
-                        // events fired on updates.
-                        assertEquals(2, handler.updateEventCount);
-                        assertEquals(2, handler.totalEventCount);
-
-                        finishTestAndReset();
-                      }
-                    });
-                  }
-                });
-          }
-        });
-  }
-
-  public void testFetchEntity() {
-    delayTestFinish(5000);
-    req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-        new Receiver<SimpleFooStringProxy>() {
-          @Override
-          public void onSuccess(SimpleFooStringProxy response) {
-            assertEquals(42, (int) response.getIntId());
-            assertEquals("GWT", response.getUserName());
-            assertEquals(8L, (long) response.getLongField());
-            assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.FOO,
-                response.getEnumField());
-            assertEquals(null, response.getBarField());
-            finishTestAndReset();
-          }
-        });
-  }
-
-  public void testFetchEntityWithRelation() {
-    delayTestFinish(5000);
-    req.simpleFooStringRequest().findSimpleFooStringById("999x").with(
-        "barField").fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onSuccess(SimpleFooStringProxy response) {
-        assertEquals(42, (int) response.getIntId());
-        assertEquals("GWT", response.getUserName());
-        assertEquals(8L, (long) response.getLongField());
-        assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.FOO,
-            response.getEnumField());
-        assertNotNull(response.getBarField());
-        finishTestAndReset();
-      }
-    });
-  }
-
-  public void testGetEventBus() {
-    assertEquals(eventBus, req.getEventBus());
-  }
-
-  public void testGetListStringId() {
-    delayTestFinish(5000);
-
-    // String ids
-    req.simpleBarRequest().findAll().fire(new Receiver<List<SimpleBarProxy>>() {
-      @Override
-      public void onSuccess(List<SimpleBarProxy> response) {
-        assertEquals(2, response.size());
-        for (SimpleBarProxy bar : response) {
-          assertNotNull(bar.stableId());
-          finishTestAndReset();
-        }
-      }
-    });
-  }
-
-  public void testGetListLongId() {
-    delayTestFinish(5000);
-
-    // Long ids
-    req.simpleFooStringRequest().findAll().with("barField.userName").fire(
-        new Receiver<List<SimpleFooStringProxy>>() {
-          @Override
-          public void onSuccess(List<SimpleFooStringProxy> response) {
-            assertEquals(1, response.size());
-            for (SimpleFooStringProxy foo : response) {
-              assertNotNull(foo.stableId());
-              assertEquals("FOO", foo.getBarField().getUserName());
-              finishTestAndReset();
-            }
-          }
-        });
-  }
-
-  /*
-   * tests that (a) any method can have a side effect that is handled correctly.
-   * (b) instance methods are handled correctly and (c) a request cannot be
-   * reused after a successful response is received. (Yet?)
-   */
-  public void testMethodWithSideEffects() {
-    delayTestFinish(5000);
-
-    final SimpleFooEventHandler<SimpleFooStringProxy> handler = new SimpleFooEventHandler<SimpleFooStringProxy>();
-    EntityProxyChange.registerForProxyType(req.getEventBus(),
-        SimpleFooStringProxy.class, handler);
-
-    req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-        new Receiver<SimpleFooStringProxy>() {
-
-          @Override
-          public void onSuccess(SimpleFooStringProxy newFoo) {
-            assertEquals(1, handler.updateEventCount);
-            assertEquals(1, handler.totalEventCount);
-            SimpleFooStringRequest context = req.simpleFooStringRequest();
-            final Request<Long> mutateRequest = context.countSimpleFooWithUserNameSideEffect().using(
-                newFoo);
-            newFoo = context.edit(newFoo);
-            newFoo.setUserName("Ray");
-            mutateRequest.fire(new Receiver<Long>() {
-              @Override
-              public void onSuccess(Long response) {
-                assertCannotFire(mutateRequest);
-                assertEquals(new Long(1L), response);
-                assertEquals(2, handler.updateEventCount);
-                assertEquals(2, handler.totalEventCount);
-
-                // confirm that the instance method did have the desired
-                // sideEffect.
-                req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-                    new Receiver<SimpleFooStringProxy>() {
-                      @Override
-                      public void onSuccess(SimpleFooStringProxy finalFoo) {
-                        assertEquals("Ray", finalFoo.getUserName());
-                        assertEquals(2, handler.updateEventCount);
-                        assertEquals(2, handler.totalEventCount);
-                        finishTestAndReset();
-                      }
-                    });
-              }
-
-            });
-
-            try {
-              newFoo.setUserName("Barney");
-              fail();
-            } catch (IllegalStateException e) {
-              /* pass, cannot change a request that is in flight */
-            }
-          }
-        });
-  }
-
-  /*
-   * TODO: all these tests should check the final values. It will be easy when
-   * we have better persistence than the singleton pattern.
-   */
-  public void testPersistExistingEntityExistingRelation() {
-    delayTestFinish(5000);
-
-    req.simpleBarRequest().findSimpleBarById("999L").fire(
-        new Receiver<SimpleBarProxy>() {
-          @Override
-          public void onSuccess(final SimpleBarProxy barProxy) {
-            req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-                new Receiver<SimpleFooStringProxy>() {
-                  @Override
-                  public void onSuccess(SimpleFooStringProxy fooProxy) {
-                    SimpleFooStringRequest context = req.simpleFooStringRequest();
-                    Request<Void> updReq = context.persist().using(fooProxy);
-                    fooProxy = context.edit(fooProxy);
-                    fooProxy.setBarField(barProxy);
-                    updReq.fire(new Receiver<Void>() {
-                      @Override
-                      public void onSuccess(Void response) {
-
-                        finishTestAndReset();
-                      }
-                    });
-                  }
-                });
-          }
-        });
-  }
-
-  /*
-   * Find Entity Create Entity2 Relate Entity2 to Entity Persist Entity
-   */
-  public void testPersistExistingEntityNewRelation() {
-    delayTestFinish(5000);
-
-    // Make a new bar
-    SimpleBarRequest context = req.simpleBarRequest();
-    SimpleBarProxy makeABar = context.create(SimpleBarProxy.class);
-    Request<SimpleBarProxy> persistRequest = context.persistAndReturnSelf().using(
-        makeABar);
-    makeABar = context.edit(makeABar);
-    makeABar.setUserName("Amit");
-
-    persistRequest.fire(new Receiver<SimpleBarProxy>() {
-      @Override
-      public void onSuccess(final SimpleBarProxy persistedBar) {
-
-        // It was made, now find a foo to assign it to
-        req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-            new Receiver<SimpleFooStringProxy>() {
-              @Override
-              public void onSuccess(SimpleFooStringProxy response) {
-
-                // Found the foo, edit it
-                SimpleFooStringRequest context = req.simpleFooStringRequest();
-                Request<Void> fooReq = context.persist().using(response);
-                response = context.edit(response);
-                response.setBarField(persistedBar);
-                fooReq.fire(new Receiver<Void>() {
-                  @Override
-                  public void onSuccess(Void response) {
-
-                    // Foo was persisted, fetch it again check the goods
-                    req.simpleFooStringRequest().findSimpleFooStringById("999x").with(
-                        "barField.userName").fire(
-                        new Receiver<SimpleFooStringProxy>() {
-
-                          // Here it is
-                          @Override
-                          public void onSuccess(
-                              SimpleFooStringProxy finalFooProxy) {
-                            assertEquals("Amit",
-                                finalFooProxy.getBarField().getUserName());
-                            finishTestAndReset();
-                          }
-                        });
-                  }
-                });
-              }
-            });
-      }
-    });
-  }
-
-  /*
-   * Find Entity2 Create Entity, Persist Entity Relate Entity2 to Entity Persist
-   * Entity
-   */
-  public void testPersistNewEntityExistingRelation() {
-    delayTestFinish(5000);
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy newFoo = context.create(SimpleFooStringProxy.class);
-    final Request<Void> fooReq = context.persist().using(newFoo);
-
-    newFoo = context.edit(newFoo);
-    newFoo.setUserName("Ray");
-
-    final SimpleFooStringProxy finalFoo = newFoo;
-    req.simpleBarRequest().findSimpleBarById("999L").fire(
-        new Receiver<SimpleBarProxy>() {
-          @Override
-          public void onSuccess(SimpleBarProxy response) {
-            finalFoo.setBarField(response);
-            fooReq.fire(new Receiver<Void>() {
-              @Override
-              public void onSuccess(Void response) {
-                req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-                    new Receiver<SimpleFooStringProxy>() {
-                      @Override
-                      public void onSuccess(SimpleFooStringProxy finalFooProxy) {
-                        // newFoo hasn't been persisted, so userName is the old
-                        // value.
-                        assertEquals("GWT", finalFooProxy.getUserName());
-                        finishTestAndReset();
-                      }
-                    });
-              }
-            });
-          }
-        });
-  }
-
-  /*
-   * Create Entity, Persist Entity Create Entity2, Perist Entity2 relate Entity2
-   * to Entity Persist
-   */
-  public void testPersistNewEntityNewRelation() {
-    delayTestFinish(5000);
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy newFoo = context.create(SimpleFooStringProxy.class);
-    final Request<SimpleFooStringProxy> fooReq = context.persistAndReturnSelf().using(
-        newFoo);
-
-    newFoo = context.edit(newFoo);
-    newFoo.setUserName("Ray");
-
-    SimpleBarRequest contextBar = req.simpleBarRequest();
-    SimpleBarProxy newBar = contextBar.create(SimpleBarProxy.class);
-    final Request<SimpleBarProxy> barReq = contextBar.persistAndReturnSelf().using(
-        newBar);
-    newBar = contextBar.edit(newBar);
-    newBar.setUserName("Amit");
-
-    fooReq.fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onSuccess(final SimpleFooStringProxy persistedFoo) {
-        barReq.fire(new Receiver<SimpleBarProxy>() {
-          @Override
-          public void onSuccess(final SimpleBarProxy persistedBar) {
-            assertEquals("Ray", persistedFoo.getUserName());
-            SimpleFooStringRequest context = req.simpleFooStringRequest();
-            final Request<Void> fooReq2 = context.persist().using(persistedFoo);
-            SimpleFooStringProxy editablePersistedFoo = context.edit(persistedFoo);
-            editablePersistedFoo.setBarField(persistedBar);
-            fooReq2.fire(new Receiver<Void>() {
-              @Override
-              public void onSuccess(Void response) {
-                req.simpleFooStringRequest().findSimpleFooStringById("999x").with(
-                    "barField.userName").fire(
-                    new Receiver<SimpleFooStringProxy>() {
-                      @Override
-                      public void onSuccess(SimpleFooStringProxy finalFooProxy) {
-                        assertEquals("Amit",
-                            finalFooProxy.getBarField().getUserName());
-                        finishTestAndReset();
-                      }
-                    });
-              }
-            });
-          }
-        });
-      }
-    });
-  }
-
-  public void testPersistRecursiveRelation() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy rayFoo = context.create(SimpleFooStringProxy.class);
-    final Request<SimpleFooStringProxy> persistRay = context.persistAndReturnSelf().using(
-        rayFoo);
-    rayFoo = context.edit(rayFoo);
-    rayFoo.setUserName("Ray");
-    rayFoo.setFooField(rayFoo);
-    persistRay.fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onSuccess(final SimpleFooStringProxy persistedRay) {
-        finishTestAndReset();
-      }
-    });
-  }
-
-  public void testPersistRelation() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy rayFoo = context.create(SimpleFooStringProxy.class);
-    final Request<SimpleFooStringProxy> persistRay = context.persistAndReturnSelf().using(
-        rayFoo);
-    rayFoo = context.edit(rayFoo);
-    rayFoo.setUserName("Ray");
-
-    persistRay.fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onSuccess(final SimpleFooStringProxy persistedRay) {
-        SimpleBarRequest context = req.simpleBarRequest();
-        SimpleBarProxy amitBar = context.create(SimpleBarProxy.class);
-        final Request<SimpleBarProxy> persistAmit = context.persistAndReturnSelf().using(
-            amitBar);
-        amitBar = context.edit(amitBar);
-        amitBar.setUserName("Amit");
-
-        persistAmit.fire(new Receiver<SimpleBarProxy>() {
-          @Override
-          public void onSuccess(SimpleBarProxy persistedAmit) {
-
-            SimpleFooStringRequest context = req.simpleFooStringRequest();
-            final Request<SimpleFooStringProxy> persistRelationship = context.persistAndReturnSelf().using(
-                persistedRay).with("barField");
-            SimpleFooStringProxy newRec = context.edit(persistedRay);
-            newRec.setBarField(persistedAmit);
-
-            persistRelationship.fire(new Receiver<SimpleFooStringProxy>() {
-              @Override
-              public void onSuccess(SimpleFooStringProxy relatedRay) {
-                assertEquals("Amit", relatedRay.getBarField().getUserName());
-                finishTestAndReset();
-              }
-            });
-          }
-        });
-      }
-    });
-  }
-
-  public void testProxysAsInstanceMethodParams() {
-    delayTestFinish(5000);
-    req.simpleFooStringRequest().findSimpleFooStringById("999x").fire(
-        new Receiver<SimpleFooStringProxy>() {
-          @Override
-          public void onSuccess(SimpleFooStringProxy response) {
-            SimpleFooStringRequest context = req.simpleFooStringRequest();
-            SimpleBarProxy bar = context.create(SimpleBarProxy.class);
-            Request<String> helloReq = context.hello(bar).using(response);
-            bar = context.edit(bar);
-            bar.setUserName("BAR");
-            helloReq.fire(new Receiver<String>() {
-              @Override
-              public void onSuccess(String response) {
-                assertEquals("Greetings BAR from GWT", response);
-                finishTestAndReset();
-              }
-            });
-          }
-        });
-  }
-
-  public void testServerFailure() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy newFoo = context.create(SimpleFooStringProxy.class);
-    final Request<SimpleFooStringProxy> persistRequest = context.persistAndReturnSelf().using(
-        newFoo);
-
-    final SimpleFooStringProxy mutableFoo = context.edit(newFoo);
-    mutableFoo.setPleaseCrash(42); // 42 is the crash causing magic number
-
-    persistRequest.fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onFailure(ServerFailure error) {
-        assertEquals("Server Error: THIS EXCEPTION IS EXPECTED BY A TEST",
-            error.getMessage());
-        assertEquals("", error.getExceptionType());
-        assertEquals("", error.getStackTraceString());
-
-        // Now show that we can fix the error and try again with the same
-        // request
-
-        mutableFoo.setPleaseCrash(24); // Only 42 crashes
-        persistRequest.fire(new Receiver<SimpleFooStringProxy>() {
-          @Override
-          public void onSuccess(SimpleFooStringProxy response) {
-            finishTestAndReset();
-          }
-        });
-      }
-
-      @Override
-      public void onSuccess(SimpleFooStringProxy response) {
-        fail("Failure expected but onSuccess() was called");
-      }
-
-      @Override
-      public void onViolation(Set<Violation> errors) {
-        fail("Failure expected but onViolation() was called");
-      }
-    });
-  }
-
-  public void testStableId() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    final SimpleFooStringProxy foo = context.create(SimpleFooStringProxy.class);
-    final Object futureId = foo.getId();
-    Request<SimpleFooStringProxy> fooReq = context.persistAndReturnSelf().using(
-        foo);
-
-    final SimpleFooStringProxy newFoo = context.edit(foo);
-    assertEquals(futureId, foo.getId());
-    assertEquals(futureId, newFoo.getId());
-
-    newFoo.setUserName("GWT basic user");
-    fooReq.fire(new Receiver<SimpleFooStringProxy>() {
-
-      @Override
-      public void onSuccess(final SimpleFooStringProxy returned) {
-        assertEquals(futureId, foo.getId());
-        assertEquals(futureId, newFoo.getId());
-
-        checkStableIdEquals(foo, returned);
-        checkStableIdEquals(newFoo, returned);
-
-        SimpleFooStringRequest context = req.simpleFooStringRequest();
-        Request<SimpleFooStringProxy> editRequest = context.persistAndReturnSelf().using(
-            returned);
-        final SimpleFooStringProxy editableFoo = context.edit(returned);
-        editableFoo.setUserName("GWT power user");
-        editRequest.fire(new Receiver<SimpleFooStringProxy>() {
-
-          @Override
-          public void onSuccess(SimpleFooStringProxy returnedAfterEdit) {
-            checkStableIdEquals(editableFoo, returnedAfterEdit);
-            assertEquals(returnedAfterEdit.getId(), returned.getId());
-            finishTestAndReset();
-          }
-        });
-      }
-    });
-  }
-
-  public void testViolationAbsent() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy newFoo = context.create(SimpleFooStringProxy.class);
-    final Request<Void> fooReq = context.persist().using(newFoo);
-
-    newFoo = context.edit(newFoo);
-    newFoo.setUserName("Amit"); // will not cause violation.
-
-    fooReq.fire(new Receiver<Void>() {
-      @Override
-      public void onSuccess(Void ignore) {
-        finishTestAndReset();
-      }
-    });
-  }
-
-  public void testViolationsOnCreate() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy newFoo = context.create(SimpleFooStringProxy.class);
-    final Request<SimpleFooStringProxy> create = context.persistAndReturnSelf().using(
-        newFoo);
-    new FailFixAndRefire<SimpleFooStringProxy>(newFoo, context, create).doTest();
-  }
-
-  public void testViolationsOnCreateVoidReturn() {
-    delayTestFinish(5000);
-
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy newFoo = context.create(SimpleFooStringProxy.class);
-    final Request<Void> create = context.persist().using(newFoo);
-    new FailFixAndRefire<Void>(newFoo, context, create).doVoidTest();
-  }
-
-  public void testViolationsOnEdit() {
-    delayTestFinish(5000);
-
-    fooCreationRequest().fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onSuccess(SimpleFooStringProxy returned) {
-        SimpleFooStringRequest context = req.simpleFooStringRequest();
-        Request<SimpleFooStringProxy> editRequest = context.persistAndReturnSelf().using(
-            returned);
-        new FailFixAndRefire<SimpleFooStringProxy>(returned, context,
-            editRequest).doTest();
-      }
-    });
-  }
-
-  public void testViolationsOnEditVoidReturn() {
-    delayTestFinish(5000);
-
-    fooCreationRequest().fire(new Receiver<SimpleFooStringProxy>() {
-      @Override
-      public void onSuccess(SimpleFooStringProxy returned) {
-        SimpleFooStringRequest context = req.simpleFooStringRequest();
-        Request<Void> editRequest = context.persist().using(returned);
-        new FailFixAndRefire<Void>(returned, context, editRequest).doVoidTest();
-      }
-    });
-  }
-
-  private void assertCannotFire(final Request<Long> mutateRequest) {
-    try {
-      mutateRequest.fire(new Receiver<Long>() {
-        @Override
-        public void onSuccess(Long response) {
-          fail("Should not be called");
-        }
-      });
-      fail("Expected IllegalStateException");
-    } catch (IllegalStateException e) {
-      /* cannot reuse a successful request, mores the pity */
-    }
-  }
-
-  private Request<SimpleFooStringProxy> fooCreationRequest() {
-    SimpleFooStringRequest context = req.simpleFooStringRequest();
-    SimpleFooStringProxy originalFoo = context.create(SimpleFooStringProxy.class);
-    final Request<SimpleFooStringProxy> fooReq = context.persistAndReturnSelf().using(
-        originalFoo);
-    originalFoo = context.edit(originalFoo);
-    originalFoo.setUserName("GWT User");
-    return fooReq;
-  }
-}
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 048745d..642d96a 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.requestfactory.client;
 
-import com.google.gwt.requestfactory.client.impl.SimpleEntityProxyId;
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyChange;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
@@ -29,6 +28,7 @@
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooRequest;
 import com.google.gwt.requestfactory.shared.Violation;
+import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -62,11 +62,11 @@
     @Override
     public void onFailure(ServerFailure error) {
       assertEquals(expectedException, error.getExceptionType());
-      if (expectedException.length() > 0) {
+      if (expectedException != null) {
         assertFalse(error.getStackTraceString().length() == 0);
         assertEquals("THIS EXCEPTION IS EXPECTED BY A TEST", error.getMessage());
       } else {
-        assertEquals("", error.getStackTraceString());
+        assertEquals(null, error.getStackTraceString());
         assertEquals("Server Error: THIS EXCEPTION IS EXPECTED BY A TEST",
             error.getMessage());
       }
@@ -338,7 +338,8 @@
       public void onSuccess(SimpleFooProxy response) {
         assertFalse(((SimpleEntityProxyId<SimpleFooProxy>) response.stableId()).isEphemeral());
         assertEquals(2, handler.persistEventCount); // two bars persisted.
-        assertEquals(2, handler.totalEventCount);
+        assertEquals(2, handler.updateEventCount); // two bars persisted.
+        assertEquals(4, handler.totalEventCount);
         finishTestAndReset();
       }
     });
@@ -924,7 +925,7 @@
    */
   public void testNullValueInIntegerListRequest() {
     delayTestFinish(DELAY_TEST_FINISH);
-    List<Integer> list = Arrays.asList(new Integer[] {1, 2, null});
+    List<Integer> list = Arrays.asList(new Integer[]{1, 2, null});
     final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInIntegerList(
         list);
     fooReq.fire(new Receiver<Void>() {
@@ -940,7 +941,7 @@
    */
   public void testNullValueInStringListRequest() {
     delayTestFinish(DELAY_TEST_FINISH);
-    List<String> list = Arrays.asList(new String[] {"nonnull", "null", null});
+    List<String> list = Arrays.asList(new String[]{"nonnull", "null", null});
     final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInStringList(
         list);
     fooReq.fire(new Receiver<Void>() {
@@ -973,7 +974,7 @@
         assertEquals("user name", f.getUserName());
         assertEquals(Byte.valueOf((byte) 100), f.getByteField());
         assertEquals(Short.valueOf((short) 12345), f.getShortField());
-        assertEquals(Float.valueOf(1234.56f), f.getFloatField());
+        assertEquals(0, (int) Math.rint(123456f - 100 * f.getFloatField()));
         assertEquals(Double.valueOf(1.2345), f.getDoubleField());
         assertEquals(Long.valueOf(1234L), f.getLongField());
         assertFalse(f.getBoolField());
@@ -1821,7 +1822,7 @@
     final SimpleFooProxy mutableFoo = context.edit(newFoo);
     // 43 is the crash causing magic number for a checked exception
     mutableFoo.setPleaseCrash(43);
-    persistRequest.fire(new FooReciever(mutableFoo, persistRequest, ""));
+    persistRequest.fire(new FooReciever(mutableFoo, persistRequest, null));
   }
 
   public void testServerFailureRuntimeException() {
@@ -1833,7 +1834,7 @@
     final SimpleFooProxy mutableFoo = context.edit(newFoo);
     // 42 is the crash causing magic number for a runtime exception
     mutableFoo.setPleaseCrash(42);
-    persistRequest.fire(new FooReciever(mutableFoo, persistRequest, ""));
+    persistRequest.fire(new FooReciever(mutableFoo, persistRequest, null));
   }
 
   /**
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
index 07c780d..c162c63 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
@@ -32,9 +32,6 @@
  */
 public abstract class RequestFactoryTestBase extends GWTTestCase {
 
-  protected SimpleRequestFactory req;
-  protected EventBus eventBus;
-
   /**
    * Class for counting events.
    */
@@ -63,42 +60,13 @@
     }
   }
 
+  protected EventBus eventBus;
+  protected SimpleRequestFactory req;
+
   @Override
   public void gwtSetUp() {
-    eventBus = new SimpleEventBus();
-    req = GWT.create(SimpleRequestFactory.class);
-    req.initialize(eventBus);
-  }
-
-  protected void finishTestAndReset() {
-    final boolean[] reallyDone = {false, false, false};
-    req.simpleFooRequest().reset().fire(new Receiver<Void>() {
-      @Override
-      public void onSuccess(Void response) {
-        reallyDone[0] = true;
-        if (reallyDone[0] && reallyDone[1] && reallyDone[2]) {
-          finishTest();
-        }
-      }
-    });
-    req.simpleFooStringRequest().reset().fire(new Receiver<Void>() {
-      @Override
-      public void onSuccess(Void response) {
-        reallyDone[1] = true;
-        if (reallyDone[0] && reallyDone[1] && reallyDone[2]) {
-          finishTest();
-        }
-      }
-    });
-    req.simpleBarRequest().reset().fire(new Receiver<Void>() {
-      @Override
-      public void onSuccess(Void response) {
-        reallyDone[2] = true;
-        if (reallyDone[0] && reallyDone[1] && reallyDone[2]) {
-          finishTest();
-        }
-      }
-    });
+    req = createFactory();
+    eventBus = req.getEventBus();
   }
 
   protected void checkStableIdEquals(EntityProxy expected, EntityProxy actual) {
@@ -110,4 +78,35 @@
     assertNotSame(expected, actual);
     assertFalse(expected.equals(actual));
   }
+
+  /**
+   * Create and initialize a new {@link SimpleRequestFactory}.
+   */
+  protected SimpleRequestFactory createFactory() {
+    SimpleRequestFactory toReturn = GWT.create(SimpleRequestFactory.class);
+    toReturn.initialize(new SimpleEventBus());
+    return toReturn;
+  }
+
+  protected void finishTestAndReset() {
+    final boolean[] reallyDone = {false, false};
+    req.simpleFooRequest().reset().fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        reallyDone[0] = true;
+        if (reallyDone[0] && reallyDone[1]) {
+          finishTest();
+        }
+      }
+    });
+    req.simpleBarRequest().reset().fire(new Receiver<Void>() {
+      @Override
+      public void onSuccess(Void response) {
+        reallyDone[1] = true;
+        if (reallyDone[0] && reallyDone[1]) {
+          finishTest();
+        }
+      }
+    });
+  }
 }
diff --git a/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.java b/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.java
new file mode 100644
index 0000000..de8028b
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.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.server;
+
+import com.google.gwt.requestfactory.client.FindServiceTest;
+import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Run the FindService tests in-process.
+ */
+public class FindServiceJreTest extends FindServiceTest {
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected SimpleRequestFactory createFactory() {
+    return RequestFactoryJreTest.createInProcess();
+  }
+}
diff --git a/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java b/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java
deleted file mode 100644
index 919f897..0000000
--- a/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import com.google.gwt.requestfactory.shared.SimpleBarProxy;
-import com.google.gwt.requestfactory.shared.SimpleEnum;
-import com.google.gwt.requestfactory.shared.SimpleFooProxy;
-import com.google.gwt.requestfactory.shared.WriteOperation;
-import com.google.gwt.requestfactory.shared.impl.Constants;
-
-import junit.framework.TestCase;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.lang.reflect.InvocationTargetException;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests for {@link JsonRequestProcessor} .
- */
-public class JsonRequestProcessorTest extends TestCase {
-
-  enum Foo {
-    BAR, BAZ
-  }
-
-  private JsonRequestProcessor requestProcessor;
-
-  @Override
-  public void setUp() {
-    requestProcessor = new JsonRequestProcessor();
-    requestProcessor.setOperationRegistry(new ReflectionBasedOperationRegistry(
-        new DefaultSecurityProvider()));
-  }
-
-  public void testDecodeParameterValue() throws SecurityException,
-      JSONException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException, InstantiationException {
-    // primitives
-    assertTypeAndValueEquals(String.class, "Hello", "Hello");
-    assertTypeAndValueEquals(Integer.class, 1234, "1234");
-    assertTypeAndValueEquals(Byte.class, (byte) 100, "100");
-    assertTypeAndValueEquals(Short.class, (short) 12345, "12345");
-    assertTypeAndValueEquals(Float.class, 1.234f, "1.234");
-    assertTypeAndValueEquals(Double.class, 1.234567, "1.234567");
-    assertTypeAndValueEquals(Long.class, 1234L, "1234");
-    assertTypeAndValueEquals(Boolean.class, true, "true");
-    assertTypeAndValueEquals(Boolean.class, false, "false");
-
-    // dates
-    Date now = new Date();
-    assertTypeAndValueEquals(Date.class, now, "" + now.getTime());
-    // bigdecimal and biginteger
-    BigDecimal dec = new BigDecimal("10").pow(100);
-    assertTypeAndValueEquals(BigDecimal.class, dec, dec.toPlainString());
-    assertTypeAndValueEquals(BigInteger.class, dec.toBigInteger(),
-        dec.toBigInteger().toString());
-    // enums
-    assertTypeAndValueEquals(Foo.class, Foo.BAR, "" + Foo.BAR.ordinal());
-    
-    // nulls
-    assertTypeAndValueEquals(String.class, null, null);
-    assertTypeAndValueEquals(Integer.class, null, null);
-    assertTypeAndValueEquals(Byte.class, null, null);
-    assertTypeAndValueEquals(Short.class, null, null);
-    assertTypeAndValueEquals(Float.class, null, null);
-    assertTypeAndValueEquals(Double.class, null, null);
-    assertTypeAndValueEquals(Long.class, null, null);
-    assertTypeAndValueEquals(Boolean.class, null, null);
-    assertTypeAndValueEquals(Date.class, null, null);
-    assertTypeAndValueEquals(BigDecimal.class, null, null);
-    assertTypeAndValueEquals(BigInteger.class, null, null);
-    assertTypeAndValueEquals(Foo.class, null, null);
-  }
-
-  public void testEncodePropertyValue() {
-    assertEncodedType(String.class, "Hello");
-    // primitive numbers become doubles
-    assertEncodedType(Double.class, (byte) 10);
-    assertEncodedType(Double.class, (short) 1234);
-    assertEncodedType(Double.class, 123.4f);
-    assertEncodedType(Double.class, 1234.0);
-    assertEncodedType(Double.class, 1234);
-    // longs, big nums become strings
-    assertEncodedType(String.class, 1234L);
-    assertEncodedType(String.class, new BigDecimal(1));
-    assertEncodedType(String.class, new BigInteger("1"));
-    assertEncodedType(String.class, new Date());
-    assertEncodedType(Double.class, Foo.BAR);
-    assertEncodedType(Boolean.class, true);
-    assertEncodedType(Boolean.class, false);
-    // nulls becomes JSON Null. Needed because JSONObject stringify treats 'null'
-    // as a reason to not emit the key in the stringified object
-    assertEquals(JSONObject.NULL, requestProcessor.encodePropertyValue(null));
-    assertEquals(new Double(1.0), requestProcessor.encodePropertyValue(new Integer(1)));
-  }
-
-  public void testEndToEnd() throws Exception {
-    com.google.gwt.requestfactory.server.SimpleFoo.reset();
-    // fetch object
-    JSONObject foo = fetchVerifyAndGetInitialObject();
-
-    // modify fields and sync
-    foo.put("intId", 45);
-    foo.put("userName", "JSC");
-    foo.put("longField", "" + 9L);
-    foo.put("enumField", SimpleEnum.BAR.ordinal());
-    foo.put("boolField", false);
-    Date now = new Date();
-    foo.put("created", "" + now.getTime());
-
-    JSONObject result = getResultFromServer(foo);
-
-    // check modified fields and no violations
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    JSONArray updateArray = result.getJSONObject("sideEffects").getJSONArray(
-        "UPDATE");
-    assertEquals(1, updateArray.length());
-    assertEquals(2, updateArray.getJSONObject(0).length());
-    assertTrue(updateArray.getJSONObject(0).has(Constants.ENCODED_ID_PROPERTY));
-    assertTrue(updateArray.getJSONObject(0).has(Constants.ENCODED_VERSION_PROPERTY));
-    assertFalse(updateArray.getJSONObject(0).has("violations"));
-    assertEquals(45, (int) fooResult.getIntId());
-    assertEquals("JSC", fooResult.getUserName());
-    assertEquals(now, fooResult.getCreated());
-    assertEquals(9L, (long) fooResult.getLongField());
-    assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.BAR,
-        fooResult.getEnumField());
-    assertEquals(false, (boolean) fooResult.getBoolField());
-  }
-
-  private String createNoChangeRequestAndGetServerVersion(JSONObject foo) throws Exception {
-    // change the value on the server behind the back
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    fooResult.setUserName("JSC");
-    fooResult.setIntId(45);
-    String savedVersion = requestProcessor.encodePropertyValue(fooResult.getVersion() + 1).toString();
-    fooResult.setVersion(fooResult.getVersion() + 1);
-    fooResult.isChanged = false;
-
-    // modify fields and sync -- there should be no change on the server.
-    foo.put("intId", 45);
-    foo.put("userName", "JSC");
-    return savedVersion;
-  }
-
-  public void testEndToEndSmartDiff_NoChange() throws Exception {
-    com.google.gwt.requestfactory.server.SimpleFoo.reset();
-    // fetch object
-    JSONObject foo = fetchVerifyAndGetInitialObject();
-
-    String savedVersion = createNoChangeRequestAndGetServerVersion(foo);
-    JSONObject result = getResultFromServer(foo);
-
-    // check modified fields and no violations
-    assertTrue(result.getJSONObject("sideEffects").has("UPDATE"));
-    JSONArray updateArray = result.getJSONObject("sideEffects").getJSONArray(
-        "UPDATE");
-    // verify that the version number is unchanged.
-    assertEquals(1, updateArray.length());
-    assertEquals(2, updateArray.getJSONObject(0).length());
-    assertTrue(updateArray.getJSONObject(0).has(Constants.ENCODED_ID_PROPERTY));
-    assertTrue(updateArray.getJSONObject(0).has(
-        Constants.ENCODED_VERSION_PROPERTY));
-    assertEquals(savedVersion,
-        updateArray.getJSONObject(0).getString(
-            Constants.ENCODED_VERSION_PROPERTY));
-    // verify that the server values are unchanged.
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    assertEquals(45, (int) fooResult.getIntId());
-    assertEquals("JSC", fooResult.getUserName());
-    assertEquals(savedVersion,
-        requestProcessor.encodePropertyValue(fooResult.getVersion()).toString());
-  }
-
-  /*
-   * This test differs from testEndToEndSmartDiff_NoChange in that the version
-   * numbers are not sent. As a result, the server does not send an UPDATE
-   * sideEffect in its response.
-   */
-  public void testEndToEndSmartDiff_NoChange_NoVersion() throws Exception {
-    com.google.gwt.requestfactory.server.SimpleFoo.reset();
-    // fetch object
-    JSONObject foo = fetchVerifyAndGetInitialObject();
-
-    String savedVersion = createNoChangeRequestAndGetServerVersion(foo);
-    // remove version number from the request.
-    foo.remove(Constants.ENCODED_VERSION_PROPERTY);
-    JSONObject result = getResultFromServer(foo);
-
-    // check modified fields and no violations
-    assertFalse(result.getJSONObject("sideEffects").has("UPDATE"));
-
-    // verify that the server values are unchanged.
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    assertEquals(45, (int) fooResult.getIntId());
-    assertEquals("JSC", fooResult.getUserName());
-    assertEquals(savedVersion,
-        requestProcessor.encodePropertyValue(fooResult.getVersion()).toString());
-  }
-
-  /*
-   * This test differs from testEndToEndSmartDiff_NoChange in that the property
-   * that is changed here does not cause a version increment. As a result, no
-   * UPDATE is sent back.
-   */
-  public void testEndToEndSmartDiff_NoVersionChange() throws Exception {
-    com.google.gwt.requestfactory.server.SimpleFoo.reset();
-    // fetch object
-    JSONObject foo = fetchVerifyAndGetInitialObject();
-    // change the value on the server behind the back
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    fooResult.setLongField(45L);
-    String savedVersion = requestProcessor.encodePropertyValue(fooResult.getVersion()).toString();
-    
-    // modify fields and sync -- there should be no change on the server.
-    foo.put("longField", 45L);
-    JSONObject result = getResultFromServer(foo);
-
-    // check modified fields and no violations
-    assertFalse(result.getJSONObject("sideEffects").has("UPDATE"));
-
-    // verify that the server values are unchanged.
-    fooResult = SimpleFoo.findSimpleFooById(999L);
-    assertEquals(45L, (long) fooResult.getLongField());
-    assertEquals(savedVersion,
-        requestProcessor.encodePropertyValue(fooResult.getVersion()).toString());
-  }
-
-  public void testEndToEndSmartDiff_SomeChangeWithNull() throws Exception {
-    com.google.gwt.requestfactory.server.SimpleFoo.reset();
-    // fetch object
-    JSONObject foo = fetchVerifyAndGetInitialObject();
-
-    // change some fields but don't change other fields.
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    fooResult.setUserName("JSC");
-    fooResult.setIntId(45);
-    fooResult.setNullField(null);
-    fooResult.setBarField(SimpleBar.getSingleton());
-    fooResult.setBarNullField(null);
-    foo.put("userName", JSONObject.NULL);
-    foo.put("intId", 45);
-    foo.put("nullField", "test");
-    foo.put("barNullField",
-        JsonRequestProcessor.base64Encode(SimpleBar.getSingleton().getId())
-            + "@NO@" + SimpleBarProxy.class.getName());
-    foo.put("barField", JSONObject.NULL);
-
-    JSONObject result = getResultFromServer(foo);
-
-    // check modified fields and no violations
-    assertTrue(result.getJSONObject("sideEffects").has("UPDATE"));
-    fooResult = SimpleFoo.findSimpleFooById(999L);
-    assertEquals(45, (int) fooResult.getIntId());
-    assertNull(fooResult.getUserName());
-    assertEquals("test", fooResult.getNullField());
-    assertEquals(SimpleBar.getSingleton(), fooResult.getBarNullField());
-    assertNull(fooResult.getBarField());
-  }
-
-  public void testEndToEndSmartDiff_SomeChange() throws Exception {
-    com.google.gwt.requestfactory.server.SimpleFoo.reset();
-    // fetch object
-    JSONObject foo = fetchVerifyAndGetInitialObject();
-
-    // change some fields but don't change other fields.
-    SimpleFoo fooResult = SimpleFoo.findSimpleFooById(999L);
-    fooResult.setUserName("JSC");
-    fooResult.setIntId(45);
-    foo.put("userName", "JSC");
-    foo.put("intId", 45);
-    Date now = new Date();
-    long newTime = now.getTime() + 10000;
-    foo.put("created", "" + newTime);
-
-    JSONObject result = getResultFromServer(foo);
-
-    // check modified fields and no violations
-    assertTrue(result.getJSONObject("sideEffects").has("UPDATE"));
-    fooResult = SimpleFoo.findSimpleFooById(999L);
-    assertEquals(45, (int) fooResult.getIntId());
-    assertEquals("JSC", fooResult.getUserName());
-    assertEquals(newTime, fooResult.getCreated().getTime());
-  }
-
-  public void testEndToEndNumberList()
-      throws ClassNotFoundException, InvocationTargetException,
-      NoSuchMethodException, JSONException, InstantiationException,
-      IllegalAccessException {
-    fetchVerifyAndGetNumberList();
-  }
-  
-  private void assertEncodedType(Class<?> expected, Object value) {
-    assertEquals(expected,
-        requestProcessor.encodePropertyValue(value).getClass());
-  }
-
-  private <T> void assertTypeAndValueEquals(Class<T> expectedType,
-      T expectedValue, String paramValue) throws SecurityException,
-      JSONException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException, InstantiationException {
-    Object val = requestProcessor.decodeParameterValue(expectedType, paramValue);
-    if (val != null) {
-      assertEquals(expectedType, val.getClass());
-    }
-    assertEquals(expectedValue, val);
-  }
-
-  @SuppressWarnings("unchecked")
-  private JSONObject fetchVerifyAndGetInitialObject() throws JSONException,
-      NoSuchMethodException, IllegalAccessException, InvocationTargetException,
-      ClassNotFoundException, SecurityException, InstantiationException {
-    JSONObject results = requestProcessor.processJsonRequest("{ \""
-        + Constants.OPERATION_TOKEN
-        + "\": \""
-        + com.google.gwt.requestfactory.shared.SimpleFooRequest.class.getName()
-        + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR
-        + "findSimpleFooById\", "
-        + "\""
-        + Constants.PARAM_TOKEN
-        + "0\": \"999\", \"" + Constants.PROPERTY_REF_TOKEN  + "\": "
-        + "\"oneToManyField,oneToManySetField,selfOneToManyField\""
-        + "}");
-    JSONObject foo = results.getJSONObject("result");
-    assertEquals(foo.getLong("id"), 999L);
-    assertEquals(foo.getInt("intId"), 42);
-    assertEquals(foo.getString("userName"), "GWT");
-    assertEquals(foo.getLong("longField"), 8L);
-    assertEquals(foo.getInt("enumField"), 0);
-    assertEquals(foo.getInt(Constants.ENCODED_VERSION_PROPERTY), 1);
-    assertEquals(foo.getBoolean("boolField"), true);
-    assertNotNull(foo.getString("!id"));
-    assertTrue(foo.has("created"));
-    List<Double> numList = (List<Double>) foo.get("numberListField");
-    assertEquals(2, numList.size());
-    assertEquals(42.0, numList.get(0));
-    assertEquals(99.0, numList.get(1));
-
-    List<String> oneToMany = (List<String>) foo.get("oneToManyField");
-    assertEquals(2, oneToMany.size());
-    assertEquals(JsonRequestProcessor.base64Encode("1L") + "@NO@com.google.gwt.requestfactory.shared.SimpleBarProxy", oneToMany.get(0));
-    assertEquals(JsonRequestProcessor.base64Encode("1L") + "@NO@com.google.gwt.requestfactory.shared.SimpleBarProxy", oneToMany.get(1));
-
-    List<String> selfOneToMany = (List<String>) foo.get("selfOneToManyField");
-    assertEquals(1, selfOneToMany.size());
-    assertEquals("999@NO@com.google.gwt.requestfactory.shared.SimpleFooProxy", selfOneToMany.get(0));
-
-    Set<String> oneToManySet = (Set<String>) foo.get("oneToManySetField");
-    assertEquals(1, oneToManySet.size());
-    assertEquals(JsonRequestProcessor.base64Encode("1L") + "@NO@com.google.gwt.requestfactory.shared.SimpleBarProxy", oneToManySet.iterator().next());
-    return foo;
-  }
-
-   private JSONArray fetchVerifyAndGetNumberList() throws JSONException,
-      NoSuchMethodException, IllegalAccessException, InvocationTargetException,
-      ClassNotFoundException, SecurityException, InstantiationException {
-    JSONObject results = requestProcessor.processJsonRequest("{ \""
-        + Constants.OPERATION_TOKEN
-        + "\": \""
-        + com.google.gwt.requestfactory.shared.SimpleFooRequest.class.getName()
-        + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR
-        + "getNumberList\" }");
-    JSONArray foo = results.getJSONArray("result");
-    assertEquals(foo.length(), 3);
-    assertEquals(foo.getInt(0), 1);
-    assertEquals(foo.getInt(1), 2);
-    assertEquals(foo.getInt(2), 3);     
-    return foo;
-  }
-
-  public void testPrimitiveListAsParameter() throws JSONException,
-      NoSuchMethodException, IllegalAccessException, InvocationTargetException,
-      ClassNotFoundException, SecurityException, InstantiationException {
-
-    JSONObject results = requestProcessor.processJsonRequest("{ \""
-        + Constants.OPERATION_TOKEN
-        + "\": \""
-        + com.google.gwt.requestfactory.shared.SimpleFooRequest.class.getName()
-        + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR
-        + "sum\", "
-        + "\"" + Constants.PARAM_TOKEN + "0\":"
-        + "\"1@NO@com.google.gwt.requestfactory.shared.SimpleFooProxy\","
-        + "\""
-        + Constants.PARAM_TOKEN
-        + "1\": [1, 2, 3] }");
-    assertEquals(6, results.getInt("result"));
-  }
-
-  public void testProxyListAsParameter() throws JSONException,
-        NoSuchMethodException, IllegalAccessException, InvocationTargetException,
-        ClassNotFoundException, SecurityException, InstantiationException {
-      SimpleFoo.reset();
-      JSONObject results = requestProcessor.processJsonRequest("{ \""
-          + Constants.OPERATION_TOKEN
-          + "\": \""
-          + com.google.gwt.requestfactory.shared.SimpleFooRequest.class.getName()
-          + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR
-          + "processList\", "
-          + "\"" + Constants.PARAM_TOKEN + "0\":"
-          + "\"1@NO@com.google.gwt.requestfactory.shared.SimpleFooProxy\","
-          + "\""
-          + Constants.PARAM_TOKEN
-          + "1\": [\"1@NO@com.google.gwt.requestfactory.shared.SimpleFooProxy\", \"1@NO@com.google.gwt.requestfactory.shared.SimpleFooProxy\", \"1@NO@com.google.gwt.requestfactory.shared.SimpleFooProxy\"] }");
-      assertEquals("GWTGWTGWT", results.getString("result"));
-    }
-
-  private JSONObject getResultFromServer(JSONObject foo) throws JSONException,
-      NoSuchMethodException, IllegalAccessException, InvocationTargetException,
-      ClassNotFoundException, SecurityException, InstantiationException {
-    JSONObject proxyWithSchema = new JSONObject();
-    proxyWithSchema.put(SimpleFooProxy.class.getName(), foo);
-    JSONArray arr = new JSONArray();
-    arr.put(proxyWithSchema);
-    JSONObject operation = new JSONObject();
-    operation.put(WriteOperation.UPDATE.toString(), arr);
-    JSONObject sync = new JSONObject();
-    sync.put(Constants.OPERATION_TOKEN,
-        "com.google.gwt.requestfactory.shared.SimpleFooRequest::persist");
-    sync.put(Constants.CONTENT_TOKEN, operation.toString());
-    sync.put(Constants.PARAM_TOKEN + "0", foo.getString("id") + "@NO" + "@"
-        + SimpleFooProxy.class.getName());
-    return requestProcessor.processJsonRequest(sync.toString());
-  }
-}
diff --git a/user/test/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistryTest.java b/user/test/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistryTest.java
deleted file mode 100644
index 5cb7ceb..0000000
--- a/user/test/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistryTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import com.google.gwt.requestfactory.shared.SimpleBarProxy;
-import com.google.gwt.requestfactory.shared.SimpleFooProxy;
-
-import junit.framework.TestCase;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Tests for
- * {@link com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistry}
- * .
- */
-public class ReflectionBasedOperationRegistryTest extends TestCase {
-
-  private ReflectionBasedOperationRegistry registry;
-
-  @Override
-  public void setUp() {
-    registry = new ReflectionBasedOperationRegistry(
-        new DefaultSecurityProvider());
-  }
-
-  public void testGetOperationListNoArgs() {
-    RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.shared.SimpleFooRequest::findAll");
-    assert request != null;
-    assertEquals("com.google.gwt.requestfactory.server.SimpleFoo",
-        request.getDomainClassName());
-    assertEquals("findAll", request.getDomainMethod().getName());
-    assertEquals(SimpleFooProxy.class, request.getReturnType());
-    assertEquals(0, request.getParameterTypes().length);
-    assertTrue(List.class.isAssignableFrom(request.getDomainMethod().getReturnType()));
-  }
-
-  public void testGetOperationScalarNoArgs() {
-    RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.shared.SimpleFooRequest::countSimpleFoo");
-    assert request != null;
-    assertEquals("com.google.gwt.requestfactory.server.SimpleFoo",
-        request.getDomainClassName());
-    assertEquals("countSimpleFoo", request.getDomainMethod().getName());
-    assertEquals(Long.class, request.getReturnType());
-    assertEquals(0, request.getParameterTypes().length);
-    assertFalse(List.class.isAssignableFrom(request.getDomainMethod().getReturnType()));
-  }
-
-  public void testGetOpertionScalarWithArgs() {
-    {
-      RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.shared.SimpleFooRequest::findSimpleFooById");
-      assertNotNull(request);
-      assertEquals("com.google.gwt.requestfactory.server.SimpleFoo",
-          request.getDomainClassName());
-      assertEquals("findSimpleFooById", request.getDomainMethod().getName());
-      assertEquals(SimpleFooProxy.class, request.getReturnType());
-      assertEquals(1, request.getParameterTypes().length);
-      assertEquals(Long.class, request.getParameterTypes()[0]);
-      assertFalse(List.class.isAssignableFrom(request.getDomainMethod().getReturnType()));
-    }
-    {
-      RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.shared.SimpleBarRequest::findSimpleBarById");
-      assertNotNull(request);
-      assertEquals("com.google.gwt.requestfactory.server.SimpleBar",
-          request.getDomainClassName());
-      assertEquals("findSimpleBarById", request.getDomainMethod().getName());
-      assertEquals(SimpleBarProxy.class, request.getReturnType());
-      assertEquals(1, request.getParameterTypes().length);
-      assertEquals(String.class, request.getParameterTypes()[0]);
-      assertFalse(List.class.isAssignableFrom(request.getDomainMethod().getReturnType()));
-    }
-  }
-
-  public void testGetOperationSetNoArgs() {
-    RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.shared.SimpleBarRequest::findAsSet");
-    assert request != null;
-    assertEquals("com.google.gwt.requestfactory.server.SimpleBar",
-        request.getDomainClassName());
-    assertEquals("findAsSet", request.getDomainMethod().getName());
-    assertEquals(SimpleBarProxy.class, request.getReturnType());
-    assertEquals(0, request.getParameterTypes().length);
-    assertTrue(Set.class.isAssignableFrom(request.getDomainMethod().getReturnType()));
-  }
-
-  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() {
-    RequestDefinition request = registry.getOperation("com.google.gwt.requestfactory.shared.SimpleFooRequest::privateMethod");
-    assert request == null;
-  }
-}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 75b4f50..24739f7 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -24,6 +24,7 @@
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.Service;
 import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.FindRequest;
 
 import junit.framework.TestCase;
 
@@ -144,6 +145,26 @@
   RequestFactoryInterfaceValidator v;
 
   /**
+   * Ensure that calling {@link RequestFactoryInterfaceValidator#antidote()}
+   * doesn't cause information to be lost.
+   */
+  public void testAntidote() {
+    v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+    assertTrue(v.isPoisoned());
+    v.antidote();
+    assertFalse(v.isPoisoned());
+    v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+    assertTrue(v.isPoisoned());
+  }
+
+  /**
+   * Test the {@link FindRequest} context used to implement find().
+   */
+  public void testFindRequestContext() {
+    v.validateRequestContext(FindRequest.class.getName());
+  }
+
+  /**
    * Ensure that the &lt;clinit> methods don't interfere with validation.
    */
   public void testIntecfacesWithClinits() {
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
new file mode 100644
index 0000000..d0d69b1
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.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.SimpleRequestFactory;
+
+/**
+ * Runs the RequestFactory tests in-process.
+ */
+public class RequestFactoryJreTest extends RequestFactoryTest {
+
+  public static SimpleRequestFactory createInProcess() {
+    EventBus eventBus = new SimpleEventBus();
+    SimpleRequestFactory req = RequestFactoryMagic.create(SimpleRequestFactory.class);
+    ServiceLayer serviceLayer = new ReflectiveServiceLayer();
+    SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
+    req.initialize(eventBus, new InProcessRequestTransport(processor));
+    return req;
+  }
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  @Override
+  protected SimpleRequestFactory createFactory() {
+    return createInProcess();
+  }
+}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java b/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java
deleted file mode 100644
index 17cc049..0000000
--- a/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link com.google.gwt.requestfactory.server.RequestProperty} .
- */
-public class RequestPropertyTest extends TestCase {
-
-  public void testParseMultipleSelector() {
-    RequestProperty prop = RequestProperty.parse("supervisor.name, supervisor.age");
-    RequestProperty sup = prop.getProperty("supervisor");
-    assertNotNull(sup);
-    RequestProperty name = sup.getProperty("name");
-    assertNotNull(name);
-    RequestProperty age = sup.getProperty("age");
-    assertNotNull(age);
-  }
-  public void testParseSingleSelector() {
-    RequestProperty prop = RequestProperty.parse("supervisor.name");
-    RequestProperty sup = prop.getProperty("supervisor");
-    assertNotNull(sup);
-    RequestProperty name = sup.getProperty("name");
-    assertNotNull(name);
-  }
-}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleBar.java b/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
index 80495e9..d75ff3b 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
@@ -16,7 +16,6 @@
 package com.google.gwt.requestfactory.server;
 
 import com.google.gwt.dev.util.collect.HashSet;
-import com.google.gwt.requestfactory.shared.Id;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -124,7 +123,6 @@
 
   Integer version = 1;
 
-  @Id
   private String id = "999L";
   private boolean findFails;
   private boolean isNew = true;
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 6c53ac3..417aef2 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.requestfactory.server;
 
-import com.google.gwt.requestfactory.shared.Id;
 import com.google.gwt.requestfactory.shared.SimpleEnum;
 
 import java.math.BigDecimal;
@@ -39,7 +38,7 @@
   /**
    * DO NOT USE THIS UGLY HACK DIRECTLY! Call {@link #get} instead.
    */
-  private static Map<Long, SimpleFoo> jreTestSingleton = new HashMap<Long, SimpleFoo>();
+  private static Map<Long, SimpleFoo> jreTestSingleton;
 
   private static Long nextId = 1L;
 
@@ -87,6 +86,9 @@
     HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
     if (req == null) {
       // May be in a JRE test case, use the singleton
+      if (jreTestSingleton == null) {
+        jreTestSingleton = resetImpl();
+      }
       return jreTestSingleton;
     } else {
       /*
@@ -278,7 +280,6 @@
 
   Integer version = 1;
 
-  @Id
   private Long id = 1L;
   private boolean isNew = true;
 
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFooString.java b/user/test/com/google/gwt/requestfactory/server/SimpleFooString.java
deleted file mode 100644
index aaa4870..0000000
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFooString.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-import com.google.gwt.requestfactory.shared.Id;
-import com.google.gwt.requestfactory.shared.SimpleEnum;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.validation.constraints.Size;
-
-/**
- * Domain object for SimpleFooStringRequest. Ugly copy of SimpleFoo, just
- * changes id to String.
- */
-public class SimpleFooString {
-  /**
-   * DO NOT USE THIS UGLY HACK DIRECTLY! Call {@link #get} instead.
-   */
-  private static SimpleFooString jreTestSingleton = new SimpleFooString();
-
-  private static Long nextId = 1L;
-
-  public static Long countSimpleFoo() {
-    return 1L;
-  }
-
-  public static List<SimpleFooString> findAll() {
-    return Collections.singletonList(get());
-  }
-
-    public static SimpleFooString findSimpleFooString(String id) {
-    return findSimpleFooStringById(id);
-  }
-
-  public static SimpleFooString findSimpleFooStringById(String id) {
-    get().setId(id);
-    return get();
-  }
-
-  public static synchronized SimpleFooString get() {
-    HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
-    if (req == null) {
-      // May be in a JRE test case, use the singleton
-      return jreTestSingleton;
-    } else {
-      /*
-       * This will not behave entirely correctly unless we have a servlet filter
-       * that doesn't allow any requests to be processed unless they're
-       * associated with an existing session.
-       */
-      SimpleFooString value = (SimpleFooString) req.getSession().getAttribute(
-          SimpleFooString.class.getCanonicalName());
-      if (value == null) {
-        value = resetImpl();
-      }
-      return value;
-    }
-  }
-
-  public static List<Integer> getNumberList() {
-    ArrayList<Integer> list = new ArrayList<Integer>();
-    list.add(1);
-    list.add(2);
-    list.add(3);
-    return list;
-  }
-
-  public static Set<Integer> getNumberSet() {
-    Set<Integer> list = new HashSet<Integer>();
-    list.add(1);
-    list.add(2);
-    list.add(3);
-    return list;
-  }
-
-  public static SimpleFooString getSingleton() {
-    return get();
-  }
-
-  public static void reset() {
-    resetImpl();
-  }
-
-  public static synchronized SimpleFooString resetImpl() {
-    SimpleFooString instance = new SimpleFooString();
-    HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
-    if (req == null) {
-      jreTestSingleton = instance;
-    } else {
-      req.getSession().setAttribute(SimpleFooString.class.getCanonicalName(),
-          instance);
-    }
-    return instance;
-  }
-
-  @SuppressWarnings("unused")
-  private static Integer privateMethod() {
-    return 0;
-  }
-
-  Integer version = 1;
-
-  @Id
-  private String id = "1x";
-
-  @Size(min = 3, max = 30)
-  private String userName;
-  private String password;
-
-  private Character charField;
-  private Long longField;
-
-  private BigDecimal bigDecimalField;
-
-  private BigInteger bigIntField;
-  private Integer intId = -1;
-  private Short shortField;
-
-  private Byte byteField;
-
-  private Date created;
-  private Double doubleField;
-
-  private Float floatField;
-
-  private SimpleEnum enumField;
-  private Boolean boolField;
-
-  private Boolean otherBoolField;
-  private Integer pleaseCrash;
-
-  private SimpleBar barField;
-  private SimpleFooString fooField;
-
-  private String nullField;
-  private SimpleBar barNullField;
-
-  private List<SimpleBar> oneToManyField;
-  private List<SimpleFooString> selfOneToManyField;
-  private Set<SimpleBar> oneToManySetField;
-
-  private List<Integer> numberListField;
-
-  public SimpleFooString() {
-    intId = 42;
-    version = 1;
-    userName = "GWT";
-    longField = 8L;
-    enumField = SimpleEnum.FOO;
-    created = new Date();
-    barField = SimpleBar.getSingleton();
-    boolField = true;
-    oneToManyField = new ArrayList<SimpleBar>();
-    oneToManyField.add(barField);
-    oneToManyField.add(barField);
-    numberListField = new ArrayList<Integer>();
-    numberListField.add(42);
-    numberListField.add(99);
-    selfOneToManyField = new ArrayList<SimpleFooString>();
-    selfOneToManyField.add(this);
-    oneToManySetField = new HashSet<SimpleBar>();
-    oneToManySetField.add(barField);
-    nullField = null;
-    barNullField = null;
-    pleaseCrash = 0;
-  }
-
-  public Long countSimpleFooWithUserNameSideEffect() {
-    get().setUserName(userName);
-    version++;
-    return 1L;
-  }
-
-  public SimpleBar getBarField() {
-    return barField;
-  }
-
-  public SimpleBar getBarNullField() {
-    return barNullField;
-  }
-
-  /**
-   * Returns the bigDecimalField.
-   */
-  public BigDecimal getBigDecimalField() {
-    return bigDecimalField;
-  }
-
-  /**
-   * Returns the bigIntegerField.
-   */
-  public BigInteger getBigIntField() {
-    return bigIntField;
-  }
-
-  public Boolean getBoolField() {
-    return boolField;
-  }
-
-  /**
-   * Returns the byteField.
-   */
-  public Byte getByteField() {
-    return byteField;
-  }
-
-  /**
-   * Returns the charField.
-   */
-  public Character getCharField() {
-    return charField;
-  }
-
-  public Date getCreated() {
-    return created;
-  }
-
-  /**
-   * Returns the doubleField.
-   */
-  public Double getDoubleField() {
-    return doubleField;
-  }
-
-  public SimpleEnum getEnumField() {
-    return enumField;
-  }
-
-  /**
-   * Returns the floatField.
-   */
-  public Float getFloatField() {
-    return floatField;
-  }
-
-  public SimpleFooString getFooField() {
-    return fooField;
-  }
-
-  public String getId() {
-    return id;
-  }
-
-  public Integer getIntId() {
-    return intId;
-  }
-
-  public Long getLongField() {
-    return longField;
-  }
-
-  public List<Integer> getNumberListField() {
-    return numberListField;
-  }
-
-  public List<SimpleBar> getOneToManyField() {
-    return oneToManyField;
-  }
-
-  public Set<SimpleBar> getOneToManySetField() {
-    return oneToManySetField;
-  }
-
-  public String getNullField() {
-    return nullField;
-  }
-
-  /**
-   * Returns the otherBoolField.
-   */
-  public Boolean getOtherBoolField() {
-    return otherBoolField;
-  }
-
-  public String getPassword() {
-    return password;
-  }
-
-  public Integer getPleaseCrash() {
-    return pleaseCrash;
-  }
-
-  public List<SimpleFooString> getSelfOneToManyField() {
-    return selfOneToManyField;
-  }
-
-  /**
-   * Returns the shortField.
-   */
-  public Short getShortField() {
-    return shortField;
-  }
-
-  public String getUserName() {
-    return userName;
-  }
-
-  public Integer getVersion() {
-    return version;
-  }
-
-  public String hello(SimpleBar bar) {
-    return "Greetings " + bar.getUserName() + " from " + getUserName();
-  }
-
-  public void persist() {
-    setId(nextId++ + "x");
-    version++;
-  }
-
-  public SimpleFooString persistAndReturnSelf() {
-    persist();
-    return this;
-  }
-
-  public String processList(List<SimpleFooString> values) {
-    String result = "";
-    for (SimpleFooString n : values) {
-      result += n.getUserName();
-    }
-    return result;
-  }
-
-  public void setBarField(SimpleBar barField) {
-    this.barField = barField;
-  }
-
-  public void setBarNullField(SimpleBar barNullField) {
-    this.barNullField = barNullField;
-  }
-
-  /**
-   * @param bigDecimalField the bigDecimalField to set
-   */
-  public void setBigDecimalField(BigDecimal bigDecimalField) {
-    this.bigDecimalField = bigDecimalField;
-  }
-
-  /**
-   * @param bigIntegerField the bigIntegerField to set
-   */
-  public void setBigIntField(BigInteger bigIntegerField) {
-    this.bigIntField = bigIntegerField;
-  }
-
-  public void setBoolField(Boolean bool) {
-    boolField = bool;
-  }
-
-  /**
-   * @param byteField the byteField to set
-   */
-  public void setByteField(Byte byteField) {
-    this.byteField = byteField;
-  }
-
-  /**
-   * @param charField the charField to set
-   */
-  public void setCharField(Character charField) {
-    this.charField = charField;
-  }
-
-  public void setCreated(Date created) {
-    this.created = created;
-  }
-
-  /**
-   * @param doubleField the doubleField to set
-   */
-  public void setDoubleField(Double doubleField) {
-    this.doubleField = doubleField;
-  }
-
-  public void setEnumField(SimpleEnum enumField) {
-    this.enumField = enumField;
-  }
-
-  /**
-   * @param floatField the floatField to set
-   */
-  public void setFloatField(Float floatField) {
-    this.floatField = floatField;
-  }
-
-  public void setFooField(SimpleFooString fooField) {
-    this.fooField = fooField;
-  }
-
-  public void setId(String id) {
-    this.id = id;
-  }
-
-  public void setIntId(Integer id) {
-    this.intId = id;
-  }
-
-  public void setLongField(Long longField) {
-    this.longField = longField;
-  }
-
-  public void setNumberListField(List<Integer> numberListField) {
-    this.numberListField = numberListField;
-  }
-
-  public void setOneToManyField(List<SimpleBar> oneToManyField) {
-    this.oneToManyField = oneToManyField;
-  }
-
-  public void setOneToManySetField(Set<SimpleBar> oneToManySetField) {
-    this.oneToManySetField = oneToManySetField;
-  }
-
-  public void setNullField(String nullField) {
-    this.nullField = nullField;
-  }
-
-  /**
-   * @param otherBoolField the otherBoolField to set
-   */
-  public void setOtherBoolField(Boolean otherBoolField) {
-    this.otherBoolField = otherBoolField;
-  }
-
-  public void setPleaseCrash(Integer crashIf42) {
-    if (crashIf42 == 42) {
-      throw new UnsupportedOperationException("THIS EXCEPTION IS EXPECTED BY A TEST");
-    }
-    pleaseCrash = crashIf42;
-  }
-
-  public void setPassword(String password) {
-    this.password = password;
-  }
-
-  public void setSelfOneToManyField(List<SimpleFooString> selfOneToManyField) {
-    this.selfOneToManyField = selfOneToManyField;
-  }
-
-  /**
-   * @param shortField the shortField to set
-   */
-  public void setShortField(Short shortField) {
-    this.shortField = shortField;
-  }
-
-  public void setUserName(String userName) {
-    this.userName = userName;
-  }
-
-  public void setVersion(Integer version) {
-    this.version = version;
-  }
-
-  public Integer sum(List<Integer> values) {
-    int sum = 0;
-    for (int n : values) {
-      sum += n;
-    }
-    return sum;
-  }
-}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooStringProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooStringProxy.java
deleted file mode 100644
index 875d44c..0000000
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooStringProxy.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.shared;
-
-import com.google.gwt.requestfactory.server.SimpleFooString;
-
-/**
- * An extension of AbstractFooProxy with String id.
- */
-@ProxyFor(SimpleFooString.class)
-public interface SimpleFooStringProxy extends BaseFooProxy {
-  SimpleFooStringProxy getFooField();
-
-  String getId();
-
-  void setFooField(SimpleFooStringProxy fooField);
-
-  EntityProxyId<SimpleFooStringProxy> stableId();
-}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooStringRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooStringRequest.java
deleted file mode 100644
index 2a0346f..0000000
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooStringRequest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.shared;
-
-import java.util.List;
-
-/**
- * Do nothing test interface.
- */
-@Service(com.google.gwt.requestfactory.server.SimpleFooString.class)
-public interface SimpleFooStringRequest extends RequestContext {
-  Request<Long> countSimpleFoo();
-
-  InstanceRequest<SimpleFooStringProxy, Long> countSimpleFooWithUserNameSideEffect();
-
-  Request<List<SimpleFooStringProxy>> findAll();
-
-  Request<SimpleFooStringProxy> findSimpleFooStringById(String id);
-
-  InstanceRequest<SimpleFooStringProxy, Void> persist();
-
-  InstanceRequest<SimpleFooStringProxy, SimpleFooStringProxy> persistAndReturnSelf();
-
-  Request<Void> reset();
-
-  InstanceRequest<SimpleFooStringProxy, String> hello(SimpleBarProxy proxy);
-}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
index f763381..aaea4dc 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
@@ -22,7 +22,5 @@
 
   SimpleFooRequest simpleFooRequest();
 
-  SimpleFooStringRequest simpleFooStringRequest();
-
   SimpleBarRequest simpleBarRequest();
 }
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/SimpleEntityProxyIdTest.java b/user/test/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
similarity index 87%
rename from user/test/com/google/gwt/requestfactory/client/impl/SimpleEntityProxyIdTest.java
rename to user/test/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
index ab24902..ebb9433 100644
--- a/user/test/com/google/gwt/requestfactory/client/impl/SimpleEntityProxyIdTest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.client.impl;
+package com.google.gwt.requestfactory.shared.impl;
 
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.SimpleBarProxy;
@@ -53,16 +53,16 @@
   public void testInequality() {
     assertFalse(isStable(id(EntityProxy.class, 1), id(EntityProxy.class, 2)));
 
-    assertFalse(isStable(id(EntityProxy.class, "server1"), id(
-        EntityProxy.class, "server2")));
+    assertFalse(isStable(id(EntityProxy.class, "server1"),
+        id(EntityProxy.class, "server2")));
 
     // Same client-side id, but different types
-    assertFalse(isStable(id(SimpleFooProxy.class, 1), id(SimpleBarProxy.class,
-        1)));
+    assertFalse(isStable(id(SimpleFooProxy.class, 1),
+        id(SimpleBarProxy.class, 1)));
 
     // Same server id, but different types
-    assertFalse(isStable(id(SimpleFooProxy.class, "server1"), id(
-        SimpleBarProxy.class, "server1")));
+    assertFalse(isStable(id(SimpleFooProxy.class, "server1"),
+        id(SimpleBarProxy.class, "server1")));
   }
 
   private <T extends EntityProxy> SimpleEntityProxyId<T> id(Class<T> clazz,
diff --git a/user/test/com/google/gwt/valuestore/server/SimpleFoo.java b/user/test/com/google/gwt/valuestore/server/SimpleFoo.java
deleted file mode 100644
index 0745240..0000000
--- a/user/test/com/google/gwt/valuestore/server/SimpleFoo.java
+++ /dev/null
@@ -1,334 +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.valuestore.server;
-
-import com.google.gwt.requestfactory.server.SimpleBar;
-import com.google.gwt.requestfactory.shared.Id;
-import com.google.gwt.requestfactory.shared.SimpleEnum;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Domain object for SimpleFooRequest.
- */
-public class SimpleFoo {
-
-  static SimpleFoo singleton = new SimpleFoo();
-
-  private static Long nextId = 1L;
-
-  public static Long countSimpleFoo() {
-    return 1L;
-  }
-
-  public static List<SimpleFoo> findAll() {
-    return Collections.singletonList(singleton);
-  }
-
-  public static SimpleFoo findSimpleFoo(Long id) {
-    return findSimpleFooById(id);
-  }
-
-  public static SimpleFoo findSimpleFooById(Long id) {
-    singleton.setId(id);
-    return singleton;
-  }
-
-  public static SimpleFoo getSingleton() {
-    return singleton;
-  }
-
-  public static void reset() {
-    singleton = new SimpleFoo();
-  }
-
-  @SuppressWarnings("unused")
-  private static Integer privateMethod() {
-    return 0;
-  }
-
-  Integer version = 1;
-
-  @Id
-  private Long id = 1L;
-
-  private Integer intId = -1;
-
-  private String password;
-
-  private String userName;
-
-  private Character charField;
-
-  private Long longField;
-
-  private BigDecimal bigDecimalField;
-
-  private BigInteger bigIntField;
-
-  private Short shortField;
-
-  private Byte byteField;
-
-  private Date created;
-
-  private Double doubleField;
-
-  private Float floatField;
-
-  private SimpleEnum enumField;
-
-  private Boolean boolField;
-
-  private Boolean otherBoolField;
-
-  private SimpleBar barField;
-
-  private SimpleFoo fooField;
-
-  public SimpleFoo() {
-    intId = 42;
-    version = 1;
-    userName = "GWT";
-    longField = 8L;
-    enumField = SimpleEnum.FOO;
-    created = new Date();
-    barField = SimpleBar.getSingleton();
-    boolField = true;
-  }
-
-  public Long countSimpleFooWithUserNameSideEffect() {
-    singleton.setUserName(userName);
-    return 1L;
-  }
-
-  public SimpleBar getBarField() {
-    return barField;
-  }
-
-  /**
-   * Returns the bigDecimalField.
-   */
-  public BigDecimal getBigDecimalField() {
-    return bigDecimalField;
-  }
-
-  /**
-   * Returns the bigIntField.
-   */
-  public BigInteger getBigIntField() {
-    return bigIntField;
-  }
-
-  public Boolean getBoolField() {
-    return boolField;
-  }
-
-  /**
-   * Returns the byteField.
-   */
-  public Byte getByteField() {
-    return byteField;
-  }
-
-  /**
-   * Returns the charField.
-   */
-  public Character getCharField() {
-    return charField;
-  }
-
-  public Date getCreated() {
-    return created;
-  }
-
-  /**
-   * Returns the doubleField.
-   */
-  public Double getDoubleField() {
-    return doubleField;
-  }
-
-  public SimpleEnum getEnumField() {
-    return enumField;
-  }
-
-  /**
-   * Returns the floatField.
-   */
-  public Float getFloatField() {
-    return floatField;
-  }
-
-  public SimpleFoo getFooField() {
-    return fooField;
-  }
-
-  public Long getId() {
-    return id;
-  }
-
-  public Integer getIntId() {
-    return intId;
-  }
-
-  public Long getLongField() {
-    return longField;
-  }
-
-  /**
-   * Returns the otherBoolField.
-   */
-  public Boolean getOtherBoolField() {
-    return otherBoolField;
-  }
-
-  public String getPassword() {
-    return password;
-  }
-
-  /**
-   * Returns the shortField.
-   */
-  public Short getShortField() {
-    return shortField;
-  }
-
-  public String getUserName() {
-    return userName;
-  }
-
-  public Integer getVersion() {
-    return version;
-  }
-
-  public String hello(SimpleBar bar) {
-    return "Greetings " + bar.getUserName() + " from " + getUserName();
-  }
-
-  public void persist() {
-    setId(nextId++);
-  }
-
-  public SimpleFoo persistAndReturnSelf() {
-    persist();
-    return this;
-  }
-
-  public void setBarField(SimpleBar barField) {
-    this.barField = barField;
-  }
-
-  /**
-   * @param bigDecimalField the bigDecimalField to set
-   */
-  public void setBigDecimalField(BigDecimal bigDecimalField) {
-    this.bigDecimalField = bigDecimalField;
-  }
-
-  /**
-   * @param bigIntField the bigIntField to set
-   */
-  public void setBigIntField(BigInteger bigIntField) {
-    this.bigIntField = bigIntField;
-  }
-
-  public void setBoolField(Boolean bool) {
-    boolField = bool;
-  }
-
-  /**
-   * @param byteField the byteField to set
-   */
-  public void setByteField(Byte byteField) {
-    this.byteField = byteField;
-  }
-
-  /**
-   * @param charField the charField to set
-   */
-  public void setCharField(Character charField) {
-    this.charField = charField;
-  }
-
-  public void setCreated(Date created) {
-    this.created = created;
-  }
-
-  /**
-   * @param doubleField the doubleField to set
-   */
-  public void setDoubleField(Double doubleField) {
-    this.doubleField = doubleField;
-  }
-
-  public void setEnumField(SimpleEnum enumField) {
-    this.enumField = enumField;
-  }
-
-  /**
-   * @param floatField the floatField to set
-   */
-  public void setFloatField(Float floatField) {
-    this.floatField = floatField;
-  }
-
-  public void setFooField(SimpleFoo fooField) {
-    this.fooField = fooField;
-  }
-
-  public void setId(Long id) {
-    this.id = id;
-  }
-
-  public void setIntId(Integer id) {
-    this.intId = id;
-  }
-
-  public void setLongField(Long longField) {
-    this.longField = longField;
-  }
-
-  /**
-   * @param otherBoolField the otherBoolField to set
-   */
-  public void setOtherBoolField(Boolean otherBoolField) {
-    this.otherBoolField = otherBoolField;
-  }
-
-  public void setPassword(String password) {
-    this.password = password;
-  }
-
-  /**
-   * @param shortField the shortField to set
-   */
-  public void setShortField(Short shortField) {
-    this.shortField = shortField;
-  }
-
-  public void setUserName(String userName) {
-    this.userName = userName;
-  }
-
-  public void setVersion(Integer version) {
-    this.version = version;
-  }
-}