Adds partial support for related fields to RequestFactory. Right now, getters of such fields always return null.

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

Review by: rjrjr@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8541 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java
index b4b78fc..14fed57 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java
@@ -23,12 +23,14 @@
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestEvent;
 import com.google.gwt.requestfactory.shared.UserInformationRecord;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.Window.Location;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.valuestore.shared.SyncResult;
+import com.google.gwt.valuestore.shared.Value;
 
 import java.util.Set;
 
@@ -44,7 +46,7 @@
 
   /**
    * TODO(jgw): Put this some place more sensible.
-   * 
+   *
    * @param amount the amount in dollars
    */
   public static String formatCurrency(double amount) {
@@ -89,24 +91,36 @@
     }
 
     final HandlerManager eventBus = new HandlerManager(null);
-    final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
+    final ExpensesRequestFactory requestFactory = GWT.create(
+        ExpensesRequestFactory.class);
     requestFactory.init(eventBus);
 
-    final ExpensesMobileShell shell = new ExpensesMobileShell(eventBus,
-        requestFactory, employeeId);
-    RootPanel.get().add(shell);
-    
-    // Check for Authentication failures or mismatches
-    eventBus.addHandler(RequestEvent.TYPE, new AuthenticationFailureHandler());
+    final long finalEmployeeId = employeeId;
+    requestFactory.employeeRequest().findEmployee(Value.of(employeeId)).fire(
+        new Receiver<EmployeeRecord>() {
+          @Override
+          public void onSuccess(EmployeeRecord employee,
+              Set<SyncResult> syncResults) {
+            final ExpensesMobileShell shell = new ExpensesMobileShell(eventBus,
+                requestFactory, employee);
+            RootPanel.get().add(shell);
 
-    // Add a login widget to the page
-    final LoginWidget login = shell.getLoginWidget();
-    Receiver<UserInformationRecord> receiver = new Receiver<UserInformationRecord>() {
-      public void onSuccess(UserInformationRecord userInformationRecord, Set<SyncResult> syncResults) {
-        login.setUserInformation(userInformationRecord);
-      }       
-     };
-     requestFactory.userInformationRequest().getCurrentUserInformation(
-         Location.getHref()).fire(receiver);
+            // Check for Authentication failures or mismatches
+            eventBus.addHandler(RequestEvent.TYPE,
+                new AuthenticationFailureHandler());
+
+            // Add a login widget to the page
+            final LoginWidget login = shell.getLoginWidget();
+            Receiver<UserInformationRecord> receiver
+                = new Receiver<UserInformationRecord>() {
+              public void onSuccess(UserInformationRecord userInformationRecord,
+                  Set<SyncResult> syncResults) {
+                login.setUserInformation(userInformationRecord);
+              }
+            };
+            requestFactory.userInformationRequest().getCurrentUserInformation(
+                Location.getHref()).fire(receiver);
+          }
+        } );
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java
index b4d0ac6..d540149 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java
@@ -20,6 +20,7 @@
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.requestfactory.client.LoginWidget;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
@@ -52,16 +53,16 @@
   private MobileExpenseEntry expenseEntry;
   private MobileReportEntry reportEntry;
 
-  private final long employeeId;
+  private final EmployeeRecord employee;
   private final HandlerManager eventBus;
   private final ExpensesRequestFactory requestFactory;
   private ArrayList<MobilePage> pages = new ArrayList<MobilePage>();
 
   public ExpensesMobileShell(HandlerManager eventBus,
-      ExpensesRequestFactory requestFactory, long employeeId) {
+      ExpensesRequestFactory requestFactory, EmployeeRecord employee) {
     this.eventBus = eventBus;
     this.requestFactory = requestFactory;
-    this.employeeId = employeeId;
+    this.employee = employee;
 
     initWidget(BINDER.createAndBindUi(this));
     showReportList();
@@ -151,11 +152,11 @@
     pushPage(reportEntry);
   }
 
-  private void showExpenseList(ReportRecord report) {
+  private void showExpenseList(final ReportRecord report) {
     if (expenseList == null) {
       expenseList = new MobileExpenseList(new MobileExpenseList.Listener() {
-        public void onCreateExpense(Long reportId) {
-          showNewExpenseEntry(reportId);
+        public void onCreateExpense(ReportRecord report) {
+          showNewExpenseEntry(report);
         }
 
         public void onEditReport(ReportRecord report) {
@@ -172,7 +173,7 @@
     pushPage(expenseList);
   }
 
-  private void showNewExpenseEntry(Long reportId) {
+  private void showNewExpenseEntry(ReportRecord report) {
     if (expenseEntry == null) {
       expenseEntry = new MobileExpenseEntry(new MobileExpenseEntry.Listener() {
         public void onExpenseUpdated() {
@@ -181,11 +182,11 @@
       }, requestFactory);
     }
 
-    expenseEntry.create(reportId);
+    expenseEntry.create(report);
     pushPage(expenseEntry);
   }
 
-  private void showNewReportEntry(Long reporterId) {
+  private void showNewReportEntry(EmployeeRecord reporter) {
     if (reportEntry == null) {
       reportEntry = new MobileReportEntry(new MobileReportEntry.Listener() {
         public void onReportUpdated() {
@@ -194,7 +195,7 @@
       }, requestFactory);
     }
 
-    reportEntry.create(reporterId);
+    reportEntry.create(reporter);
     pushPage(reportEntry);
   }
 
@@ -223,14 +224,14 @@
   private void showReportList() {
     if (reportList == null) {
       reportList = new MobileReportList(new MobileReportList.Listener() {
-        public void onCreateReport(Long reporterId) {
-          showNewReportEntry(reporterId);
+        public void onCreateReport(EmployeeRecord reporter) {
+          showNewReportEntry(reporter);
         }
 
         public void onReportSelected(ReportRecord report) {
           showExpenseList(report);
         }
-      }, requestFactory, employeeId);
+      }, requestFactory, employee);
     }
 
     pushPage(reportList);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
index 4c6f3b5..7cd556b 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
@@ -21,6 +21,7 @@
 import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
@@ -72,11 +73,11 @@
     return this;
   }
 
-  public void create(Long reportId) {
+  public void create(ReportRecord report) {
     expense = (ExpenseRecord) requestFactory.create(ExpenseRecord.class);
     requestObject = requestFactory.expenseRequest().persist(expense);
     ExpenseRecord editableExpense = requestObject.edit(expense);
-    editableExpense.setReportId(reportId);
+    editableExpense.setReport(report);
     displayExpense();
   }
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
index d372a55..fb9a08c 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
@@ -51,7 +51,7 @@
    * TODO: doc.
    */
   public interface Listener {
-    void onCreateExpense(Long reportId);
+    void onCreateExpense(ReportRecord report);
 
     void onEditReport(ReportRecord report);
 
@@ -169,7 +169,7 @@
   }
 
   public void onAdd() {
-    listener.onCreateExpense(report.getId());
+    listener.onCreateExpense(report);
   }
 
   public void onCustom() {
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
index a08461d..5791343 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
@@ -19,6 +19,7 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -81,11 +82,11 @@
     return this;
   }
 
-  public void create(Long reporterId) {
+  public void create(EmployeeRecord reporter) {
     report = (ReportRecord) requestFactory.create(ReportRecord.class);
     requestObject = requestFactory.reportRequest().persist(report);
     ReportRecord editableReport = requestObject.edit(report);
-    report.setReporterKey(reporterId);
+    editableReport.setReporter(reporter);
     displayReport();
   }
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
index be83685..5cda91f 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
 import com.google.gwt.user.cellview.client.CellList;
@@ -43,7 +44,7 @@
    * TODO: doc.
    */
   public interface Listener {
-    void onCreateReport(Long reporterId);
+    void onCreateReport(EmployeeRecord reporter);
 
     void onReportSelected(ReportRecord report);
   }
@@ -53,7 +54,7 @@
    */
   private Receiver<List<ReportRecord>> lastReceiver;
 
-  private final Long employeeId;
+  private final EmployeeRecord employee;
   private final Listener listener;
   private final CellList<ReportRecord> reportList;
   private final AsyncListViewAdapter<ReportRecord> reportAdapter;
@@ -61,10 +62,10 @@
   private final ExpensesRequestFactory requestFactory;
 
   public MobileReportList(final Listener listener,
-      final ExpensesRequestFactory requestFactory, long employeeId) {
+      final ExpensesRequestFactory requestFactory, EmployeeRecord employee) {
     this.listener = listener;
     this.requestFactory = requestFactory;
-    this.employeeId = new Long(employeeId);
+    this.employee = employee;
     reportAdapter = new AsyncListViewAdapter<ReportRecord>() {
       @Override
       protected void onRangeChanged(HasData<ReportRecord> view) {
@@ -116,7 +117,7 @@
   }
 
   public void onAdd() {
-    listener.onCreateReport(employeeId);
+    listener.onCreateReport(employee);
   }
 
   public void onCustom() {
@@ -147,7 +148,7 @@
         reportAdapter.updateViewData(0, size, newValues);
       }
     };
-    requestFactory.reportRequest().findReportEntriesBySearch(employeeId, "",
+    requestFactory.reportRequest().findReportEntriesBySearch(employee.getId(), "",
         "", ReportRecord.created.getName() + " DESC", 0, 25).forProperties(
         getReportColumns()).fire(lastReceiver);
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
index 21ac793..ac47217 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
@@ -32,8 +32,8 @@
   Property<String> displayName = new Property<String>("displayName", "Display Name",
       String.class);
   Property<String> password = new Property<String>("password", "Password", String.class);
-  Property<Long> supervisorKey = new Property<Long>("supervisorKey", "Supervisor Key",
-      Long.class);
+  Property<EmployeeRecord> supervisor = new Property<EmployeeRecord>("supervisor", "Supervisor",
+      EmployeeRecord.class);
 
   Property<String> department = new Property<String>("department", "Department", String.class);
 
@@ -43,7 +43,7 @@
 
   String getPassword();
 
-  Long getSupervisorKey();
+  EmployeeRecord getSupervisor();
 
   String getUserName();
 
@@ -55,7 +55,7 @@
 
   void setPassword(String password);
 
-  void setSupervisorKey(Long supervisorKey);
+  void setSupervisor(EmployeeRecord supervisor);
 
   void setUserName(String userName);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
index 268c569..8b9a154 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
@@ -36,7 +36,7 @@
   Property<Date> created = new Property<Date>("created", "Created", Date.class);
   Property<String> description = new Property<String>("description", "Description", String.class);
   Property<String> reasonDenied = new Property<String>("reasonDenied", "Reason Denied", String.class);
-  Property<Long> reportId = new Property<Long>("reportId", "Report Id", Long.class);
+  Property<ReportRecord> report = new Property<ReportRecord>("report", "Report", ReportRecord.class);
 
   Double getAmount();
   
@@ -50,7 +50,7 @@
 
   String getReasonDenied();
 
-  Long getReportId();
+  ReportRecord getReport();
 
   boolean isChanged();
 
@@ -66,5 +66,5 @@
 
   void setReasonDenied(String reasonDenied);
 
-  void setReportId(Long reportId);
+  void setReport(ReportRecord report);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
index d98c4b9..7652744 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
@@ -30,16 +30,16 @@
 @DataTransferObject(com.google.gwt.sample.expenses.server.domain.Report.class)
 public interface ReportRecord extends Record {
 
-  Property<Long> approvedSupervisorKey = new Property<Long>("approvedSupervisorKey", "Approved Supervisor Key",
-      Long.class);
+  Property<EmployeeRecord> approvedSupervisor = new Property<EmployeeRecord>("approvedSupervisor", "Approved Supervisor Key",
+      EmployeeRecord.class);
   Property<Date> created = new Property<Date>("created", "Created", Date.class);
   Property<String> department = new Property<String>("department", "Department", String.class);
   Property<String> notes = new Property<String>("notes", "Notes", String.class);
   Property<String> purpose = new Property<String>("purpose", "Purpose", String.class);
-  Property<Long> reporterKey = new Property<Long>("reporterKey", "Reporter Key",
-      Long.class);
+  Property<EmployeeRecord> reporter = new Property<EmployeeRecord>("reporter", "Reporter",
+      EmployeeRecord.class);
 
-  Long getApprovedSupervisorKey();
+  EmployeeRecord getApprovedSupervisor();
 
   Date getCreated();
 
@@ -49,11 +49,11 @@
 
   String getPurpose();
   
-  Long getReporterKey();
+  EmployeeRecord getReporter();
 
   boolean isChanged();
 
-  void setApprovedSupervisorKey(Long approvedSupervisorKey);
+  void setApprovedSupervisor(EmployeeRecord approvedSupervisor);
 
   void setCreated(Date created);
 
@@ -63,5 +63,5 @@
 
   void setPurpose(String purpose);
 
-  void setReporterKey(Long reporterKey);
+  void setReporter(EmployeeRecord reporter);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java
index 6aa0395..776f790 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java
@@ -55,8 +55,8 @@
       repWriter = new PrintWriter(dir + "/Report.csv");
       expWriter = new PrintWriter(dir + "/Expense.csv");
       empWriter.println("userName,displayName,supervisorKey,VERSION,key,department,password");
-      repWriter.println("created,notes,VERSION,approvedSupervisorKey,key,reporterKey,purposeLowerCase,purpose,department");
-      expWriter.println("category,description,reasonDenied,amount,VERSION,reportId,key,created,approval");
+      repWriter.println("created,notes,VERSION,approvedSupervisor,key,reporter,purposeLowerCase,purpose,department");
+      expWriter.println("category,description,reasonDenied,amount,VERSION,report,key,created,approval");
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -95,7 +95,7 @@
         @Override
         public long storeExpense(ExpenseDTO expense) {
           long id = allids++;
-          // category,description,reasonDenied,amount,VERSION,reportId,key,created,approval"
+          // category,description,reasonDenied,amount,VERSION,report,key,created,approval"
           expWriter.println(expense.category + "," + expense.description + ",," + expense.amount + ","
               + VERSION + "," + expense.reportId + "," + id + "," + dateToString(expense.created)
               + ",");
@@ -105,7 +105,7 @@
         @Override
         public long storeReport(ReportDTO report) {          
           long id = allids++;
-          // created,notes,VERSION,approvedSupervisorKey,key,reporterKey,purposeLowerCase,purpose,department
+          // created,notes,VERSION,approvedSupervisor,key,reporter,purposeLowerCase,purpose,department
           repWriter.println(dateToString(report.created) + ",\"" + report.notes + "\"," + VERSION + ","
               + report.approvedSupervisorKey + "," + id + "," + report.reporterKey + ",\""
               + report.purpose.toLowerCase() + "\",\"" + report.purpose + "\"," + report.department);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
index 6cb007c..1b73e9f 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
@@ -120,6 +120,6 @@
       }
     };
 
-    requests.employeeRequest().findEmployee(Value.of(id)).fire(callback);
+    requests.employeeRequest().findEmployee(Value.of(id)).with("supervisor").fire(callback);
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
index a71f8ed..6bc8acb 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
@@ -52,7 +52,7 @@
   @UiField
   SpanElement password;
   @UiField
-  SpanElement supervisorKey;
+  SpanElement supervisor;
   @UiField
   HasClickHandlers edit;
   @UiField
@@ -95,7 +95,7 @@
     displayName.setInnerText(record.getDisplayName());
     userName.setInnerText(record.getUserName());
     password.setInnerText(record.getPassword());
-    supervisorKey.setInnerText(String.valueOf(record.getSupervisorKey()));
+    supervisor.setInnerText(String.valueOf(record.getSupervisor() == null ? null : record.getSupervisor().getId()));
     idSpan.setInnerText(record.getId().toString());
     versionSpan.setInnerText(record.getVersion().toString());
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml
index bc70a28..2f473d9 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml
@@ -35,7 +35,7 @@
     <table class='{style.fields}'>
       <tr><td><div class='{style.label}'>Display Name:</div></td><td><span ui:field='displayName'></span></td></tr>
       <tr><td><div class='{style.label}'>User Name:</div></td><td><span ui:field='userName'></span></td></tr>
-      <tr><td><div class='{style.label}'>Supervisor Key:</div></td><td><span ui:field='supervisorKey'></span></td></tr>
+      <tr><td><div class='{style.label}'>Supervisor:</div></td><td><span ui:field='supervisor'></span></td></tr>
       <tr><td><div class='{style.label}'>Password:</div></td><td><span ui:field='password'></span></td></tr>
    </table>
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
index f50fe57..e481300 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
-import com.google.gwt.app.client.LongRenderer;
+import com.google.gwt.app.client.ProxyIdRenderer;
 import com.google.gwt.app.place.AbstractRecordListView;
 import com.google.gwt.app.place.PropertyColumn;
 import com.google.gwt.core.client.GWT;
@@ -53,8 +53,8 @@
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.userName));
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.displayName));
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.password));
-    columns.add(new PropertyColumn<EmployeeRecord, java.lang.Long>(
-        EmployeeRecord.supervisorKey, LongRenderer.instance()));
+    columns.add(new PropertyColumn<EmployeeRecord, EmployeeRecord>(
+        EmployeeRecord.supervisor, ProxyIdRenderer.<EmployeeRecord>instance()));
     return columns;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
index a7c1163..d7c7770 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
@@ -119,6 +119,7 @@
         display.showActivityWidget(view);
       }
     };
-    requests.reportRequest().findReport(Value.of(id)).fire(callback);
+    requests.reportRequest().findReport(Value.of(id)).with(
+        "reporter", "approvedSupervisor").fire(callback);
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
index 11190e9..7c2fed6 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
@@ -99,7 +99,7 @@
     created.setInnerText(new DateTimeFormatRenderer().render(record.getCreated()));
     idSpan.setInnerText(record.getId().toString());
     versionSpan.setInnerText(record.getVersion().toString());
-    reporterKey.setInnerText(String.valueOf(record.getReporterKey()));
-    approvedSupervisorKey.setInnerText(String.valueOf(record.getApprovedSupervisorKey()));
+    reporterKey.setInnerText(String.valueOf(record.getReporter()));
+    approvedSupervisorKey.setInnerText(String.valueOf(record.getApprovedSupervisor()));
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
index 559886c..0c4c0ac 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
@@ -15,12 +15,13 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
-import com.google.gwt.app.client.LongRenderer;
+import com.google.gwt.app.client.ProxyIdRenderer;
 import com.google.gwt.app.place.AbstractRecordListView;
 import com.google.gwt.app.place.PropertyColumn;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.i18n.client.DateTimeFormat;
 import com.google.gwt.i18n.client.DateTimeFormatRenderer;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
@@ -58,10 +59,10 @@
     columns.add(new PropertyColumn<ReportRecord, Date>(ReportRecord.created,
         new DateTimeFormatRenderer(DateTimeFormat.getShortDateFormat())));
     columns.add(PropertyColumn.<ReportRecord> getStringPropertyColumn(ReportRecord.purpose));
-    columns.add(new PropertyColumn<ReportRecord, java.lang.Long>(
-        ReportRecord.reporterKey, LongRenderer.instance()));
-    columns.add(new PropertyColumn<ReportRecord, java.lang.Long>(
-        ReportRecord.approvedSupervisorKey, LongRenderer.instance()));
+    columns.add(new PropertyColumn<ReportRecord, EmployeeRecord>(
+        ReportRecord.reporter, ProxyIdRenderer.<EmployeeRecord>instance()));
+    columns.add(new PropertyColumn<ReportRecord, EmployeeRecord>(
+        ReportRecord.approvedSupervisor, ProxyIdRenderer.<EmployeeRecord>instance()));
     return columns;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java b/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java
index fdf218c..ef2fde3 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java
@@ -206,7 +206,7 @@
   private JSONObject getExpenseAsJson(Expense expense) throws JSONException {
     JSONObject jsonObject = new JSONObject();
     jsonObject.put("id", "" + expense.getId());
-    jsonObject.put("reportId", expense.getReportId());
+    jsonObject.put("report", expense.getReportId());
     jsonObject.put("description", expense.getDescription());
     jsonObject.put("created", expense.getCreated().getTime());
     jsonObject.put("amount", expense.getAmount());
@@ -239,12 +239,12 @@
     jsonObject.put("id", "" + report.getId());
     jsonObject.put("created", report.getCreated().getTime());
     jsonObject.put("department", report.getDepartment());
-    jsonObject.put("reporterKey", report.getReporterKey());
+    jsonObject.put("reporter", report.getReporterKey());
     jsonObject.put("purpose", report.getPurpose());
     jsonObject.put("notes", report.getNotes());
     Long approvedSupervisorKey = report.getApprovedSupervisorKey();
     if (approvedSupervisorKey != null) {
-      jsonObject.put("approvedSupervisorKey", "" + report.getApprovedSupervisorKey());
+      jsonObject.put("approvedSupervisor", "" + report.getApprovedSupervisorKey());
     }
     return jsonObject;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
index 28b2cdf..c7802b0 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
@@ -24,6 +24,7 @@
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Query;
+import javax.persistence.Transient;
 import javax.persistence.Version;
 import javax.validation.constraints.NotNull;
 import javax.validation.constraints.Size;
@@ -138,6 +139,9 @@
   @Column(name = "version")
   private Integer version;
 
+  @Transient
+  private Employee supervisor;
+
   public String getDepartment() {
     return department;
   }
@@ -154,6 +158,10 @@
     return this.password;
   }
 
+  public Employee getSupervisor() {
+    return supervisorKey != null ? findEmployee(supervisorKey) : null;
+  }
+
   public Long getSupervisorKey() {
     return supervisorKey;
   }
@@ -201,6 +209,10 @@
     this.password = password;
   }
 
+  public void setSupervisor(Employee supervisor) {
+    supervisorKey = supervisor == null ? null : supervisor.getId();
+  }
+
   public void setSupervisorKey(Long supervisorKey) {
     this.supervisorKey = supervisorKey;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java
index d0034a0..4ca3206 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java
@@ -26,6 +26,7 @@
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Query;
+import javax.persistence.Transient;
 import javax.persistence.Version;
 
 /**
@@ -59,7 +60,7 @@
       em.close();
     }
   }
-  
+
   public static Expense findExpense(Long id) {
     if (id == null) {
       return null;
@@ -71,7 +72,7 @@
       em.close();
     }
   }
-  
+
   @SuppressWarnings("unchecked")
   public static List<Expense> findExpensesByReport(Long reportId) {
     EntityManager em = entityManager();
@@ -98,17 +99,21 @@
   private String category;
 
   private Date created;
-  
+
   private String description;
-  
+
   @Id
   @Column(name = "id")
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;
-  
+
   private String reasonDenied;
-  
+
+  @Transient
+  private Report report;
+
   // @JoinColumn
+
   private Long reportId;
 
   @Version
@@ -122,7 +127,7 @@
   public String getApproval() {
     return this.approval;
   }
-  
+
   public String getCategory() {
     return this.category;
   }
@@ -134,15 +139,19 @@
   public String getDescription() {
     return description;
   }
-  
+
   public Long getId() {
     return this.id;
   }
-  
+
   public String getReasonDenied() {
     return this.reasonDenied;
   }
 
+  public Report getReporter() {
+    return reportId != null ? Report.findReport(reportId) : null;
+  }
+
   public Long getReportId() {
     return this.reportId;
   }
@@ -169,11 +178,11 @@
       em.close();
     }
   }
-  
+
   public void setAmount(Double amount) {
     this.amount = amount;
   }
-  
+
   public void setApproval(String approval) {
     this.approval = approval;
   }
@@ -198,8 +207,12 @@
     this.reasonDenied = reasonDenied;
   }
 
+  public void setReport(Report report) {
+    reportId = report == null ? null : report.getId();
+  }
+
   public void setReportId(Long reportId) {
-    this.reportId = reportId; 
+    this.reportId = reportId;
   }
 
   public void setVersion(Integer version) {
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
index 7d7378d..ac574e1 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
@@ -36,6 +36,7 @@
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Query;
+import javax.persistence.Transient;
 import javax.persistence.Version;
 
 /**
@@ -66,8 +67,8 @@
       String startsWith) {
     EntityManager em = entityManager();
     try {
-      Query query = queryReportsBySearch(em, employeeId, department,
-          startsWith, null, true);
+      Query query = queryReportsBySearch(em, employeeId, department, startsWith,
+          null, true);
       if (query == null) {
         return REPORT_COUNT;
       }
@@ -108,11 +109,13 @@
   }
 
   @SuppressWarnings("unchecked")
-  public static List<Report> findReportEntries(int firstResult, int maxResults) {
+  public static List<Report> findReportEntries(int firstResult,
+      int maxResults) {
     EntityManager em = entityManager();
     try {
-      List<Report> reportList = em.createQuery("select o from Report o").setFirstResult(
-          firstResult).setMaxResults(maxResults).getResultList();
+      List<Report> reportList = em.createQuery(
+          "select o from Report o").setFirstResult(firstResult).setMaxResults(
+          maxResults).getResultList();
       // force it to materialize
       reportList.size();
       return reportList;
@@ -127,8 +130,8 @@
       int maxResults) {
     EntityManager em = entityManager();
     try {
-      Query query = queryReportsBySearch(em, employeeId, department,
-          startsWith, orderBy, false);
+      Query query = queryReportsBySearch(em, employeeId, department, startsWith,
+          orderBy, false);
 
       // Try to get the memcache
       if (cache == null) {
@@ -176,7 +179,7 @@
         query.setHint(JPACursorHelper.CURSOR_HINT, trialCursor);
         while (firstResult > pos) {
           int min = Math.min(firstResult - pos, 1000);
-          
+
           // If we need to skip more than 1000 records, ensure the
           // breaks occur at multiples of 1000 in order to increase the
           // chances of reusing cursors from the memcache
@@ -184,7 +187,7 @@
             int mod = (pos + min) % 1000;
             min -= mod;
           }
-          
+
           query.setMaxResults(min);
           List<Report> results = query.getResultList();
           int count = results.size();
@@ -192,7 +195,7 @@
             break;
           }
           pos += count;
-          
+
           // Save the cursor for later
           Cursor cursor = JPACursorHelper.getCursor(results);
           if (cache != null) {
@@ -200,24 +203,25 @@
                 pos);
             cache.put(key, cursor.toWebSafeString());
           }
-          
+
           query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
         }
       }
-      
+
       query.setMaxResults(maxResults);
 
       List<Report> reportList = query.getResultList();
       // force it to materialize
       reportList.size();
-      
+
       Cursor cursor = JPACursorHelper.getCursor(reportList);
       if (cache != null) {
         int pos = firstResult + reportList.size();
-        String key = createKey(employeeId, department, startsWith, orderBy, pos);
+        String key = createKey(employeeId, department, startsWith, orderBy,
+            pos);
         cache.put(key, cursor.toWebSafeString());
       }
-      
+
       return reportList;
     } finally {
       em.close();
@@ -228,7 +232,8 @@
   public static List<Report> findReportsByEmployee(Long employeeId) {
     EntityManager em = entityManager();
     try {
-      Query query = em.createQuery("select o from Report o where o.reporterKey =:reporterKey");
+      Query query = em.createQuery(
+          "select o from Report o where o.reporterKey =:reporterKey");
       query.setParameter("reporterKey", employeeId);
       List<Report> reportList = query.getResultList();
       // force it to materialize
@@ -241,22 +246,19 @@
 
   private static String createKey(Long employeeId, String department,
       String startsWith, String orderBy, int firstResult) {
-    return "" + employeeId + "+" + encode(department) + "+"
-        + encode(startsWith) + "+" + encode(orderBy) + "+" + firstResult;
+    return "" + employeeId + "+" + encode(department) + "+" + encode(startsWith)
+        + "+" + encode(orderBy) + "+" + firstResult;
   }
 
   /**
    * Returns a String based on an input String that provides the following
    * guarantees.
-   * 
-   * <ol>
-   * <li>The result contains no '+' characters
-   * <li>Distinct inputs always produce distinct results
-   * </ol>
-   * 
-   * <p>
-   * Note that the transformation is not required to be reversible.
-   * 
+   *
+   * <ol> <li>The result contains no '+' characters <li>Distinct inputs always
+   * produce distinct results </ol>
+   *
+   * <p> Note that the transformation is not required to be reversible.
+   *
    * @param s the input String
    * @return a String suitable for use as part of a a memcache key
    */
@@ -272,13 +274,13 @@
   /**
    * Query for reports based on the search parameters. If startsWith is
    * specified, the results will not be ordered.
-   * 
-   * @param em the {@link EntityManager} to use
+   *
+   * @param em         the {@link EntityManager} to use
    * @param employeeId the employee id
    * @param department the department to search
    * @param startsWith the starting string
-   * @param orderBy the order of the results
-   * @param isCount true to query on the count only
+   * @param orderBy    the order of the results
+   * @param isCount    true to query on the count only
    * @return the query, or null to return full report count.
    */
   private static Query queryReportsBySearch(EntityManager em, Long employeeId,
@@ -335,7 +337,14 @@
     return query;
   }
 
+  @Transient
+  private Employee approvedSupervisor;
+
+  @Transient
+  private Employee reporter;
+
   // @JoinColumn
+
   private Long approvedSupervisorKey;
 
   private Date created;
@@ -358,8 +367,7 @@
   private String purposeLowerCase;
 
   /**
-   * Store reporter's key instead of reporter. See:
-   * http://code.google.com/appengine
+   * Store reporter's key instead of reporter. See: http://code.google.com/appengine
    * /docs/java/datastore/relationships.html#Unowned_Relationships
    */
   // @JoinColumn
@@ -369,6 +377,11 @@
   @Column(name = "version")
   private Integer version;
 
+  public Employee getApprovedSupervisor() {
+    return approvedSupervisorKey != null ? Employee.findEmployee(
+        approvedSupervisorKey) : null;
+  }
+
   public Long getApprovedSupervisorKey() {
     return approvedSupervisorKey;
   }
@@ -393,6 +406,10 @@
     return this.purpose;
   }
 
+  public Employee getReporter() {
+    return reporterKey != null ? Employee.findEmployee(reporterKey) : null;
+  }
+
   public Long getReporterKey() {
     return this.reporterKey;
   }
@@ -445,10 +462,18 @@
     this.purposeLowerCase = purpose == null ? "" : purpose.toLowerCase();
   }
 
+  public void setReporter(Employee reporter) {
+    reporterKey = reporter == null ? null : reporter.getId();
+  }
+
   public void setReporterKey(Long reporter) {
     this.reporterKey = reporter;
   }
 
+  public void setSupervisor(Employee supervisor) {
+    approvedSupervisorKey = supervisor == null ? null : supervisor.getId();
+  }
+
   public void setVersion(Integer version) {
     this.version = version;
   }
@@ -466,4 +491,5 @@
     sb.append("ApprovedSupervisor: ").append(getApprovedSupervisorKey());
     return sb.toString();
   }
+
 }
diff --git a/user/src/com/google/gwt/app/client/NullParser.java b/user/src/com/google/gwt/app/client/NullParser.java
new file mode 100644
index 0000000..e37ea84
--- /dev/null
+++ b/user/src/com/google/gwt/app/client/NullParser.java
@@ -0,0 +1,51 @@
+/*
+ * 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.app.client;
+
+import com.google.gwt.text.shared.Parser;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.text.ParseException;
+
+/**
+ * <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 no-op renderer, always returns null
+ * @param <T> a Record type.
+ */
+public class NullParser<T extends Record> implements Parser<T> {
+
+  private static NullParser INSTANCE;
+
+  /**
+   * @return the instance of the null parser
+   */
+  public static <T extends Record> Parser<T> instance() {
+    if (INSTANCE == null) {
+      INSTANCE = new NullParser<T>();
+    }
+    return INSTANCE;
+  }
+
+  protected NullParser() {
+  }
+
+  public T parse(CharSequence object) throws ParseException {
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/app/client/ProxyBox.java b/user/src/com/google/gwt/app/client/ProxyBox.java
new file mode 100644
index 0000000..30410a9
--- /dev/null
+++ b/user/src/com/google/gwt/app/client/ProxyBox.java
@@ -0,0 +1,43 @@
+/*
+ * 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.app.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.ui.ValueBox;
+import com.google.gwt.valuestore.shared.Record;
+
+/**
+ * <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 ValueBox that uses {@link com.google.gwt.app.client.ProxyParser} and
+ * {@link com.google.gwt.app.client.ProxyRenderer}.
+ *
+ * @param <T> a proxy record
+ */
+public class ProxyBox<T extends Record> extends ValueBox<T> {
+
+  public ProxyBox() {
+    super(Document.get().createTextInputElement(), ProxyRenderer.<T>instance(),
+        ProxyParser.<T>instance());
+  }
+
+  public T getValue() {
+    // Until a sensible ProxyParser can be written
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/app/client/ProxyIdRenderer.java b/user/src/com/google/gwt/app/client/ProxyIdRenderer.java
new file mode 100644
index 0000000..239ecf8
--- /dev/null
+++ b/user/src/com/google/gwt/app/client/ProxyIdRenderer.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.app.client;
+
+import com.google.gwt.text.shared.AbstractRenderer;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.valuestore.shared.Record;
+
+/**
+ * <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>
+ * Renderer of Record values
+ * @param <T> a record type
+ */
+public class ProxyIdRenderer<T extends Record> extends AbstractRenderer<T> {
+  private static ProxyIdRenderer INSTANCE;
+
+  /**
+   * @return the instance
+   */
+  public static <T extends Record> Renderer<T> instance() {
+    if (INSTANCE == null) {
+      INSTANCE = new ProxyIdRenderer<T>();
+    }
+    return INSTANCE;
+  }
+
+  protected ProxyIdRenderer() {
+  }
+
+  public String render(T object) {
+    return toString(object == null ? null : object.getId());
+  }
+}
diff --git a/user/src/com/google/gwt/app/client/ProxyParser.java b/user/src/com/google/gwt/app/client/ProxyParser.java
new file mode 100644
index 0000000..221a4f0
--- /dev/null
+++ b/user/src/com/google/gwt/app/client/ProxyParser.java
@@ -0,0 +1,57 @@
+/*
+ * 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.app.client;
+
+import com.google.gwt.text.shared.Parser;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.text.ParseException;
+
+/**
+ * <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 no-op renderer.
+ * @param <T> a Record type.
+ */
+public class ProxyParser<T extends Record> implements Parser<T> {
+
+  private static ProxyParser INSTANCE;
+
+  /**
+   * @return the instance of the no-op renderer
+   */
+  public static <T extends Record> Parser<T> instance() {
+    if (INSTANCE == null) {
+      INSTANCE = new ProxyParser<T>();
+    }
+    return INSTANCE;
+  }
+
+  protected ProxyParser() {
+  }
+
+  public T parse(CharSequence object) throws ParseException {
+    try {
+      // TODO: how can we map an id back into a record synchronously? Use
+      // ValueStore?
+      return null;
+    } catch (NumberFormatException e) { 
+      throw new ParseException(e.getMessage(), 0);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/app/client/ProxyRenderer.java b/user/src/com/google/gwt/app/client/ProxyRenderer.java
new file mode 100644
index 0000000..5ea3d4f
--- /dev/null
+++ b/user/src/com/google/gwt/app/client/ProxyRenderer.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.app.client;
+
+import com.google.gwt.text.shared.AbstractRenderer;
+import com.google.gwt.text.shared.Renderer;
+import com.google.gwt.valuestore.shared.Record;
+
+/**
+ * <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>
+ * Renderer of Record values
+ * @param <T> a record type
+ */
+public class ProxyRenderer<T extends Record> extends AbstractRenderer<T> {
+  private static ProxyRenderer INSTANCE;
+
+  /**
+   * @return the instance
+   */
+  public static <T extends Record> Renderer<T> instance() {
+    if (INSTANCE == null) {
+      INSTANCE = new ProxyRenderer<T>();
+    }
+    return INSTANCE;
+  }
+
+  protected ProxyRenderer() {
+  }
+
+  public String render(T object) {
+    return toString(object == null ? null : object.getId());
+  }
+}
diff --git a/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java b/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java
new file mode 100644
index 0000000..cafe722
--- /dev/null
+++ b/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java
@@ -0,0 +1,51 @@
+/*
+ * 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.app.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.ui.ValueBox;
+import com.google.gwt.valuestore.shared.Record;
+
+/**
+ * <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 ValueBox that uses {@link NullParser} and
+ * {@link ProxyIdRenderer}. It is a display only placeholder class for now;
+ *
+ * @param <T> a proxy record
+ */
+public class ReadonlyProxyBox<T extends Record> extends ValueBox<T> {
+
+  private T currentValue;
+
+  public ReadonlyProxyBox() {
+    super(Document.get().createTextInputElement(), ProxyIdRenderer.<T>instance(),
+        NullParser.<T>instance());
+    setReadOnly(true);
+  }
+
+  public T getValue() {
+    // The display value cannot be modified;
+    return currentValue;
+  }
+
+  public void setValue(T value) {
+    this.currentValue = value;
+    super.setValue(value);
+  }
+}
diff --git a/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java b/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
index 9df2bbe..f8bc60f 100644
--- a/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
+++ b/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
@@ -36,6 +36,7 @@
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Record;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -90,6 +91,7 @@
   JClassType takesValueType;
   JClassType hasTextType;
 
+  JClassType jrecordType;
   JClassType stringType;
 
   private String capitalize(String name) {
@@ -180,7 +182,8 @@
         HasText.class.getName());
     stringType = generatorContext.getTypeOracle().findType(
         String.class.getName());
-
+    recordType = generatorContext.getTypeOracle().findType(
+        Record.class.getName());
     writeGetPropertiesMethod(sw, recordType);
     writeInit(sw, viewType, recordType);
     writeIsChangedMethod(sw, recordType, viewType);
@@ -303,6 +306,9 @@
     if (returnType.isAssignableTo(stringType)) {
       return "";
     }
+    if (returnType.isAssignableTo(jrecordType)) {
+      return ".getId()+\"\"";
+    }
     return ".toString()";
   }
 
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java
index 461303b..0c60cf5 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java
@@ -50,7 +50,9 @@
   }
 
   public void handleResponseText(String text) {
-    JsArray<RecordJsoImpl> valueJsos = RecordJsoImpl.arrayFromJson(text);
+    RecordJsoImpl.JsonResults results = RecordJsoImpl.fromResults(text);
+
+    JsArray<RecordJsoImpl> valueJsos = results.getListResult();
     List<T> valueList = new ArrayList<T>(valueJsos.length());
     for (int i = 0; i < valueJsos.length(); i++) {
       RecordJsoImpl jso = valueJsos.get(i);
@@ -58,7 +60,9 @@
       valueList.add(schema.create(jso));
     }
 
-    requestFactory.getValueStore().setRecords(valueJsos);
+    requestFactory.getValueStore().setRecords(valueJsos, requestFactory);
+    processRelated(results.getRelated());
+
     receiver.onSuccess(valueList, Collections.<SyncResult> emptySet());
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java
index 903458f..41c0864 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java
@@ -22,22 +22,19 @@
 import java.util.Collections;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
+ * <p> <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * Abstract implementation of
- * {@link com.google.gwt.requestfactory.shared.RequestObject
+ * </span> </p> Abstract implementation of {@link com.google.gwt.requestfactory.shared.RequestObject
  * RequestFactory.RequestObject} for requests that return single instances of
  * {@link Record}.
- * 
+ *
  * @param <T> the type of entities returned
  * @param <R> this request type
  */
 public abstract class //
-AbstractJsonObjectRequest<T extends Record, R extends AbstractJsonObjectRequest<T, R>> //
+    AbstractJsonObjectRequest<T extends Record, R extends AbstractJsonObjectRequest<T, R>> //
     extends AbstractRequest<T, R> implements RecordRequest<T> {
+
   protected final RecordSchema<? extends T> schema;
 
   public AbstractJsonObjectRequest(RecordSchema<? extends T> schema,
@@ -47,11 +44,15 @@
   }
 
   public void handleResponseText(String text) {
-    RecordJsoImpl jso = RecordJsoImpl.fromJson(text);
+    RecordJsoImpl.JsonResults results = RecordJsoImpl.fromResults(text);
+
+    RecordJsoImpl jso = results.getResult();
     jso.setSchema(schema);
 
-    requestFactory.getValueStore().setRecord(jso);
-    receiver.onSuccess(schema.create(jso),
-        Collections.<SyncResult> emptySet());
+    requestFactory.getValueStore().setRecord(jso, requestFactory);
+    processRelated(results.getRelated());
+
+    receiver.onSuccess(schema.create(jso), Collections.<SyncResult>emptySet());
   }
+
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
index f7c0631..9a9bfaf 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.requestfactory.client.impl;
 
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.valuestore.shared.Property;
@@ -26,8 +27,7 @@
 import java.util.Set;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
+ * <p> <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
  * </span>
  * </p>
@@ -44,7 +44,7 @@
   protected DeltaValueStoreJsonImpl deltaValueStore;
   protected Receiver<T> receiver;
 
-  private final Set<Property<?>> properties = new HashSet<Property<?>>();
+  private final Set<String> propertyRefs = new HashSet<String>();
 
   public AbstractRequest(RequestFactoryJsonImpl requestFactory) {
     this.requestFactory = requestFactory;
@@ -76,21 +76,23 @@
     requestFactory.fire(this);
   }
 
+  /**
+   * @deprecated use {@link #with(String...)} instead.
+   * @param properties
+   * @return
+   */
   public R forProperties(Collection<Property<?>> properties) {
-    this.properties.addAll(properties);
-    return getThis();
-  }
-
-  public R forProperty(Property<?> property) {
-    this.properties.add(property);
+    for (Property p : properties) {
+      with(p.getName());
+    }
     return getThis();
   }
 
   /**
    * @return the properties
    */
-  public Set<Property<?>> getProperties() {
-    return Collections.unmodifiableSet(properties);
+  public Set<String> getPropertyRefs() {
+    return Collections.unmodifiableSet(propertyRefs);
   }
 
   public boolean isChanged() {
@@ -102,10 +104,29 @@
     deltaValueStore = new DeltaValueStoreJsonImpl(valueStore, requestFactory);
   }
 
+  public void setSchemaAndRecord(String schemaToken, RecordJsoImpl jso) {
+    jso.setSchema(requestFactory.getSchema(schemaToken));
+    requestFactory.getValueStore().setRecord(jso, requestFactory);
+  }
+
+  public R with(String... propertyRef) {
+    for (String ref : propertyRef) {
+      propertyRefs.add(ref);
+    }
+    return getThis();
+  }
+
   /**
    * Subclasses must override to return {@code this}, to allow builder-style
    * methods to do the same.
    */
   protected abstract R getThis();
 
+  protected native void processRelated(JavaScriptObject related) /*-{
+    for(var recordKey in related) {
+      var schemaAndId = recordKey.split(/-/, 2);
+      var jso = related[recordKey];
+      this.@com.google.gwt.requestfactory.client.impl.AbstractRequest::setSchemaAndRecord(Ljava/lang/String;Lcom/google/gwt/requestfactory/client/impl/RecordJsoImpl;)(schemaAndId[0], jso);
+    }
+  }-*/;
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java
index 8f6deb3..43b4121 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java
@@ -50,7 +50,7 @@
   public <V> V get(Property<V> property) {
     return jso.get(property);
   }
-
+  
   public Long getId() {
     return jso.getId();
   }
@@ -95,4 +95,8 @@
     this.deltaValueStore = deltaValueStore;
   }
 
+  protected ValueStoreJsonImpl getValueStore() {
+    return jso.getValueStore();
+  }
+
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
index f530947..feec11b 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
@@ -28,15 +28,34 @@
 import java.util.Date;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
+ * <p> <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * JSO implementation of {@link Record}, used to back subclasses of
+ * </span> </p> JSO implementation of {@link Record}, used to back subclasses of
  * {@link RecordImpl}.
  */
 public class RecordJsoImpl extends JavaScriptObject implements Record {
+
+  /**
+   * JSO to hold result and related objects.
+   */
+  public static class JsonResults extends JavaScriptObject {
+
+    protected JsonResults() {
+    }
+
+    public final native JsArray<RecordJsoImpl> getListResult() /*-{
+      return this.result;
+    }-*/;
+
+    public final native JavaScriptObject getRelated() /*-{
+      return this.related;
+    }-*/;
+
+    public final native RecordJsoImpl getResult() /*-{
+      return this.result;
+    }-*/;
+  }
+
   public static native JsArray<RecordJsoImpl> arrayFromJson(String json) /*-{
     return eval(json);
   }-*/;
@@ -64,7 +83,14 @@
     return xyz;
   }-*/;
 
+  public static native JsonResults fromResults(String json) /*-{
+    // TODO: clean this
+    eval("xyz=" + json);
+    return xyz;
+  }-*/;
+
   /* Made protected, for testing */
+
   protected static native RecordJsoImpl create() /*-{
     return {};
   }-*/;
@@ -88,7 +114,10 @@
     }
     try {
       if (Boolean.class.equals(property.getType())) {
-      return (V) Boolean.valueOf(getBoolean(property.getName()));
+        return (V) Boolean.valueOf(getBoolean(property.getName()));
+      }
+      if (Character.class.equals(property.getType())) {
+        return (V) Character.valueOf(String.valueOf(get(property.getName())).charAt(0));
       }
       if (Byte.class.equals(property.getType())) {
         return (V) Byte.valueOf((byte) getInt(property.getName()));
@@ -130,8 +159,9 @@
         }
       }
     } catch (final Exception ex) {
-      throw new IllegalStateException("Property  " + property.getName() + " has invalid " +
-      " value " + get(property.getName()) + " for type " + property.getType());
+      throw new IllegalStateException(
+          "Property  " + property.getName() + " has invalid " + " value "
+              + get(property.getName()) + " for type " + property.getType());
     }
 
     if (property instanceof EnumProperty) {
@@ -144,13 +174,28 @@
         }
       }
     }
-    
-    // Sun JDK compile fails without this cast
-    return (V) get(property.getName());
+
+    if (String.class == property.getType()) {
+      return (V) get(property.getName());
+    }
+    // at this point, we checked all the known primitive/value types we support
+    // TODO: handle embedded types, List, Set, Map
+
+    // else, it must be a record type
+    String relatedId = (String) get(property.getName());
+    if (relatedId == null) {
+      return null;
+    } else {
+      // TODO: should cache this or modify JSO field in place
+      String schemaAndId[] = relatedId.split("-");
+      return (V) getValueStore().getRecordBySchemaAndId(
+          getRequestFactory().getSchema(schemaAndId[0]),
+          Long.valueOf(schemaAndId[1]));
+    }
   }
 
   public final native <T> T get(String propertyName) /*-{
-    return this[propertyName];
+    return this[propertyName] || null;
   }-*/;
 
   public final Long getId() {
@@ -160,10 +205,20 @@
   public final <V> PropertyReference<V> getRef(Property<V> property) {
     return new PropertyReference<V>(this, property);
   }
+  
+  // TODO: HACK! Need to look up token to schema for relatins
+  public final native RequestFactoryJsonImpl getRequestFactory() /*-{
+    return this['__rf'];
+  }-*/;
 
   public final native RecordSchema<?> getSchema() /*-{
     return this['__key'];
   }-*/;
+  
+  // TODO: HACK! Make VS public and stash it in the record for relation lookups
+  public final native ValueStoreJsonImpl getValueStore() /*-{
+     return this['__vs'];  
+  }-*/;
 
   public final Integer getVersion() {
     return this.get(version);
@@ -178,8 +233,8 @@
 
   public final boolean isEmpty() {
     for (Property<?> property : getSchema().allProperties()) {
-      if ((property != Record.id) && (property != Record.version)
-          && (isDefined(property.getName()))) {
+      if ((property != Record.id) && (property != Record.version) && (isDefined(
+          property.getName()))) {
         return false;
       }
     }
@@ -211,12 +266,19 @@
       setString(property.getName(), (String) value);
       return;
     }
-    if (value instanceof Long || value instanceof BigDecimal || value instanceof BigInteger) {
+     if (value instanceof Character) {
       setString(property.getName(), String.valueOf(value));
       return;
     }
 
-    if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
+    if (value instanceof Long || value instanceof BigDecimal
+        || value instanceof BigInteger) {
+      setString(property.getName(), String.valueOf(value));
+      return;
+    }
+
+    if (value instanceof Integer || value instanceof Short
+        || value instanceof Byte) {
       setInt(property.getName(), ((Number) value).intValue());
       return;
     }
@@ -236,17 +298,39 @@
       return;
     }
 
-    throw new UnsupportedOperationException("Can't yet set properties of type "
-        + value.getClass().getName());
+    if (value instanceof Boolean) {
+      setBoolean(property.getName(), ((Boolean) value).booleanValue());
+    }
+
+    if (value instanceof Record) {
+      setString(property.getName(),
+          getRequestFactory().getSchema(value.getClass().getName()).getToken().getName()
+              + "-" + String.valueOf(((Record) value).getId()));  
+    }
+
+    throw new UnsupportedOperationException(
+        "Can't yet set properties of type " + value.getClass().getName());
   }
 
+
+  // TODO: HACK! Need to look up token to schema for relatins
+  public final native void setRequestFactory(RequestFactoryJsonImpl requestFactory) /*-{
+    this['__rf'] = requestFactory;
+  }-*/;
+
   public final native void setSchema(RecordSchema<?> schema) /*-{
     this['__key'] = schema;
   }-*/;
 
+  // TODO: HACK! Make VS public and stash it in the record for relation lookups
+
+  public final native void setValueStore(ValueStoreJsonImpl valueStoreJson) /*-{
+    this['__vs']=valueStoreJson;
+  }-*/;
+
   /**
    * Return JSON representation using org.json library.
-   * 
+   *
    * @return returned string.
    */
   public final native String toJson() /*-{
@@ -271,7 +355,7 @@
   /**
    * Return JSON representation of just id and version fields, using org.json
    * library.
-   * 
+   *
    * @return returned string.
    */
   public final native String toJsonIdVersion() /*-{
@@ -287,7 +371,8 @@
     return $wnd.JSON.stringify(object);
   }-*/;
 
-  private native boolean copyPropertyIfDifferent(String name, RecordJsoImpl from) /*-{
+  private native boolean copyPropertyIfDifferent(String name,
+      RecordJsoImpl from) /*-{
     if (this[name] == from[name]) {
       return false;
     }
@@ -311,6 +396,10 @@
     return this[name];
   }-*/;
 
+  private native void setBoolean(String name, boolean value) /*-{
+    this[name] = value;
+  }-*/;
+
   private native void setDouble(String name, double value) /*-{
     this[name] = value;
   }-*/;
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java
index 7559688..3b1c109 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java
@@ -28,4 +28,5 @@
  */
 public interface RecordToTypeMap {
   RecordSchema<? extends Record> getType(Class<? extends Record> recordClass);
+  RecordSchema<? extends Record> getType(String token);
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
index 8f3a818..898eae5 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
@@ -120,6 +120,8 @@
           e);
     }
   }
+  
+  public abstract RecordSchema getSchema(String token);
 
   /**
    * @param handlerManager
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
index d523a71..cff570e 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.valuestore.shared.Record;
 import com.google.gwt.valuestore.shared.WriteOperation;
 
 import java.util.HashMap;
@@ -29,7 +30,7 @@
  * </span>
  * </p>
  */
-class ValueStoreJsonImpl {
+public class ValueStoreJsonImpl {
   // package protected fields for use by DeltaValueStoreJsonImpl
 
   final HandlerManager eventBus;
@@ -40,14 +41,26 @@
     this.eventBus = eventBus;
   }
 
-  public void setRecord(RecordJsoImpl newRecord) {
-    setRecordInList(newRecord, 0, null);
+  public Record getRecordBySchemaAndId(RecordSchema<?> schema, 
+      Long id) {
+    if (id == null) {
+      return null;
+    }
+    // TODO: pass isFuture to this method from decoding ID string
+    RecordKey key = new RecordKey(id, schema, false);
+    return schema.create(records.get(key));
   }
 
-  public void setRecords(JsArray<RecordJsoImpl> newRecords) {
+  public void setRecord(RecordJsoImpl newRecord,
+      RequestFactoryJsonImpl requestFactory) {
+    setRecordInList(newRecord, 0, null, requestFactory);
+  }
+
+  public void setRecords(JsArray<RecordJsoImpl> newRecords,
+      RequestFactoryJsonImpl requestFactory) {
     for (int i = 0, l = newRecords.length(); i < l; i++) {
       RecordJsoImpl newRecord = newRecords.get(i);
-      setRecordInList(newRecord, i, newRecords);
+      setRecordInList(newRecord, i, newRecords, requestFactory);
     }
   }
 
@@ -55,11 +68,14 @@
    * @param newRecord
    * @param i
    * @param array
+   * @param requestFactory
    */
   private void setRecordInList(RecordJsoImpl newRecord, int i,
-      JsArray<RecordJsoImpl> array) {
+      JsArray<RecordJsoImpl> array, RequestFactoryJsonImpl requestFactory) {
     RecordKey recordKey = new RecordKey(newRecord, RequestFactoryJsonImpl.NOT_FUTURE);
-
+    newRecord.setValueStore(this);
+    newRecord.setRequestFactory(requestFactory);
+    
     RecordJsoImpl oldRecord = records.get(recordKey);
     if (oldRecord == null) {
       records.put(recordKey, newRecord);
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index b315688..0cf7f65 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -65,23 +65,22 @@
 import java.math.BigInteger;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
+ * <p> <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * Generates implementations of
- * {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory}
- * and its nested interfaces.
+ * </span> </p> Generates implementations of {@link com.google.gwt.requestfactory.shared.RequestFactory
+ * RequestFactory} and its nested interfaces.
  */
 public class RequestFactoryGenerator extends Generator {
 
-  private final Set<JClassType> generatedRecordTypes = new HashSet<JClassType>();
+  private final Set<JClassType> generatedRecordTypes
+      = new HashSet<JClassType>();
 
   @Override
   public String generate(TreeLogger logger, GeneratorContext generatorContext,
@@ -94,15 +93,16 @@
 
     // Ensure that the requested type exists
     if (interfaceType == null) {
-      logger.log(TreeLogger.ERROR, "Could not find requested typeName: "
-          + interfaceName);
+      logger.log(TreeLogger.ERROR,
+          "Could not find requested typeName: " + interfaceName);
       throw new UnableToCompleteException();
     }
     if (interfaceType.isInterface() == null) {
       // The incoming type wasn't a plain interface, we don't support
       // abstract base classes
-      logger.log(TreeLogger.ERROR, interfaceType.getQualifiedSourceName()
-          + " is not an interface.", null);
+      logger.log(TreeLogger.ERROR,
+          interfaceType.getQualifiedSourceName() + " is not an interface.",
+          null);
       throw new UnableToCompleteException();
     }
 
@@ -114,9 +114,8 @@
 
     // If an implementation already exists, we don't need to do any work
     if (out != null) {
-      generateOnce(
-          typeOracle.findType(RequestFactory.class.getCanonicalName()), logger,
-          generatorContext, out, interfaceType, packageName, implName);
+      generateOnce(typeOracle.findType(RequestFactory.class.getCanonicalName()),
+          logger, generatorContext, out, interfaceType, packageName, implName);
     }
 
     return packageName + "." + implName;
@@ -147,6 +146,9 @@
     String recordImplTypeName = publicRecordType.getName() + "Impl";
     PrintWriter pw = generatorContext.tryCreate(logger, packageName,
         recordImplTypeName);
+
+    Set<JClassType> transitiveDeps = new LinkedHashSet<JClassType>();
+
     if (pw != null) {
       logger = logger.branch(TreeLogger.DEBUG, "Generating "
           + publicRecordType.getName());
@@ -157,9 +159,10 @@
       String eventTypeName = publicRecordType.getName() + "Changed";
       JClassType eventType = typeOracle.findType(packageName, eventTypeName);
       if (eventType == null) {
-        logger.log(TreeLogger.ERROR, String.format(
-            "Cannot find %s implementation %s.%s",
-            RecordChangedEvent.class.getName(), packageName, eventTypeName));
+        logger.log(TreeLogger.ERROR,
+            String.format("Cannot find %s implementation %s.%s",
+                RecordChangedEvent.class.getName(), packageName,
+                eventTypeName));
         throw new UnableToCompleteException();
       }
 
@@ -176,6 +179,8 @@
       f.addImport(Collections.class.getName());
       f.addImport(HashSet.class.getName());
       f.addImport(Set.class.getName());
+      f.addImport(Map.class.getName());
+      f.addImport(HashMap.class.getName());
 
       f.setSuperclass(RecordImpl.class.getSimpleName());
       f.addImplementedInterface(publicRecordType.getName());
@@ -188,8 +193,10 @@
 
       sw.println();
       String simpleImplName = publicRecordType.getSimpleSourceName() + "Impl";
-      printRequestImplClass(sw, publicRecordType, simpleImplName, true);
-      printRequestImplClass(sw, publicRecordType, simpleImplName, false);
+      printRequestImplClass(sw, publicRecordType, simpleImplName, true,
+          typeOracle);
+      printRequestImplClass(sw, publicRecordType, simpleImplName, false,
+          typeOracle);
 
       sw.println();
       sw.println(String.format(
@@ -203,6 +210,7 @@
       sw.println("super(jso, isFuture);");
       sw.outdent();
       sw.println("}");
+      JClassType recordBaseType = typeOracle.findType(Record.class.getName());
 
       // getter methods
       for (JField field : publicRecordType.getFields()) {
@@ -210,18 +218,26 @@
         if (propertyType.getErasedType() == fieldType.getErasedType()) {
           JParameterizedType parameterized = fieldType.isParameterized();
           if (parameterized == null) {
-            logger.log(TreeLogger.ERROR, fieldType
-                + " must have its param type set.");
+            logger.log(TreeLogger.ERROR,
+                fieldType + " must have its param type set.");
             throw new UnableToCompleteException();
           }
           JClassType returnType = parameterized.getTypeArgs()[0];
           sw.println();
           sw.println(String.format("public %s get%s() {",
-              returnType.getQualifiedSourceName(), capitalize(field.getName())));
+              returnType.getQualifiedSourceName(),
+              capitalize(field.getName())));
           sw.indent();
           sw.println(String.format("return get(%s);", field.getName()));
           sw.outdent();
           sw.println("}");
+          /*
+           * Because a Proxy A may relate to B which relates to C, we need to
+           * ensure transitively.
+           */
+          if (isRecordType(typeOracle, returnType)) {
+            transitiveDeps.add(returnType);
+          }
         }
       }
 
@@ -254,6 +270,11 @@
     }
 
     generatedRecordTypes.add(publicRecordType);
+    // ensure generatation of transitive dependencies
+    for (JClassType type : transitiveDeps) {
+      ensureRecordType(logger, generatorContext, type.getPackage().getName(),
+          type);
+    }
   }
 
   private void generateOnce(JClassType requestFactoryType, TreeLogger logger,
@@ -261,8 +282,9 @@
       JClassType interfaceType, String packageName, String implName)
       throws UnableToCompleteException {
 
-    logger = logger.branch(TreeLogger.DEBUG, String.format(
-        "Generating implementation of %s", interfaceType.getName()));
+    logger = logger.branch(TreeLogger.DEBUG,
+        String.format("Generating implementation of %s",
+            interfaceType.getName()));
 
     ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
         packageName, implName);
@@ -270,6 +292,8 @@
     f.addImport(RequestFactoryJsonImpl.class.getName());
     f.addImport(interfaceType.getQualifiedSourceName());
     f.addImport(RecordToTypeMap.class.getName());
+    f.addImport(Record.class.getName());
+    f.addImport(RecordSchema.class.getName());
     f.addImplementedInterface(interfaceType.getName());
     f.setSuperclass(RequestFactoryJsonImpl.class.getSimpleName());
 
@@ -288,11 +312,9 @@
       }
       JType returnType = method.getReturnType();
       if (null == returnType) {
-        logger.log(
-            TreeLogger.ERROR,
-            String.format(
-                "Illegal return type for %s. Methods of %s must return interfaces, found void",
-                method.getName(), interfaceType.getName()));
+        logger.log(TreeLogger.ERROR, String.format(
+            "Illegal return type for %s. Methods of %s must return interfaces, found void",
+            method.getName(), interfaceType.getName()));
         throw new UnableToCompleteException();
       }
       JClassType asInterface = returnType.isInterface();
@@ -317,8 +339,8 @@
     }
 
     // write create(Class..)
-    JClassType recordToTypeInterface = generatorContext.getTypeOracle().findType(
-        RecordToTypeMap.class.getName());
+    JClassType recordToTypeInterface = generatorContext.getTypeOracle().findType(RecordToTypeMap.class.getName());
+    // TODO: note, this seems like a bug. What if you have 2 RequestFactories?
     String recordToTypeMapName = recordToTypeInterface.getName() + "Impl";
     sw.println("public " + Record.class.getName() + " create(Class token) {");
     sw.indent();
@@ -326,20 +348,26 @@
     sw.outdent();
     sw.println("}");
 
+    sw.println(
+           "public RecordSchema<? extends Record> getSchema(String schemaToken) {");
+    sw.indent();
+    sw.println("return new " + recordToTypeMapName + "().getType(schemaToken);");
+    sw.outdent();
+    sw.println("}");
+
     // write a method for each request builder and generate it
     for (JMethod requestSelector : requestSelectors) {
       String returnTypeName = requestSelector.getReturnType().getQualifiedSourceName();
-      String nestedImplName = capitalize(requestSelector.getName().replace('.',
-          '_'))
-          + "Impl";
+      String nestedImplName =
+          capitalize(requestSelector.getName().replace('.', '_')) + "Impl";
       String nestedImplPackage = generatorContext.getTypeOracle().findType(
           returnTypeName).getPackage().getName();
 
       sw.println("public " + returnTypeName + " " + requestSelector.getName()
           + "() {");
       sw.indent();
-      sw.println("return new " + nestedImplPackage + "." + nestedImplName
-          + "(this);");
+      sw.println(
+          "return new " + nestedImplPackage + "." + nestedImplName + "(this);");
       sw.outdent();
       sw.println("}");
       sw.println();
@@ -369,8 +397,9 @@
   private void generateRecordToTypeMap(TreeLogger logger,
       GeneratorContext generatorContext, PrintWriter out,
       JClassType interfaceType, String packageName, String implName) {
-    logger = logger.branch(TreeLogger.DEBUG, String.format(
-        "Generating implementation of %s", interfaceType.getName()));
+    logger = logger.branch(TreeLogger.DEBUG,
+        String.format("Generating implementation of %s",
+            interfaceType.getName()));
 
     ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
         packageName, implName);
@@ -396,9 +425,30 @@
       sw.println("}");
     }
 
-    sw.println("throw new IllegalArgumentException(\"Unknown token \" + token + ");
+    sw.println(
+        "throw new IllegalArgumentException(\"Unknown token \" + token + ");
     sw.indent();
-    sw.println("\", does not match any of the TOKEN vairables of a Record\");");
+    sw.println("\", does not match any of the TOKEN variables of a Record\");");
+    sw.outdent();
+    sw.outdent();
+    sw.println("}");
+
+    // TODO: unoptimal bloat, and there doesn't need to be two of these methods
+    sw.println("public RecordSchema<? extends Record> getType(String token) {");
+    sw.indent();
+    for (JClassType publicRecordType : generatedRecordTypes) {
+      String qualifiedSourceName = publicRecordType.getQualifiedSourceName();
+      sw.println("if (token.equals(\"" + qualifiedSourceName + "\")) {");
+      sw.indent();
+      sw.println("return " + qualifiedSourceName + "Impl.SCHEMA;");
+      sw.outdent();
+      sw.println("}");
+    }
+
+    sw.println(
+        "throw new IllegalArgumentException(\"Unknown token \" + token + ");
+    sw.indent();
+    sw.println("\", does not match any of the TOKEN variables of a Record\");");
     sw.outdent();
     sw.outdent();
     sw.println("}");
@@ -413,8 +463,9 @@
       JMethod selectorMethod, JClassType mainType, String packageName,
       String implName) throws UnableToCompleteException {
     JClassType selectorInterface = selectorMethod.getReturnType().isInterface();
-    logger = logger.branch(TreeLogger.DEBUG, String.format(
-        "Generating implementation of %s", selectorInterface.getName()));
+    logger = logger.branch(TreeLogger.DEBUG,
+        String.format("Generating implementation of %s",
+            selectorInterface.getName()));
 
     ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
         packageName, implName);
@@ -428,8 +479,8 @@
     sw.println("private final " + mainType.getName() + "Impl factory;");
     sw.println();
     // constructor for the class.
-    sw.println("public " + implName + "(" + mainType.getName()
-        + "Impl factory) {");
+    sw.println(
+        "public " + implName + "(" + mainType.getName() + "Impl factory) {");
     sw.indent();
     sw.println("this.factory = factory;");
     sw.outdent();
@@ -485,21 +536,23 @@
       } else if (isVoidRequest(typeOracle, requestType)) {
         requestClassName = AbstractVoidRequest.class.getName();
       } else {
-        logger.log(TreeLogger.ERROR, "Return type " + requestType
-            + " is not yet supported");
+        logger.log(TreeLogger.ERROR,
+            "Return type " + requestType + " is not yet supported");
         throw new UnableToCompleteException();
       }
 
       sw.println(getMethodDeclaration(method) + " {");
       sw.indent();
-      sw.println("return new " + requestClassName + "(factory" + enumArgument
-          + ") {");
+      sw.println(
+          "return new " + requestClassName + "(factory" + enumArgument + ") {");
       sw.indent();
       String requestDataName = RequestData.class.getSimpleName();
       sw.println("public " + requestDataName + " getRequestData() {");
       sw.indent();
-      sw.println("return new " + requestDataName + "(\"" + operationName
-          + "\", " + getParametersAsString(method, typeOracle) + ");");
+      sw.println(
+          "return new " + requestDataName + "(\"" + operationName + "\", "
+              + getParametersAsString(method, typeOracle) + ","
+              + "getPropertyRefs());");
       sw.outdent();
       sw.println("}");
       sw.outdent();
@@ -514,9 +567,8 @@
   }
 
   /**
-   * This method is very similar to {@link
-   * com.google.gwt.core.ext.typeinfo.JMethod.getReadableDeclaration()}. The
-   * only change is that each parameter is final.
+   * This method is very similar to {@link com.google.gwt.core.ext.typeinfo.JMethod.getReadableDeclaration()}.
+   * The only change is that each parameter is final.
    */
   private String getMethodDeclaration(JMethod method) {
     StringBuilder sb = new StringBuilder("public ");
@@ -555,7 +607,8 @@
       // TODO No. This defeats the entire purpose of PropertyReference. It's
       // supposed
       // to be dereferenced server side, not client side.
-      if ("com.google.gwt.valuestore.shared.PropertyReference".equals(parameter.getType().getQualifiedBinaryName())) {
+      if ("com.google.gwt.valuestore.shared.PropertyReference".equals(
+          parameter.getType().getQualifiedBinaryName())) {
         sb.append(".get()");
       }
       JClassType classType = parameter.getType().isClassOrInterface();
@@ -577,7 +630,8 @@
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(BigInteger.class.getName()));
   }
 
-  private boolean isBooleanRequest(TypeOracle typeOracle, JClassType requestType) {
+  private boolean isBooleanRequest(TypeOracle typeOracle,
+      JClassType requestType) {
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Boolean.class.getName()));
   }
 
@@ -594,7 +648,8 @@
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Date.class.getName()));
   }
 
-  private boolean isDoubleRequest(TypeOracle typeOracle, JClassType requestType) {
+  private boolean isDoubleRequest(TypeOracle typeOracle,
+      JClassType requestType) {
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Double.class.getName()));
   }
 
@@ -602,11 +657,13 @@
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Enum.class.getName()));
   }
 
-  private boolean isFloatRequest(TypeOracle typeOracle, JClassType requestType) {
+  private boolean isFloatRequest(TypeOracle typeOracle,
+      JClassType requestType) {
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Float.class.getName()));
   }
 
-  private boolean isIntegerRequest(TypeOracle typeOracle, JClassType requestType) {
+  private boolean isIntegerRequest(TypeOracle typeOracle,
+      JClassType requestType) {
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Integer.class.getName()));
   }
 
@@ -619,11 +676,17 @@
     return requestType.isAssignableTo(typeOracle.findType(RecordListRequest.class.getName()));
   }
 
-  private boolean isRecordRequest(TypeOracle typeOracle, JClassType requestType) {
+  private boolean isRecordRequest(TypeOracle typeOracle,
+      JClassType requestType) {
     return requestType.isAssignableTo(typeOracle.findType(RecordRequest.class.getName()));
   }
 
-  private boolean isShortRequest(TypeOracle typeOracle, JClassType requestType) {
+  private boolean isRecordType(TypeOracle typeOracle, JClassType requestType) {
+    return requestType.isAssignableTo(typeOracle.findType(Record.class.getName()));
+  }
+
+  private boolean isShortRequest(TypeOracle typeOracle,
+      JClassType requestType) {
     return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Short.class.getName()));
   }
 
@@ -633,11 +696,9 @@
 
   /**
    * Prints a ListRequestImpl or ObjectRequestImpl class.
-   * 
-   * @param list
    */
   private void printRequestImplClass(SourceWriter sw, JClassType returnType,
-      String returnImplTypeName, boolean list) {
+      String returnImplTypeName, boolean list, TypeOracle typeOracle) {
 
     String name = list ? "ListRequestImpl" : "ObjectRequestImpl";
     Class<?> superClass = list ? AbstractJsonListRequest.class
@@ -648,6 +709,7 @@
         + "> {");
     sw.println();
     sw.indent();
+
     sw.println(String.format("%s(%s factory) {", name,
         RequestFactoryJsonImpl.class.getSimpleName()));
     sw.indent();
@@ -661,6 +723,7 @@
     sw.println("return this;");
     sw.outdent();
     sw.println("}");
+
     sw.outdent();
     sw.println("}");
     sw.println();
@@ -675,12 +738,13 @@
    * @return
    * @throws UnableToCompleteException
    */
+
   private JClassType printSchema(TypeOracle typeOracle,
       JClassType publicRecordType, String recordImplTypeName,
       JClassType eventType, SourceWriter sw) {
-    sw.println(String.format(
-        "public static class MySchema extends RecordSchema<%s> {",
-        recordImplTypeName));
+    sw.println(
+        String.format("public static class MySchema extends RecordSchema<%s> {",
+            recordImplTypeName));
 
     sw.indent();
     sw.println("private final Set<Property<?>> allProperties;");
@@ -737,8 +801,8 @@
     sw.println();
     sw.println("public Class getToken() {");
     sw.indent();
-    sw.println("return " + publicRecordType.getQualifiedSourceName()
-        + ".class;" + " // special field");
+    sw.println("return " + publicRecordType.getQualifiedSourceName() + ".class;"
+        + " // special field");
     sw.outdent();
     sw.println("}");
 
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
index a063474..72b1ed2 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
@@ -43,6 +43,10 @@
     }
   }
 
+  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/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
index d017f36..ed09eca 100644
--- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
@@ -18,6 +18,7 @@
 import com.google.gwt.requestfactory.shared.DataTransferObject;
 import com.google.gwt.requestfactory.shared.RequestData;
 import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.Service;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 import com.google.gwt.valuestore.shared.WriteOperation;
@@ -56,12 +57,15 @@
   private static final Logger log = Logger.getLogger(JsonRequestProcessor.class.getName());
 
   // TODO should we consume String, InputStream, or JSONObject?
+
   /**
    * A class representing the pair of a domain entity and its corresponding
    * record class on the client side.
    */
   public static class EntityRecordPair {
+
     public final Class<?> entity;
+
     public final Class<? extends Record> record;
 
     EntityRecordPair(Class<?> entity, Class<? extends Record> record) {
@@ -80,13 +84,17 @@
     return Collections.unmodifiableSet(blackList);
   }
 
+  private RequestProperty propertyRefs;
+
+  private Map<String, JSONObject> relatedObjects
+      = new HashMap<String, JSONObject>();
+
   private OperationRegistry operationRegistry;
 
   public Collection<Property<?>> allProperties(Class<? extends Record> clazz) {
     Set<Property<?>> rtn = new HashSet<Property<?>>();
     for (Field f : clazz.getFields()) {
-      if (Modifier.isStatic(f.getModifiers())
-          && Property.class.isAssignableFrom(f.getType())) {
+      if (Modifier.isStatic(f.getModifiers()) && Property.class.isAssignableFrom(f.getType())) {
         try {
           rtn.add((Property<?>) f.get(null));
         } catch (IllegalAccessException e) {
@@ -99,7 +107,12 @@
 
   public String decodeAndInvokeRequest(String encodedRequest) throws Exception {
     try {
-      return processJsonRequest(encodedRequest).toString();
+      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 (ClassNotFoundException e) {
       throw new IllegalArgumentException(e);
     } catch (IllegalAccessException e) {
@@ -119,7 +132,6 @@
 
   /**
    * Encodes parameter value.
-   * 
    */
   public Object decodeParameterValue(Class<?> parameterType,
       String parameterValue) {
@@ -174,21 +186,53 @@
             }
           }
         }
-        throw new IllegalArgumentException("Can't decode enum " + parameterType
-            + " no matching ordinal " + ordinal);
+        throw new IllegalArgumentException(
+            "Can't decode enum " + parameterType + " no matching ordinal "
+                + ordinal);
       } catch (NoSuchMethodException e) {
-        throw new IllegalArgumentException("Can't decode enum " + parameterType);
+        throw new IllegalArgumentException(
+            "Can't decode enum " + parameterType);
       } catch (InvocationTargetException e) {
-        throw new IllegalArgumentException("Can't decode enum " + parameterType);
+        throw new IllegalArgumentException(
+            "Can't decode enum " + parameterType);
       } catch (IllegalAccessException e) {
-        throw new IllegalArgumentException("Can't decode enum " + parameterType);
+        throw new IllegalArgumentException(
+            "Can't decode enum " + parameterType);
       }
     }
     if (Date.class == parameterType) {
       return new Date(Long.parseLong(parameterValue));
     }
-    throw new IllegalArgumentException("Unknown parameter type: "
-        + parameterType);
+    if (Record.class.isAssignableFrom(parameterType)) {
+      Service service = parameterType.getAnnotation(Service.class);
+      if (service != null) {
+        Class<?> sClass = service.value();
+        String schemaAndId[] = parameterValue.toString().split("-", 2);
+        // ignore schema for now and use Property type
+        String findMeth = null;
+        try {
+          findMeth = getMethodNameFromPropertyName(sClass.getSimpleName(),
+              "find");
+          Method meth = sClass.getMethod(findMeth, Long.class);
+          return meth.invoke(null, Long.valueOf(schemaAndId[1]));
+        } catch (NoSuchMethodException e) {
+          e.printStackTrace();
+          throw new IllegalArgumentException(
+              sClass + " no method named " + findMeth);
+        } catch (InvocationTargetException e) {
+          e.printStackTrace();
+
+          throw new IllegalArgumentException(
+              sClass + " can't invoke method named " + findMeth);
+        } catch (IllegalAccessException e) {
+          e.printStackTrace();
+          throw new IllegalArgumentException(
+              sClass + " can't access method named " + findMeth);
+        }
+      }
+    }
+    throw new IllegalArgumentException(
+        "Unknown parameter type: " + parameterType);
   }
 
   public Object encodePropertyValue(Object value) {
@@ -220,18 +264,33 @@
    * is sent into the response.
    */
   public Object encodePropertyValueFromDataStore(Object entityElement,
-      String propertyName) throws SecurityException, NoSuchMethodException,
-      IllegalAccessException, InvocationTargetException {
+      Class<?> propertyType, String propertyName,
+      RequestProperty propertyContext)
+      throws SecurityException, NoSuchMethodException, IllegalAccessException,
+      InvocationTargetException, JSONException {
     String methodName = getMethodNameFromPropertyName(propertyName, "get");
     Method method = entityElement.getClass().getMethod(methodName);
     Object returnValue = method.invoke(entityElement);
+    if (returnValue != null && Record.class.isAssignableFrom(propertyType)) {
+      Method idMethod = entityElement.getClass().getMethod("getId");
+      Long id = (Long) idMethod.invoke(entityElement);
+
+      String keyRef =
+          operationRegistry.getSecurityProvider().encodeClassType(propertyType)
+              + "-" + id;
+      addRelatedObject(keyRef, returnValue,
+          (Class<? extends Record>) propertyType,
+          propertyContext.getProperty(propertyName));
+      // replace value with id reference
+      return keyRef;
+    }
     return encodePropertyValue(returnValue);
   }
 
   /**
    * Generate an ID for a new record. The default behavior is to return null and
    * let the data store generate the ID automatically.
-   * 
+   *
    * @param key the key of the record field
    * @return the ID of the new record, or null to auto generate
    */
@@ -260,43 +319,46 @@
       return entity.getConstructor().newInstance();
     }
     // TODO: check "version" validity.
-    return entity.getMethod("find" + entity.getSimpleName(), idType).invoke(
-        null, decodeParameterValue(idType, idValue.toString()));
+    return entity.getMethod("find" + entity.getSimpleName(), 
+        idType).invoke(null, decodeParameterValue(idType, idValue.toString()));
   }
 
   /**
    * Converts the returnValue of a 'get' method to a JSONArray.
-   * 
-   * @param resultList object returned by a 'get' method, must be of type
-   *          List<?>
+   *
+   * @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(List<?> resultList,
-      Class<? extends Record> entityKeyClass) throws IllegalArgumentException,
-      SecurityException, IllegalAccessException, JSONException,
-      NoSuchMethodException, InvocationTargetException {
+      Class<? extends Record> entityKeyClass)
+      throws IllegalArgumentException, SecurityException,
+      IllegalAccessException, JSONException, NoSuchMethodException,
+      InvocationTargetException {
     JSONArray jsonArray = new JSONArray();
     if (resultList.size() == 0) {
       return jsonArray;
     }
 
     for (Object entityElement : resultList) {
-      jsonArray.put(getJsonObject(entityElement, entityKeyClass));
+      jsonArray.put(getJsonObject(entityElement, entityKeyClass, propertyRefs));
     }
     return jsonArray;
   }
 
   public JSONObject getJsonObject(Object entityElement,
-      Class<? extends Record> entityKeyClass) throws JSONException,
-      NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+      Class<? extends Record> entityKeyClass, RequestProperty propertyContext)
+      throws JSONException, NoSuchMethodException, IllegalAccessException,
+      InvocationTargetException {
     JSONObject jsonObject = new JSONObject();
     for (Property<?> p : allProperties(entityKeyClass)) {
 
-      if (requestedProperty(p)) {
+      if (requestedProperty(p, propertyContext)) {
         String propertyName = p.getName();
-        jsonObject.put(propertyName, encodePropertyValueFromDataStore(
-            entityElement, propertyName));
+        jsonObject.put(propertyName,
+            encodePropertyValueFromDataStore(entityElement, p.getType(),
+                propertyName, propertyContext));
       }
     }
     return jsonObject;
@@ -305,11 +367,12 @@
   /**
    * 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) {
+  public String getMethodNameFromPropertyName(String propertyName,
+      String prefix) {
     if (propertyName == null) {
       throw new NullPointerException("propertyName must not be null");
     }
@@ -362,13 +425,14 @@
    * Returns the property fields (name => type) for a record.
    */
   public Map<String, Class<?>> getPropertiesFromRecord(
-      Class<? extends Record> record) throws SecurityException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+      Class<? extends Record> record)
+      throws SecurityException, IllegalAccessException,
+      InvocationTargetException, NoSuchMethodException {
     Map<String, Class<?>> properties = new HashMap<String, Class<?>>();
     for (Field f : record.getFields()) {
       if (Property.class.isAssignableFrom(f.getType())) {
-        Class<?> propertyType = (Class<?>) f.getType().getMethod("getType").invoke(
-            f.get(null));
+        Class<?> propertyType = (Class<?>) f.getType().getMethod(
+            "getType").invoke(f.get(null));
         properties.put(f.getName(), propertyType);
       }
     }
@@ -379,32 +443,33 @@
    * Returns the property value, in the specified type, from the request object.
    * The value is put in the DataStore.
    */
-  public Object getPropertyValueFromRequest(JSONObject recordObject,
-      String key, Class<?> propertyType) throws JSONException {
+  public Object getPropertyValueFromRequest(JSONObject recordObject, String key,
+      Class<?> propertyType) throws JSONException {
     return decodeParameterValue(propertyType, recordObject.get(key).toString());
   }
 
   @SuppressWarnings("unchecked")
   public Class<Record> getRecordFromClassToken(String recordToken) {
     try {
-      Class<?> clazz = Class.forName(recordToken, false,
+      Class<?> clazz = Class.forName(recordToken, false, 
           getClass().getClassLoader());
       if (Record.class.isAssignableFrom(clazz)) {
         return (Class<Record>) clazz;
       }
-      throw new SecurityException("Attempt to access non-record class "
-          + recordToken);
+      throw new SecurityException(
+          "Attempt to access non-record class " + recordToken);
     } catch (ClassNotFoundException e) {
-      throw new IllegalArgumentException("Non-existent record class "
-          + recordToken);
+      throw new IllegalArgumentException(
+          "Non-existent record class " + recordToken);
     }
   }
 
   public JSONObject getReturnRecord(WriteOperation writeOperation,
       Object entityInstance, JSONObject recordObject,
-      Set<ConstraintViolation<Object>> violations) throws SecurityException,
-      JSONException, IllegalAccessException, InvocationTargetException,
-      NoSuchMethodException {
+      Set<ConstraintViolation<Object>> violations,
+      Class<? extends Record> record)
+      throws SecurityException, JSONException, IllegalAccessException,
+      InvocationTargetException, NoSuchMethodException {
     // id/futureId, the identifying field is sent back from the incoming record.
     JSONObject returnObject = new JSONObject();
     final boolean hasViolations = violations != null && !violations.isEmpty();
@@ -415,10 +480,12 @@
       case CREATE:
         returnObject.put("futureId", recordObject.getString("id"));
         if (!hasViolations) {
-          returnObject.put("id", encodePropertyValueFromDataStore(
-              entityInstance, "id"));
-          returnObject.put("version", encodePropertyValueFromDataStore(
-              entityInstance, "version"));
+          returnObject.put("id",
+              encodePropertyValueFromDataStore(entityInstance, Long.class,
+                  "id", propertyRefs));
+          returnObject.put("version",
+              encodePropertyValueFromDataStore(entityInstance, Integer.class,
+                  "version", propertyRefs));
         }
         break;
       case DELETE:
@@ -427,8 +494,9 @@
       case UPDATE:
         returnObject.put("id", recordObject.getString("id"));
         if (!hasViolations) {
-          returnObject.put("version", encodePropertyValueFromDataStore(
-              entityInstance, "version"));
+          returnObject.put("version",
+              encodePropertyValueFromDataStore(entityInstance, Integer.class,
+                  "version", propertyRefs));
         }
         break;
     }
@@ -464,7 +532,7 @@
       Set<ConstraintViolation<Object>> violations) throws JSONException {
     JSONObject violationsAsJson = new JSONObject();
     for (ConstraintViolation<Object> violation : violations) {
-      violationsAsJson.put(violation.getPropertyPath().toString(),
+      violationsAsJson.put(violation.getPropertyPath().toString(), 
           violation.getMessage());
     }
     return violationsAsJson;
@@ -481,7 +549,13 @@
     RequestDefinition operation;
     JSONObject topLevelJsonObject = new JSONObject(jsonRequestString);
 
-    String operationName = topLevelJsonObject.getString(RequestData.OPERATION_TOKEN);
+    String operationName = topLevelJsonObject.getString(
+        RequestData.OPERATION_TOKEN);
+
+    String propertyRefsString =
+        topLevelJsonObject.has(RequestData.PROPERTY_REF_TOKEN) ?
+            topLevelJsonObject.getString(RequestData.PROPERTY_REF_TOKEN) : "";
+    propertyRefs = RequestProperty.parse(propertyRefsString);
     if (operationName.equals(RequestFactory.SYNC)) {
       return sync(topLevelJsonObject.getString(RequestData.CONTENT_TOKEN));
     } else {
@@ -490,28 +564,35 @@
       Method domainMethod = domainClass.getMethod(
           operation.getDomainMethodName(), operation.getParameterTypes());
       if (!Modifier.isStatic(domainMethod.getModifiers())) {
-        throw new IllegalArgumentException("the " + domainMethod.getName()
-            + " is not static");
+        throw new IllegalArgumentException(
+            "the " + domainMethod.getName() + " is not static");
       }
       Object args[] = getObjectsFromParameterMap(
-          getParameterMap(topLevelJsonObject), domainMethod.getParameterTypes());
+          getParameterMap(topLevelJsonObject),
+          domainMethod.getParameterTypes());
       Object result = invokeStaticDomainMethod(domainMethod, args);
       if ((result instanceof List<?>) != operation.isReturnTypeList()) {
-        throw new IllegalArgumentException(String.format(
-            "Type mismatch, expected %s%s, but %s returns %s",
-            operation.isReturnTypeList() ? "list of " : "",
-            operation.getReturnType(), domainMethod,
-            domainMethod.getReturnType()));
+        throw new IllegalArgumentException(
+            String.format("Type mismatch, expected %s%s, but %s returns %s",
+                operation.isReturnTypeList() ? "list of " : "",
+                operation.getReturnType(), domainMethod,
+                domainMethod.getReturnType()));
       }
 
       if (result instanceof List<?>) {
-        return toJsonArray(operation, result);
-      } else if (result instanceof Number
-          && !(result instanceof BigDecimal || result instanceof BigInteger)) {
+        JSONObject envelop = new JSONObject();
+        envelop.put("result", toJsonArray(operation, result));
+        envelop.put("related", encodeRelatedObjectsToJson());
+        return envelop;
+      } else if (result instanceof Number && !(result instanceof BigDecimal
+          || result instanceof BigInteger)) {
         return result;
       } else {
+        JSONObject envelop = new JSONObject();
         JSONObject jsonObject = toJsonObject(operation, result);
-        return jsonObject;
+        envelop.put("result", jsonObject);
+        envelop.put("related", encodeRelatedObjectsToJson());
+        return envelop;
       }
     }
   }
@@ -519,12 +600,18 @@
   /**
    * 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 p               the field of entity ref
+   * @param propertyContext the root of the current dotted property reference
    * @return has the property value been requested
    */
-  public boolean requestedProperty(Property<?> p) {
-    return !BLACK_LIST.contains(p.getName());
+  public boolean requestedProperty(Property<?> p,
+      RequestProperty propertyContext) {
+    if (Record.class.isAssignableFrom(p.getType())) {
+      return propertyContext.hasProperty(p.getName());
+    } else {
+      return !BLACK_LIST.contains(p.getName());
+    }
   }
 
   public void setOperationRegistry(OperationRegistry operationRegistry) {
@@ -546,8 +633,9 @@
 
         int length = reportArray.length();
         if (length == 0) {
-          throw new IllegalArgumentException("No json array for "
-              + writeOperation.name() + " should have been sent");
+          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);
@@ -585,18 +673,11 @@
 
   /**
    * Persist a recordObject of token "recordToken" and return useful information
-   * as a JSONObject to return back.
-   * <p>
-   * Example: recordToken = "Employee", entity = Employee.class, record =
-   * EmployeeRecord.class
-   *<p>
-   * Steps:
-   * <ol>
-   * <li>assert that each property is present in "EmployeeRecord"
-   * <li>invoke "findEmployee (id)" OR new Employee()
-   * <li>set various fields on the attached entity and persist OR remove()
-   * <li>return data
-   * </ol>
+   * as a JSONObject to return back. <p> Example: recordToken = "Employee",
+   * entity = Employee.class, record = EmployeeRecord.class <p> Steps: <ol>
+   * <li>assert that each property is present in "EmployeeRecord" <li>invoke
+   * "findEmployee (id)" OR new Employee() <li>set various fields on the
+   * attached entity and persist OR remove() <li>return data </ol>
    */
   public JSONObject updateRecordInDataStore(String recordToken,
       JSONObject recordObject, WriteOperation writeOperation) {
@@ -605,7 +686,8 @@
       Class<? extends Record> record = getRecordFromClassToken(recordToken);
       Class<?> entity = getEntityFromRecordAnnotation(record);
 
-      Map<String, Class<?>> propertiesInRecord = getPropertiesFromRecord(record);
+      Map<String, Class<?>> propertiesInRecord = getPropertiesFromRecord(
+          record);
       validateKeys(recordObject, propertiesInRecord.keySet());
       updatePropertyTypes(propertiesInRecord, entity);
 
@@ -664,7 +746,7 @@
 
       // return data back.
       return getReturnRecord(writeOperation, entityInstance, recordObject,
-          violations);
+          violations, record);
     } catch (Exception ex) {
       log.severe(String.format("Caught exception [%s] %s",
           ex.getClass().getName(), ex.getLocalizedMessage()));
@@ -678,12 +760,31 @@
     while (keys.hasNext()) {
       String key = (String) keys.next();
       if (!declaredProperties.contains(key)) {
-        throw new IllegalArgumentException("key " + key
-            + " is not permitted to be set");
+        throw new IllegalArgumentException(
+            "key " + key + " is not permitted to be set");
       }
     }
   }
 
+  private void addRelatedObject(String keyRef, Object returnValue,
+      Class<? extends Record> propertyType, RequestProperty propertyContext)
+      throws JSONException, IllegalAccessException, NoSuchMethodException,
+      InvocationTargetException {
+    Class<? extends Record> clazz =
+        (Class<? extends Record>) returnValue.getClass();
+
+    relatedObjects.put(keyRef, getJsonObject(returnValue, propertyType, 
+        propertyContext));
+  }
+
+  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;
+  }
+
   @SuppressWarnings("unchecked")
   private Object toJsonArray(RequestDefinition operation, Object result)
       throws IllegalAccessException, JSONException, NoSuchMethodException,
@@ -698,7 +799,7 @@
       throws JSONException, NoSuchMethodException, IllegalAccessException,
       InvocationTargetException {
     JSONObject jsonObject = getJsonObject(result,
-        (Class<? extends Record>) operation.getReturnType());
+        (Class<? extends Record>) operation.getReturnType(), propertyRefs);
     return jsonObject;
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
index 5cb63a6..06c7675 100644
--- a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
+++ b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
@@ -26,4 +26,6 @@
 public interface OperationRegistry {
 
   RequestDefinition getOperation(String operationName);
+
+  RequestSecurityProvider getSecurityProvider();
 }
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
index a0deea1..14efd2b 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
@@ -151,6 +151,7 @@
   }
 
   /**
+
    * Turns an operation in the form of package.requestClass::method into a
    * RequestDefinition via reflection.
    */
@@ -182,6 +183,10 @@
     }
   }
 
+  public RequestSecurityProvider getSecurityProvider() {
+    return securityProvider;
+  }
+
   private Method findMethod(Class<?> clazz, String methodName) {
     for (Method method : clazz.getDeclaredMethods()) {
       if ((method.getModifiers() & Modifier.PUBLIC) != 0) {
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index a52862d..72dc579 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -20,6 +20,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.logging.Logger;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -88,6 +89,8 @@
       }
       // TODO: clean exception handling code below.
     } catch (Exception e) {
+      Logger.getLogger(getClass().getName()).severe(e.getMessage());
+      e.printStackTrace();
       throw new RuntimeException(e);
     }
   }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
new file mode 100644
index 0000000..7287a0b
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProperty.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.requestfactory.server;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * <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> Represents one piece in a
+ * property reference sequence.
+ */
+public class RequestProperty implements Iterable<RequestProperty> {
+
+  /**
+   * Merge two property chains.
+   */
+  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;
+  }
+
+  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;
+  }
+
+  public RequestProperty add(RequestProperty propertyRef) {
+    if (subProperties == null) {
+      subProperties = new HashMap<String, RequestProperty>();
+    }
+    subProperties.put(propertyRef.getPropertyName(), propertyRef);
+    return this;
+  }
+
+  public RequestProperty getProperty(String propName) {
+    return subProperties == null ? null : subProperties.get(propName);
+  }
+
+  public String getPropertyName() {
+    return propertyName;
+  }
+
+  public boolean hasProperty(String name) {
+    return subProperties == null ? false : subProperties.containsKey(name);
+  }
+
+  public Iterator<RequestProperty> iterator() {
+    return subProperties == null ?
+        (Iterator<RequestProperty>) Collections.EMPTY_MAP.values().iterator() :
+        subProperties.values().iterator();
+  }
+
+  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;
+  }
+
+  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
index 232eb85..b16dd02 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
@@ -32,6 +32,11 @@
   void checkClass(Class<?> clazz) throws SecurityException;
 
   /**
+   * Encode a Class type into a String token.
+   */
+  String encodeClassType(Class<?> type);
+
+  /**
    * Optionally decodes a previously encoded operation. Throws exception if
    * argument is not a legal operation.
    */
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformation.java b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
index 15e871d..a90243f 100644
--- a/user/src/com/google/gwt/requestfactory/server/UserInformation.java
+++ b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
@@ -74,7 +74,7 @@
   
   public static UserInformation getCurrentUserInformation(String redirectUrl) {
     UserInformation userInfo = null;
-    if (!userInformationImplClass.isEmpty()) {
+    if (!"".equals(userInformationImplClass)) {
       try {
         userInfo = (UserInformation) Class.forName(
             userInformationImplClass).getConstructor(
diff --git a/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java b/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
index 7d741de..ee417a5 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
@@ -22,20 +22,20 @@
 import java.util.List;
 
 /**
- * <p>
- * <span style="color:red">Experimental API: This class is still under rapid
+ * <p> <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
- * </span>
- * </p>
- * Implemented by RequestObjects for service methods that return lists of
- * records.
- * 
+ * </span> </p> Implemented by RequestObjects for service methods that return
+ * lists of records.
+ *
  * @param <R> The type held by the returned list
  */
-public interface RecordListRequest<R extends Record> extends
-    RequestObject<List<R>> {
+public interface RecordListRequest<R extends Record>
+    extends RequestObject<List<R>> {
 
+  RecordListRequest<R> with(String... propertyRefs);
+
+  /**
+   * @deprecated use {@link #with(String...)} instead.
+   */
   RecordListRequest<R> forProperties(Collection<Property<?>> properties);
-
-  RecordListRequest<R> forProperty(Property<?> property);
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java b/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java
index a359fb5..db93689 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java
@@ -34,7 +34,11 @@
 public interface RecordRequest<R extends Record> extends
     RequestObject<R> {
 
+  RecordRequest<R> with(String... propertyRefs);
+
+  /**
+   * @deprecated use {@link #with(String...)} instead.
+   */
   RecordRequest<R> forProperties(Collection<Property<?>> properties);
 
-  RecordRequest<R> forProperty(Property<?> property);
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestData.java b/user/src/com/google/gwt/requestfactory/shared/RequestData.java
index 59e77ec..e6085c7 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestData.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestData.java
@@ -16,7 +16,9 @@
 package com.google.gwt.requestfactory.shared;
 
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * <p>
@@ -34,14 +36,19 @@
   public static final String CONTENT_TOKEN = "contentData";
   public static final String OPERATION_TOKEN = "operation";
   public static final String PARAM_TOKEN = "param";
+  public static final String PROPERTY_REF_TOKEN = "propertyRefs";
 
   // TODO: non-final is a hack for now.
   private String operation;
   private final Object[] parameters;
 
-  public RequestData(String operation, Object[] parameters) {
+  private Set<String> propertyRefs;
+
+  public RequestData(String operation, Object[] parameters,
+      Set<String> propertyRefs) {
     this.operation = operation;
     this.parameters = parameters;
+    this.propertyRefs = propertyRefs;
   }
 
   /**
@@ -64,6 +71,18 @@
     if (contentData != null) {
       requestMap.put(CONTENT_TOKEN, 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(PROPERTY_REF_TOKEN, props.toString());
+    }
     return requestMap;
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index c032975..dd80628 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -27,8 +27,7 @@
  * Marker interface for the RequestFactory code generator.
  */
 public interface RequestFactory {
-  String JSON_CONTENT_TYPE_UTF8 =
-    "application/json; charset=utf-8";
+  static final String JSON_CONTENT_TYPE_UTF8 = "application/json; charset=utf-8";
 
   String SYNC = "SYNC";
 
@@ -36,7 +35,7 @@
   String URL = "gwtRequest";
 
   Record create(Class token);
-
+  
   void init(HandlerManager eventBus);
 
   // The following methods match the format for the generated sub-interfaces
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java b/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
index d7f828f..faa4821 100644
--- a/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
@@ -51,6 +51,14 @@
       }
       throw new IllegalArgumentException("Unknown token " + recordClass);
     }
+
+     public RecordSchema<? extends Record> getType(
+       String recordClass) {
+      if (recordClass.equals(SimpleFooRecord.class.getName())) {
+        return SimpleFooRecordImpl.SCHEMA;
+      }
+      throw new IllegalArgumentException("Unknown token " + recordClass);
+    }
   };
   ValueStoreJsonImpl valueStore = null;
   RequestFactoryJsonImpl requestFactory = null;
@@ -71,6 +79,11 @@
         return create(token, typeMap);
       }
 
+
+      public RecordSchema getSchema(String token) {
+        return typeMap.getType(token);
+      }
+
       @Override
       public void init(HandlerManager eventBus) {
         // ignore.
@@ -91,7 +104,7 @@
     jso.set(SimpleFooRecord.intId, 4);
     jso.set(SimpleFooRecord.created, new Date());
     jso.setSchema(SimpleFooRecordImpl.SCHEMA);
-    valueStore.setRecord(jso);
+    valueStore.setRecord(jso, requestFactory);
   }
 
   public void testCreate() {
@@ -188,7 +201,7 @@
 
     RecordImpl mockRecord = new RecordImpl(RecordJsoImpl.create(futureId, 1,
         SimpleFooRecordImpl.SCHEMA), RequestFactoryJsonImpl.NOT_FUTURE);
-    valueStore.setRecord(mockRecord.asJso()); // marked as non-future..
+    valueStore.setRecord(mockRecord.asJso(), requestFactory); // marked as non-future..
     DeltaValueStoreJsonImpl deltaValueStore = new DeltaValueStoreJsonImpl(
         valueStore, requestFactory);
 
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java b/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java
index 1017275..6c77dc1 100644
--- a/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java
+++ b/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java
@@ -18,6 +18,7 @@
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 import com.google.gwt.valuestore.shared.RecordChangedEvent;
+import com.google.gwt.valuestore.shared.SimpleBarRecord;
 import com.google.gwt.valuestore.shared.SimpleFooRecord;
 import com.google.gwt.valuestore.shared.WriteOperation;
 
@@ -80,10 +81,14 @@
     super(jso, isFuture);
   }
 
+  public SimpleBarRecord getBarField() {
+    return (SimpleBarRecord) getValueStore().getRecordBySchemaAndId(SCHEMA, (Long) (Object) get(barField));
+  }
+
   public Boolean getBoolField() {
     return get(boolField);
   }
-  
+
   public java.util.Date getCreated() {
     return get(created);
   }
diff --git a/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java b/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java
index 82b527f..682f0e7 100644
--- a/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java
@@ -95,12 +95,13 @@
     com.google.gwt.valuestore.server.SimpleFoo.reset();
     try {
       // fetch object
-      JSONObject foo = (JSONObject) requestProcessor.processJsonRequest("{ \""
+      JSONObject results = (JSONObject) requestProcessor.processJsonRequest("{ \""
           + RequestData.OPERATION_TOKEN + "\": \""
           + com.google.gwt.valuestore.shared.SimpleFooRequest.class.getName()
           + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR
           + "findSimpleFooById\", " + "\"" + RequestData.PARAM_TOKEN
           + "0\": \"999\" }");
+      JSONObject foo = results.getJSONObject("result");   
       assertEquals(foo.getInt("id"), 999);
       assertEquals(foo.getInt("intId"), 42);
       assertEquals(foo.getString("userName"), "GWT");
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java b/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java
new file mode 100644
index 0000000..e08ca68
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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(name);
+  }
+  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/valuestore/client/RequestFactoryTest.java b/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java
index b6b2001..132646d 100644
--- a/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java
@@ -48,6 +48,27 @@
             assertEquals(8L, (long) response.getLongField());
             assertEquals(com.google.gwt.valuestore.shared.SimpleEnum.FOO,
                 response.getEnumField());
+            assertEquals(null, response.getBarField());
+            finishTest();
+          }
+        });
+  }
+
+   public void testFetchEntityWithRelation() {
+    SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class);
+    HandlerManager hm = new HandlerManager(null);
+    req.init(hm);
+    delayTestFinish(5000);
+    req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
+        new Receiver<SimpleFooRecord>() {
+          public void onSuccess(SimpleFooRecord response,
+              Set<SyncResult> syncResult) {
+            assertEquals(42, (int) response.getIntId());
+            assertEquals("GWT", response.getUserName());
+            assertEquals(8L, (long) response.getLongField());
+            assertEquals(com.google.gwt.valuestore.shared.SimpleEnum.FOO,
+                response.getEnumField());
+            assertNotNull(response.getBarField());
             finishTest();
           }
         });
diff --git a/user/test/com/google/gwt/valuestore/server/SimpleBar.java b/user/test/com/google/gwt/valuestore/server/SimpleBar.java
new file mode 100644
index 0000000..8d2e908
--- /dev/null
+++ b/user/test/com/google/gwt/valuestore/server/SimpleBar.java
@@ -0,0 +1,93 @@
+/*
+ * 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.shared.Id;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Domain object for SimpleFooRequest.
+ */
+public class SimpleBar {
+
+  static SimpleBar singleton = new SimpleBar();
+
+  public static Long countSimpleBar() {
+    return 1L;
+  }
+
+  public static List<SimpleBar> findAll() {
+    return Collections.singletonList(singleton);
+  }
+
+  public static SimpleBar findSimpleBar(Long id) {
+    return findSimpleBarById(id);
+  }
+
+  public static SimpleBar findSimpleBarById(Long id) {
+    singleton.setId(id);
+    return singleton;
+  }
+
+  public static SimpleBar getSingleton() {
+    return singleton;
+  }
+
+  public static void reset() {
+    singleton = new SimpleBar();
+  }
+
+  Integer version = 1;
+
+  @Id
+  private Long id = -1L;
+
+  private String userName;
+
+  public SimpleBar() {
+    version = 1;
+    userName = "FOO";
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public String getUserName() {
+    return userName;
+  }
+
+  public Integer getVersion() {
+    return version;
+  }
+
+  public void persist() {
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public void setUserName(String userName) {
+    this.userName = userName;
+  }
+
+  public void setVersion(Integer version) {
+    this.version = version;
+  }
+}
diff --git a/user/test/com/google/gwt/valuestore/server/SimpleFoo.java b/user/test/com/google/gwt/valuestore/server/SimpleFoo.java
index 6de9282..6c7ac35 100644
--- a/user/test/com/google/gwt/valuestore/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/valuestore/server/SimpleFoo.java
@@ -36,6 +36,7 @@
   public static List<SimpleFoo> findAll() {
     return Collections.singletonList(singleton);
   }
+
   public static SimpleFoo findSimpleFoo(Long id) {
     return findSimpleFooById(id);
   }
@@ -51,11 +52,13 @@
 
   public static void reset() {
     singleton = new SimpleFoo();
-  };
+  }
+
   @SuppressWarnings("unused")
   private static Integer privateMethod() {
     return 0;
   }
+
   Integer version = 1;
 
   private Boolean boolField;
@@ -70,6 +73,8 @@
 
   private String userName;
 
+  private SimpleBar barField;
+
   public SimpleFoo() {
     intId = 42;
     version = 1;
@@ -77,9 +82,14 @@
     longField = 8L;
     enumField = SimpleEnum.FOO;
     created = new Date();
+    barField = SimpleBar.getSingleton();
     boolField = true;
   }
 
+  public SimpleBar getBarField() {
+    return barField;
+  }
+
   public Boolean getBoolField() {
     return boolField;
   }
@@ -115,6 +125,10 @@
   public void persist() {
   }
 
+  public void setBarField(SimpleBar barField) {
+    this.barField = barField;
+  }
+
   public void setBoolField(Boolean bool) {
     boolField = bool;
   }
diff --git a/user/test/com/google/gwt/valuestore/shared/SimpleBarRecord.java b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecord.java
new file mode 100644
index 0000000..86e85b7
--- /dev/null
+++ b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecord.java
@@ -0,0 +1,33 @@
+/*
+ * 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.shared;
+
+import com.google.gwt.requestfactory.shared.DataTransferObject;
+import com.google.gwt.valuestore.server.SimpleBar;
+
+/**
+ * A simple entity used for testing. Has an int field and date field. Add other data types as their
+ * support gets built in.
+ */
+@DataTransferObject(SimpleBar.class)
+public interface SimpleBarRecord extends Record {
+
+  Property<String> userName = new Property<String>("userName", "User Name",
+      String.class);
+
+  String getUserName();
+
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/valuestore/shared/SimpleBarRecordChanged.java b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecordChanged.java
new file mode 100644
index 0000000..55b68c0
--- /dev/null
+++ b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecordChanged.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.valuestore.shared;
+
+import com.google.gwt.event.shared.EventHandler;
+
+/**
+ * Test implementation of {@link com.google.gwt.valuestore.shared.RecordChangedEvent} for
+ * {@link com.google.gwt.valuestore.shared.SimpleFooRecord}.
+ */
+public class SimpleBarRecordChanged extends
+    RecordChangedEvent<SimpleBarRecord, SimpleBarRecordChanged.Handler> {
+
+/**
+ *  Test Handler for SimpleFooChanged event.
+ */
+  public interface Handler extends EventHandler {
+    void onSimpleBarRecordChanged(SimpleBarRecordChanged record);
+  }
+
+  public static final Type<Handler> TYPE = new Type<Handler>();
+
+  public SimpleBarRecordChanged(SimpleBarRecord record,
+      WriteOperation writeOperation) {
+    super(record, writeOperation);
+  }
+
+  @Override
+  public Type<Handler> getAssociatedType() {
+    return TYPE;
+  }
+
+  @Override
+  protected void dispatch(Handler handler) {
+    handler.onSimpleBarRecordChanged(this);
+  }
+}
diff --git a/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java b/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
index 02b09ff..e0f8c44 100644
--- a/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
+++ b/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
@@ -39,18 +39,23 @@
       new EnumProperty<com.google.gwt.valuestore.shared.SimpleEnum>("enumField",
           com.google.gwt.valuestore.shared.SimpleEnum.class, SimpleEnum.values());
 
+  Property<SimpleBarRecord> barField = new Property<SimpleBarRecord>("barField",
+      SimpleBarRecord.class);
+
+  SimpleBarRecord getBarField();
+
   Boolean getBoolField();
   
   Date getCreated();
 
+  com.google.gwt.valuestore.shared.SimpleEnum getEnumField();
+
   Integer getIntId();
 
+  Long getLongField();
+
   String getPassword();
 
   String getUserName();
 
-  Long getLongField();
-
-  com.google.gwt.valuestore.shared.SimpleEnum getEnumField();
-
-}
\ No newline at end of file
+}