Adding 2.1.1-rc1 tag.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/tags/2.1.1-rc1@9388 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/ExpensesCommon.gwt.xml b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/ExpensesCommon.gwt.xml
index e70fe2e..c4acdb2 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/ExpensesCommon.gwt.xml
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/ExpensesCommon.gwt.xml
@@ -2,10 +2,10 @@
 <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
 <module>
   <inherits name='com.google.gwt.activity.Activity' />
+  <inherits name='com.google.gwt.mobile.Mobile'/>
   <inherits name='com.google.gwt.place.Place' />
   <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
-  <!--  <inherits name='com.google.gwt.sample.expenses.client.style.Style'/> -->
-  <inherits name='com.google.gwt.mobile.Mobile'/>
+  <inherits name='com.google.gwt.sample.gaerequest.GaeRequest'/>
   <inherits name='com.google.gwt.user.cellview.CellView'/>
 
   <source path='client'/>
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportDetails.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportDetails.java
index d4c02c7..6ab26c8 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportDetails.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportDetails.java
@@ -16,6 +16,8 @@
 package com.google.gwt.sample.expenses.client;
 
 import com.google.gwt.activity.shared.Activity;
+import com.google.gwt.activity.shared.IsActivity;
+import com.google.gwt.activity.shared.SimpleActivity;
 import com.google.gwt.cell.client.AbstractInputCell;
 import com.google.gwt.cell.client.Cell;
 import com.google.gwt.cell.client.DateCell;
@@ -23,7 +25,6 @@
 import com.google.gwt.cell.client.NumberCell;
 import com.google.gwt.cell.client.TextCell;
 import com.google.gwt.cell.client.ValueUpdater;
-import com.google.gwt.cell.client.Cell.Context;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
@@ -47,6 +48,7 @@
 import com.google.gwt.requestfactory.ui.client.EntityProxyKeyProvider;
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
+import com.google.gwt.safehtml.client.SafeHtmlTemplates.Template;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
@@ -89,11 +91,11 @@
  * Details about the current expense report on the right side of the app,
  * including the list of expenses.
  */
-public class ExpenseReportDetails extends Composite implements Activity {
+public class ExpenseReportDetails extends Composite implements IsActivity {
 
   interface Binder extends UiBinder<Widget, ExpenseReportDetails> {
   }
-
+  
   /**
    * Fetches an employee and a report in parallel. A fine example of the kind of
    * thing that will no longer be necessary when RequestFactory provides server
@@ -127,7 +129,7 @@
       });
     }
   }
-
+  
   /**
    * The resources applied to the table.
    */
@@ -323,6 +325,13 @@
     }
   }
 
+  private final Activity activityAspect = new SimpleActivity() {
+    @Override
+    public void start(AcceptsOneWidget panel, EventBus eventBus) {
+      ExpenseReportDetails.this.start(panel, eventBus);
+    }
+  };
+
   private static Template template;
 
   /**
@@ -469,6 +478,10 @@
     });
   }
 
+  public Activity asActivity() {
+    return activityAspect;
+  }
+
   public ReportListPlace getReportListPlace() {
     ReportListPlace listPlace = place.getListPlace();
     return listPlace == null ? ReportListPlace.ALL : listPlace;
@@ -478,13 +491,6 @@
     return reportsLink;
   }
 
-  public String mayStop() {
-    return null;
-  }
-
-  public void onCancel() {
-  }
-
   public void onExpenseRecordChanged(EntityProxyChange<ExpenseProxy> event) {
     final EntityProxyId<ExpenseProxy> proxyId = event.getProxyId();
 
@@ -531,10 +537,17 @@
     }
   }
 
-  public void onStop() {
+  /**
+   * In this application, called by {@link ExpensesActivityMapper} each time a
+   * ReportListPlace is posted. In a more typical set up, this would be a
+   * constructor argument to a one shot activity, perhaps managing a shared
+   * widget view instance.
+   */
+  public void updateForPlace(final ReportPlace place) {
+    this.place = place;
   }
 
-  public void start(AcceptsOneWidget panel, EventBus eventBus) {
+  void start(AcceptsOneWidget panel, EventBus eventBus) {
     final ReportListPlace listPlace = place.getListPlace();
 
     if (listPlace.getEmployeeId() == null) {
@@ -575,16 +588,6 @@
   }
 
   /**
-   * In this application, called by {@link ExpensesActivityMapper} each time a
-   * ReportListPlace is posted. In a more typical set up, this would be a
-   * constructor argument to a one shot activity, perhaps managing a shared
-   * widget view instance.
-   */
-  public void updateForPlace(final ReportPlace place) {
-    this.place = place;
-  }
-
-  /**
    * Add a column of a {@link Comparable} type using default comparators.
    * 
    * @param <C> the column type
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportList.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportList.java
index 2df0dbf..e970c24 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportList.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseReportList.java
@@ -16,6 +16,8 @@
 package com.google.gwt.sample.expenses.client;
 
 import com.google.gwt.activity.shared.Activity;
+import com.google.gwt.activity.shared.IsActivity;
+import com.google.gwt.activity.shared.SimpleActivity;
 import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.cell.client.Cell;
 import com.google.gwt.cell.client.DateCell;
@@ -76,7 +78,7 @@
  * The list of expense reports on the right side of the app.
  */
 public class ExpenseReportList extends Composite implements
-    EntityProxyChange.Handler<ReportProxy>, Activity {
+    EntityProxyChange.Handler<ReportProxy>, IsActivity {
 
   interface Binder extends UiBinder<Widget, ExpenseReportList> {
   }
@@ -177,6 +179,23 @@
     }
   }
 
+  private final Activity activityAspect = new SimpleActivity() {
+    @Override
+    public void onCancel() {
+      ExpenseReportList.this.onCancel();
+    }
+
+    @Override
+    public void onStop() {
+      ExpenseReportList.this.onStop();
+    }
+
+    @Override
+    public void start(AcceptsOneWidget panel, EventBus eventBus) {
+      ExpenseReportList.this.start(panel, eventBus);
+    }
+  };
+
   private static final ProvidesKey<ReportProxy> keyProvider = new EntityProxyKeyProvider<ReportProxy>();
 
   /**
@@ -337,17 +356,13 @@
     });
   }
 
-  public String mayStop() {
-    return null;
-  }
-
-  public void onCancel() {
-    onStop();
+  public Activity asActivity() {
+    return activityAspect;
   }
 
   public void onProxyChange(EntityProxyChange<ReportProxy> event) {
     EntityProxyId<ReportProxy> changedId = event.getProxyId();
-    List<ReportProxy> records = table.getDisplayedItems();
+    List<ReportProxy> records = table.getVisibleItems();
     int i = 0;
     for (ReportProxy record : records) {
       if (record != null && changedId.equals(record.stableId())) {
@@ -359,24 +374,10 @@
     }
   }
 
-  public void onStop() {
-    running = false;
-    refreshTimer.cancel();
-  }
-
   public void setListener(Listener listener) {
     this.listener = listener;
   }
 
-  public void start(AcceptsOneWidget panel, EventBus eventBus) {
-    running = true;
-    doUpdateForPlace();
-
-    EntityProxyChange.registerForProxyType(eventBus, ReportProxy.class, this);
-    requestReports(false);
-    panel.setWidget(this);
-  }
-
   /**
    * In this application, called by {@link ExpensesActivityMapper} each time a
    * ReportListPlace is posted. In a more typical set up, this would be a
@@ -398,6 +399,24 @@
     return p;
   }
 
+  void onCancel() {
+    onStop();
+  }
+
+  void onStop() {
+    running = false;
+    refreshTimer.cancel();
+  }
+
+  void start(AcceptsOneWidget panel, EventBus eventBus) {
+    running = true;
+    doUpdateForPlace();
+
+    EntityProxyChange.registerForProxyType(eventBus, ReportProxy.class, this);
+    requestReports(false);
+    panel.setWidget(this);
+  }
+
   /**
    * Add a sortable column to the table.
    * 
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesActivityMapper.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesActivityMapper.java
index ed58976..c50f736 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesActivityMapper.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesActivityMapper.java
@@ -38,12 +38,12 @@
   public Activity getActivity(Place place) {
     if (place instanceof ReportListPlace) {
       expenseList.updateForPlace((ReportListPlace) place);
-      return expenseList;
+      return expenseList.asActivity();
     }
 
     if (place instanceof ReportPlace) {
       expenseDetails.updateForPlace((ReportPlace) place);
-      return expenseDetails;
+      return expenseDetails.asActivity();
     }
 
     return null;
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesApp.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesApp.java
index 0975f0a..fe52102 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesApp.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesApp.java
@@ -23,17 +23,11 @@
 import com.google.gwt.place.shared.PlaceController;
 import com.google.gwt.place.shared.PlaceHistoryHandler;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.Receiver;
-import com.google.gwt.requestfactory.shared.RequestEvent;
-import com.google.gwt.requestfactory.shared.UserInformationProxy;
-import com.google.gwt.requestfactory.ui.client.AuthenticationFailureHandler;
-import com.google.gwt.requestfactory.ui.client.LoginWidget;
 import com.google.gwt.sample.expenses.client.place.ReportListPlace;
 import com.google.gwt.sample.expenses.client.place.ReportPlace;
 import com.google.gwt.sample.expenses.shared.EmployeeProxy;
-import com.google.gwt.sample.expenses.shared.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.shared.ReportProxy;
-import com.google.gwt.user.client.Window.Location;
+import com.google.gwt.sample.gaerequest.client.ReloadOnAuthenticationFailure;
 import com.google.gwt.user.client.ui.HasWidgets;
 
 import java.util.logging.Level;
@@ -55,7 +49,6 @@
   private final EventBus eventBus;
   private final PlaceController placeController;
   private final PlaceHistoryHandler placeHistoryHandler;
-  private final ExpensesRequestFactory requestFactory;
   private final ExpensesShell shell;
 
   private EntityProxyId<EmployeeProxy> lastEmployee;
@@ -63,12 +56,11 @@
 
   public ExpensesApp(ActivityManager activityManager, EventBus eventBus,
       PlaceController placeController, PlaceHistoryHandler placeHistoryHandler,
-      ExpensesRequestFactory requestFactory, ExpensesShell shell) {
+      ExpensesShell shell) {
     this.activityManager = activityManager;
     this.eventBus = eventBus;
     this.placeController = placeController;
     this.placeHistoryHandler = placeHistoryHandler;
-    this.requestFactory = requestFactory;
     this.shell = shell;
   }
 
@@ -94,18 +86,7 @@
     });
 
     // Check for Authentication failures or mismatches
-    RequestEvent.register(eventBus, new AuthenticationFailureHandler());
-
-    // Kick off the login widget
-    final LoginWidget login = shell.getLoginWidget();
-    Receiver<UserInformationProxy> receiver = new Receiver<UserInformationProxy>() {
-      @Override
-      public void onSuccess(UserInformationProxy userInformationRecord) {
-        login.setUserInformation(userInformationRecord);
-      }
-    };
-    requestFactory.userInformationRequest().getCurrentUserInformation(
-        Location.getHref()).fire(receiver);
+    new ReloadOnAuthenticationFailure().register(eventBus);
 
     // Listen for requests from ExpenseTree.
     expenseTree.setListener(new ExpenseTree.Listener() {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobile.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobile.java
index e7ef3f4..6f6362b 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobile.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobile.java
@@ -20,21 +20,25 @@
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.event.shared.SimpleEventBus;
 import com.google.gwt.requestfactory.shared.Receiver;
-import com.google.gwt.requestfactory.shared.RequestEvent;
-import com.google.gwt.requestfactory.shared.UserInformationProxy;
-import com.google.gwt.requestfactory.ui.client.AuthenticationFailureHandler;
-import com.google.gwt.requestfactory.ui.client.LoginWidget;
 import com.google.gwt.sample.expenses.shared.EmployeeProxy;
 import com.google.gwt.sample.expenses.shared.ExpensesRequestFactory;
+import com.google.gwt.sample.gaerequest.client.GaeAuthRequestTransport;
+import com.google.gwt.sample.gaerequest.client.LoginWidget;
+import com.google.gwt.sample.gaerequest.client.ReloadOnAuthenticationFailure;
 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 java.util.logging.Level;
+import java.util.logging.Logger;
+
 /**
  * Entry point for the mobile version of the Expenses app.
+ * <p>
+ * TODO Should be using ExpenseFactory
  */
 public class ExpensesMobile implements EntryPoint {
+  private static final Logger log = Logger.getLogger(ExpensesMobile.class.getName());
 
   /**
    * The url parameter that specifies the employee id.
@@ -77,8 +81,7 @@
   public void onModuleLoad() {
     GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
       public void onUncaughtException(Throwable e) {
-        Window.alert("Error: " + e.getMessage());
-        // placeController.goTo(Place.NOWHERE);
+        log.log(Level.SEVERE, e.getMessage(), e);
       }
     });
 
@@ -96,29 +99,18 @@
 
     final EventBus eventBus = new SimpleEventBus();
     final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
-    requestFactory.initialize(eventBus);
+    requestFactory.initialize(eventBus, new GaeAuthRequestTransport(eventBus));
 
     requestFactory.employeeRequest().findEmployee(employeeId).fire(
         new Receiver<EmployeeProxy>() {
           @Override
           public void onSuccess(EmployeeProxy employee) {
             final ExpensesMobileShell shell = new ExpensesMobileShell(eventBus,
-                requestFactory, employee);
+                requestFactory, employee, new LoginWidget(requestFactory));
             RootPanel.get().add(shell);
 
             // Check for Authentication failures or mismatches
-            RequestEvent.register(eventBus, new AuthenticationFailureHandler());
-
-            // Add a login widget to the page
-            final LoginWidget login = shell.getLoginWidget();
-            Receiver<UserInformationProxy> receiver = new Receiver<UserInformationProxy>() {
-              @Override
-              public void onSuccess(UserInformationProxy userInformationRecord) {
-                login.setUserInformation(userInformationRecord);
-              }
-            };
-            requestFactory.userInformationRequest().getCurrentUserInformation(
-                Location.getHref()).fire(receiver);
+            new ReloadOnAuthenticationFailure().register(eventBus);
           }
         });
   }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.java
index f35cf1d..9d72842 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.java
@@ -19,11 +19,11 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.shared.EventBus;
-import com.google.gwt.requestfactory.ui.client.LoginWidget;
 import com.google.gwt.sample.expenses.shared.EmployeeProxy;
 import com.google.gwt.sample.expenses.shared.ExpenseProxy;
 import com.google.gwt.sample.expenses.shared.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.shared.ReportProxy;
+import com.google.gwt.sample.gaerequest.client.LoginWidget;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -39,13 +39,19 @@
  */
 public class ExpensesMobileShell extends Composite {
 
-  interface ShellUiBinder extends UiBinder<Widget, ExpensesMobileShell> { }
+  interface ShellUiBinder extends UiBinder<Widget, ExpensesMobileShell> {
+  }
+
   private static ShellUiBinder BINDER = GWT.create(ShellUiBinder.class);
 
-  @UiField SimplePanel container;
-  @UiField HTML backButton, addButton, refreshButton, customButton;
-  @UiField LoginWidget loginWidget;
-  @UiField Element titleSpan;
+  @UiField
+  SimplePanel container;
+  @UiField
+  HTML backButton, addButton, refreshButton, customButton;
+  @UiField(provided = true)
+  final LoginWidget loginWidget;
+  @UiField
+  Element titleSpan;
 
   private MobileReportList reportList;
   private MobileExpenseList expenseList;
@@ -59,10 +65,12 @@
   private ArrayList<MobilePage> pages = new ArrayList<MobilePage>();
 
   public ExpensesMobileShell(EventBus eventBus,
-      ExpensesRequestFactory requestFactory, EmployeeProxy employee) {
+      ExpensesRequestFactory requestFactory, EmployeeProxy employee,
+      LoginWidget loginWidget) {
     this.eventBus = eventBus;
     this.requestFactory = requestFactory;
     this.employee = employee;
+    this.loginWidget = loginWidget;
 
     initWidget(BINDER.createAndBindUi(this));
     showReportList();
@@ -74,7 +82,7 @@
   public LoginWidget getLoginWidget() {
     return loginWidget;
   }
-  
+
   @UiHandler("addButton")
   void onAdd(@SuppressWarnings("unused") ClickEvent evt) {
     topPage().onAdd();
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.ui.xml b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.ui.xml
index 6a392fd..f6727a5 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.ui.xml
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesMobileShell.ui.xml
@@ -3,17 +3,37 @@
   xmlns:ui='urn:ui:com.google.gwt.uibinder'
   xmlns:m='urn:import:com.google.gwt.mobile.client'
   xmlns:r='urn:import:com.google.gwt.requestfactory.ui.client'
-  xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:a='urn:import:com.google.gwt.sample.gaerequest.client'>
 
-  <ui:style field='mobile' src='mobile.css'/>
+  <ui:image field='add'/>
+  <ui:image field='refresh'/>
 
+  <ui:style field='mobile' src='mobile.css'>
+    @sprite .refresh {
+       gwt-image: "refresh";
+     }
+    @sprite .add {
+       gwt-image: "add";
+     }
+     .button {
+        cursor: pointer;
+      }
+      .backButton {
+        cursor: pointer;
+      }
+      .customButton {
+        cursor: pointer;
+      }
+  </ui:style>
+  
   <g:HTMLPanel>
-    <r:LoginWidget styleName='{mobile.login}' ui:field="loginWidget"/>
+    <a:LoginWidget styleName='{mobile.login}' ui:field="loginWidget"/>
     <div class='{mobile.bar}'>
       <g:HTML ui:field='backButton' styleName='{mobile.backButton}'><div>Back</div></g:HTML>
-      <g:HTML ui:field='addButton' styleName='{mobile.button}'><img src='images/add.png'/></g:HTML>
+      <g:HTML ui:field='addButton' addStyleNames='{mobile.button}'><div class='{mobile.add}'/></g:HTML>
       <g:HTML ui:field='customButton' styleName='{mobile.customButton}'/>
-      <g:HTML ui:field='refreshButton' styleName='{mobile.button}'><img src='images/refresh.png'/></g:HTML>
+      <g:HTML ui:field='refreshButton' styleName='{mobile.button}'><div class='{mobile.refresh}'/></g:HTML>
       <div class='{mobile.title}' ui:field='titleSpan'>Expenses</div>
     </div>
     <g:SimplePanel ui:field='container'/>
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.java
index bee5035..0fc57c0 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.java
@@ -16,7 +16,7 @@
 package com.google.gwt.sample.expenses.client;
 
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.requestfactory.ui.client.LoginWidget;
+import com.google.gwt.sample.gaerequest.client.LoginWidget;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
@@ -35,21 +35,25 @@
 
   @UiField(provided = true)
   final ExpenseReportList expenseList;
-  
+
   @UiField(provided = true)
   final ExpenseReportDetails expenseDetails;
 
   @UiField(provided = true)
   final ExpenseTree expenseTree;
-  
+
+  @UiField(provided = true)
+  final LoginWidget loginWidget;
+
   @UiField SlidingPanel slidingPanel;
-  @UiField LoginWidget loginWidget;
   @UiField DockLayoutPanel dockLayout;
 
-  public ExpensesShell(ExpenseTree expenseTree, ExpenseReportList expenseList, ExpenseReportDetails expenseDetails) {
+  public ExpensesShell(ExpenseTree expenseTree, ExpenseReportList expenseList,
+      ExpenseReportDetails expenseDetails, LoginWidget loginWidget) {
     this.expenseTree = expenseTree;
     this.expenseList = expenseList;
     this.expenseDetails = expenseDetails;
+    this.loginWidget = loginWidget;
     initWidget(uiBinder.createAndBindUi(this));
   }
 
@@ -68,7 +72,7 @@
   public LoginWidget getLoginWidget() {
     return loginWidget;
   }
-  
+
   public HasOneWidget getPanel() {
     return slidingPanel;
   }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.ui.xml b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.ui.xml
index a146dbd..391f1ee 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.ui.xml
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpensesShell.ui.xml
@@ -4,7 +4,9 @@
   xmlns:g='urn:import:com.google.gwt.user.client.ui'
   xmlns:m='urn:import:com.google.gwt.mobile.client'
   xmlns:r='urn:import:com.google.gwt.requestfactory.ui.client'
-  xmlns:e='urn:import:com.google.gwt.sample.expenses.client'>
+  xmlns:e='urn:import:com.google.gwt.sample.expenses.client'
+  xmlns:a='urn:import:com.google.gwt.sample.gaerequest.client'>
+
 
   <ui:with field='styles' type='com.google.gwt.sample.expenses.client.style.Styles' />
 
@@ -44,7 +46,7 @@
   <g:DockLayoutPanel unit='PX'>
     <g:north size='96'>
       <g:HTMLPanel styleName='{style.title}'>
-        <r:LoginWidget styleName='{style.login}' ui:field="loginWidget"/>
+        <a:LoginWidget styleName='{style.login}' ui:field="loginWidget"/>
         <table height='100%' cellpadding='8' cellspacing='0'>
           <tr>
             <td>
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportEntry.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportEntry.java
index 8d541a1..0270578 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportEntry.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/MobileReportEntry.java
@@ -166,9 +166,15 @@
 
   @SuppressWarnings("deprecation")
   private void showCreationDate(Date d) {
-    // TODO(jgw): Use non-deprecated date methods for this.
-    dateYear.setSelectedIndex(d.getYear() - 100);
-    dateMonth.setSelectedIndex(d.getMonth());
-    dateDay.setSelectedIndex(d.getDate() - 1);
+    if (d != null) {
+      // TODO(jgw): Use non-deprecated date methods for this.
+      dateYear.setSelectedIndex(d.getYear() - 100);
+      dateMonth.setSelectedIndex(d.getMonth());
+      dateDay.setSelectedIndex(d.getDate() - 1);
+    } else {
+      dateYear.setSelectedIndex(0);
+      dateMonth.setSelectedIndex(0);
+      dateDay.setSelectedIndex(0);
+    }
   }
 }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/add.png b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/add.png
new file mode 100644
index 0000000..4db1652
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/add.png
Binary files differ
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ioc/ExpensesFactory.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ioc/ExpensesFactory.java
index aa5ec92..62e7cb6 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ioc/ExpensesFactory.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ioc/ExpensesFactory.java
@@ -32,6 +32,8 @@
 import com.google.gwt.sample.expenses.client.place.ReportListPlace;
 import com.google.gwt.sample.expenses.client.place.ReportPlace;
 import com.google.gwt.sample.expenses.shared.ExpensesRequestFactory;
+import com.google.gwt.sample.gaerequest.client.GaeAuthRequestTransport;
+import com.google.gwt.sample.gaerequest.client.LoginWidget;
 
 /**
  * In charge of instantiation.
@@ -41,6 +43,7 @@
 public class ExpensesFactory {
 
   private final EventBus eventBus = new SimpleEventBus();
+  private final GaeAuthRequestTransport requestTransport = new GaeAuthRequestTransport(eventBus);
   private final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
   private final ExpensesPlaceHistoryMapper historyMapper = GWT.create(ExpensesPlaceHistoryMapper.class);
   private final PlaceHistoryHandler placeHistoryHandler;
@@ -49,21 +52,23 @@
   private final ExpenseReportList expenseList = new ExpenseReportList(requestFactory);
   private final ExpenseReportDetails expenseDetails = new ExpenseReportDetails(
       requestFactory);
+  private final LoginWidget loginWidget = new LoginWidget(requestFactory);
+  
   private final ActivityMapper activityMapper = new ExpensesActivityMapper(
       expenseDetails, expenseList);
   private final ActivityManager activityManager = new ActivityManager(
       activityMapper, eventBus);
 
   public ExpensesFactory() {
-    requestFactory.initialize(eventBus);
+    requestFactory.initialize(eventBus, requestTransport);
     historyMapper.setFactory(this);
     placeHistoryHandler = new PlaceHistoryHandler(historyMapper);
   }
 
   public ExpensesApp getExpensesApp() {
     return new ExpensesApp(activityManager, eventBus, placeController,
-        placeHistoryHandler, requestFactory, new ExpensesShell(expenseTree,
-            expenseList, expenseDetails));
+        placeHistoryHandler, new ExpensesShell(expenseTree,
+            expenseList, expenseDetails, loginWidget));
   }
 
   /**
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/refresh.png b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/refresh.png
new file mode 100644
index 0000000..454c107
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/refresh.png
Binary files differ
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/server/domain/GaeUserInformation.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/server/domain/GaeUserInformation.java
deleted file mode 100644
index f9d78e1..0000000
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/server/domain/GaeUserInformation.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.sample.expenses.server.domain;
-
-import com.google.appengine.api.users.User;
-import com.google.appengine.api.users.UserService;
-import com.google.appengine.api.users.UserServiceFactory;
-import com.google.gwt.requestfactory.server.UserInformation;
-
-/**
- * A user information class that uses the Google App Engine authentication
- * framework.
- */
-public class GaeUserInformation extends UserInformation {
-  private static UserService userService = UserServiceFactory.getUserService();
-
-  public static GaeUserInformation getCurrentUserInformation(String redirectUrl) {
-    return new GaeUserInformation(redirectUrl);
-  }
-  
-  public GaeUserInformation(String redirectUrl) {
-    super(redirectUrl);
-  }
-  
-  @Override
-  public String getEmail() {
-    User user = userService.getCurrentUser();
-    if (user == null) {
-      return "";
-    }
-    return user.getEmail();
-  }
-
-  @Override
-  public Long getId() {
-    User user = userService.getCurrentUser();
-    if (user == null) {
-      return 0L;
-    }
-    return new Long(user.hashCode());
-  }
-  
-  @Override
-  public String getLoginUrl() {
-    return userService.createLoginURL(redirectUrl);
-  }
-  
-  @Override
-  public String getLogoutUrl() {
-    return userService.createLogoutURL(redirectUrl);
-  }
-  
-  @Override
-  public String getName() {
-    User user = userService.getCurrentUser();
-    if (user == null) {
-      return "";
-    }
-    return user.getNickname();
-  }
-  
-  @Override
-  public boolean isUserLoggedIn() {
-    return userService.isUserLoggedIn();
-  }
-
-  /**
-   * Does nothing since in GAE authentication, the unique ID is provided by
-   * the user service and is based on a hash in the User object.
-   */
-  @Override
-  public void setId(Long id) {
-    // Do nothing
-  }
-  
-}
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeProxy.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeProxy.java
index fffc9f1..4a92b9c 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeProxy.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeProxy.java
@@ -20,10 +20,7 @@
 import com.google.gwt.requestfactory.shared.ProxyFor;
 
 /**
- * "API Generated" DTO interface based on
- * {@link com.google.gwt.sample.expenses.server.domain.Employee}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ * Employee DTO.
  */
 @ProxyFor(com.google.gwt.sample.expenses.server.domain.Employee.class)
 public interface EmployeeProxy extends EntityProxy {
@@ -31,6 +28,10 @@
 
   String getDisplayName();
 
+  /* 
+   * TODO You shouldn't need to expose Ids like this.
+   * Instead use EntityProxy.stableId() and RequestFactory.find() 
+   */
   Long getId();
 
   String getPassword();
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeRequest.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeRequest.java
index 0391a48..ffc7b57 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeRequest.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/EmployeeRequest.java
@@ -24,11 +24,7 @@
 import java.util.List;
 
 /**
- * "API Generated" request selector interface implemented by objects that give
- * client access to the methods of
- * {@link com.google.gwt.sample.expenses.server.domain.Employee}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ * Builds requests for the Employee service.
  */
 @Service(Employee.class)
 public interface EmployeeRequest extends RequestContext {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseProxy.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseProxy.java
index eabf78e..1919f7a 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseProxy.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseProxy.java
@@ -22,10 +22,7 @@
 import java.util.Date;
 
 /**
- * "API Generated" DTO interface based on
- * {@link com.google.gwt.sample.expenses.server.domain.Expense}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ * Expense DTO.
  */
 @ProxyFor(com.google.gwt.sample.expenses.server.domain.Expense.class)
 public interface ExpenseProxy extends EntityProxy {
@@ -39,6 +36,10 @@
 
   String getDescription();
 
+  /* 
+   * TODO You shouldn't need to expose Ids like this.
+   * Instead use EntityProxy.stableId() and RequestFactory.find() 
+   */
   Long getId();
 
   String getReasonDenied();
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseRequest.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseRequest.java
index 38a279c..9f61c3a 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseRequest.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpenseRequest.java
@@ -24,11 +24,7 @@
 import java.util.List;
 
 /**
- * "API Generated" request selector interface implemented by objects that give
- * client access to the methods of
- * {@link com.google.gwt.sample.expenses.server.domain.Expense}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ * Builds requests for the ExpenseRequest service.
  */
 @Service(Expense.class)
 public interface ExpenseRequest extends RequestContext {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesEntityTypesProcessor.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesEntityTypesProcessor.java
index 23b7003..9ac89f4 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesEntityTypesProcessor.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesEntityTypesProcessor.java
@@ -22,11 +22,6 @@
 import java.util.Set;
 
 /**
- * "API Generated" tool for resolving an arbitray Class to a specific proxy
- * type.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
- * <p>
  * A helper class for dealing with proxy types. Subclass it and override the
  * various handle methods for type specific handling of proxy objects or
  * classes, then call {@link #process(Class)} or {@link #process(Object)}.
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesRequestFactory.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesRequestFactory.java
index 201abcb..53b56a0 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesRequestFactory.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ExpensesRequestFactory.java
@@ -16,17 +16,13 @@
 package com.google.gwt.sample.expenses.shared;
 
 import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.requestfactory.shared.UserInformationRequest;
+import com.google.gwt.sample.gaerequest.shared.MakesGaeRequests;
 
 /**
- * "API generated" factory interface to build request objects for the methods of
- * {@link com.google.gwt.sample.expenses.server.domain}. Client code can
- * GWT.create() an instance of this interface to build and fire request objects.
- * <p>
- * IRL this interface will be generated by a JPA-savvy tool run before
- * compilation.
+ * RequestFactory interface. Instances created via {@link com.google.gwt.core.client.GWT#create}
+ * can insantiate RPC request objects.
  */
-public interface ExpensesRequestFactory extends RequestFactory {
+public interface ExpensesRequestFactory extends RequestFactory, MakesGaeRequests {
 
   /**
    * @return a request selector
@@ -42,10 +38,4 @@
    * @return a request selector
    */
   ReportRequest reportRequest();
-
-  /**
-   * @return a request selector
-   */
-  UserInformationRequest userInformationRequest();
-
 }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportProxy.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportProxy.java
index 0bf0c48..846a949 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportProxy.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportProxy.java
@@ -22,10 +22,7 @@
 import java.util.Date;
 
 /**
- * "API Generated" DTO interface based on
- * {@link com.google.gwt.sample.expenses.server.domain.Report}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ * Report DTO.
  */
 @ProxyFor(com.google.gwt.sample.expenses.server.domain.Report.class)
 public interface ReportProxy extends EntityProxy {
@@ -35,6 +32,10 @@
 
   String getDepartment();
 
+  /* 
+   * TODO You shouldn't need to expose Ids like this.
+   * Instead use EntityProxy.stableId() and RequestFactory.find() 
+   */
   Long getId();
 
   String getNotes();
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportRequest.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportRequest.java
index 40af51e..2cb6a73 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportRequest.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/shared/ReportRequest.java
@@ -24,11 +24,7 @@
 import java.util.List;
 
 /**
- * "API Generated" request selector interface implemented by objects that give
- * client access to the methods of
- * {@link com.google.gwt.sample.expenses.server.domain.Report}.
- * <p>
- * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ * Builds requests for the Report service.
  */
 @Service(Report.class)
 public interface ReportRequest extends RequestContext {
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/GaeRequest.gwt.xml b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/GaeRequest.gwt.xml
new file mode 100644
index 0000000..4b46229
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/GaeRequest.gwt.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
+
+  <source path='client'/>
+  <source path='shared'/>
+</module>
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/GaeAuthRequestTransport.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/GaeAuthRequestTransport.java
new file mode 100644
index 0000000..74b8dba
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/GaeAuthRequestTransport.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.gaerequest.client;
+
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.requestfactory.client.DefaultRequestTransport;
+import com.google.gwt.requestfactory.shared.ServerFailure;
+
+/**
+ * Extends DefaultRequestTransport to handle the authentication failures
+ * reported by {@link com.google.gwt.sample.gaerequest.server.GaeAuthFilter}
+ */
+public class GaeAuthRequestTransport extends DefaultRequestTransport {
+  private final EventBus eventBus;
+
+  public GaeAuthRequestTransport(EventBus eventBus) {
+    this.eventBus = eventBus;
+  }
+
+  @Override
+  protected RequestCallback createRequestCallback(
+      final TransportReceiver receiver) {
+    final RequestCallback superCallback = super.createRequestCallback(receiver);
+
+    return new RequestCallback() {
+      public void onResponseReceived(Request request, Response response) {
+        /*
+         * The GaeAuthFailure filter responds with Response.SC_UNAUTHORIZED and
+         * adds a "login" url header if the user is not logged in. When we
+         * receive that combo, post an event so that the app can handle things
+         * as it sees fit.
+         */
+
+        if (Response.SC_UNAUTHORIZED == response.getStatusCode()) {
+          String loginUrl = response.getHeader("login");
+          if (loginUrl != null) {
+            /*
+             * Hand the receiver a non-fatal callback, so that
+             * com.google.gwt.requestfactory.shared.Receiver will not post a
+             * runtime exception.
+             */
+            receiver.onTransportFailure(new ServerFailure(
+                "Unauthenticated user", null, null, false /* not fatal */));
+            eventBus.fireEvent(new GaeAuthenticationFailureEvent(loginUrl));
+            return;
+          }
+        }
+        superCallback.onResponseReceived(request, response);
+      }
+
+      public void onError(Request request, Throwable exception) {
+        superCallback.onError(request, exception);
+      }
+    };
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestEvent.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/GaeAuthenticationFailureEvent.java
similarity index 61%
rename from user/src/com/google/gwt/requestfactory/shared/RequestEvent.java
rename to samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/GaeAuthenticationFailureEvent.java
index c08641d..fab5e22 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestEvent.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/GaeAuthenticationFailureEvent.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.requestfactory.shared;
+package com.google.gwt.sample.gaerequest.client;
 
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.event.shared.EventHandler;
@@ -22,50 +22,41 @@
 import com.google.gwt.http.client.Response;
 
 /**
- * An event posted whenever an RPC request is sent or its response is received.
+ * An event posted when an authentication failure is detected.
  */
-public class RequestEvent extends GwtEvent<RequestEvent.Handler> {
+public class GaeAuthenticationFailureEvent extends GwtEvent<GaeAuthenticationFailureEvent.Handler> {
   
   /**
    * Implemented by handlers of this type of event.
    */
   public interface Handler extends EventHandler {
     /**
-     * Called when a {@link RequestEvent} is fired.
+     * Called when a {@link GaeAuthenticationFailureEvent} is fired.
      *
-     * @param requestEvent a {@link RequestEvent} instance
+     * @param requestEvent a {@link GaeAuthenticationFailureEvent} instance
      */
-    void onRequestEvent(RequestEvent requestEvent);
-  }
-
-  /**
-   * The request state.
-   */
-  public enum State {
-    SENT, RECEIVED
+    void onAuthFailure(GaeAuthenticationFailureEvent requestEvent);
   }
 
   private static final Type<Handler> TYPE = new Type<Handler>();
 
   /**
-   * Register a {@link RequestEvent.Handler} on an {@link EventBus}.
+   * Register a {@link GaeAuthenticationFailureEvent.Handler} on an {@link EventBus}.
    *
    * @param eventBus the {@link EventBus}
-   * @param handler a {@link RequestEvent.Handler}
+   * @param handler a {@link GaeAuthenticationFailureEvent.Handler}
    * @return a {@link HandlerRegistration} instance
    */
   public static HandlerRegistration register(EventBus eventBus,
-      RequestEvent.Handler handler) {
+      GaeAuthenticationFailureEvent.Handler handler) {
     return eventBus.addHandler(TYPE, handler);
   }
 
-  private final State state;
-
   /**
    * Will only be non-null if this is an event of type {@link State#RECEIVED},
    * and the RPC was successful.
    */
-  private final Response response;
+  private final String loginUrl;
 
   /**
    * Constructs a new @{link RequestEvent}.
@@ -73,9 +64,8 @@
    * @param state a {@link State} instance
    * @param response a {@link Response} instance
    */
-  public RequestEvent(State state, Response response) {
-    this.state = state;
-    this.response = response;
+  public GaeAuthenticationFailureEvent(String loginUrl) {
+    this.loginUrl = loginUrl;
   }
 
   @Override
@@ -84,25 +74,16 @@
   }
 
   /**
-   * Returns the {@link Response} associated with this event.
-   *
+   * Returns the URL the user can visit to reauthenticate.
+   * 
    * @return a {@link Response} instance
    */
-  public Response getResponse() {
-    return response;
-  }
-
-  /**
-   * Returns the {@link State} associated with this event.
-   *
-   * @return a {@link State} instance
-   */
-  public State getState() {
-    return state;
+  public String getLoginUrl() {
+    return loginUrl;
   }
 
   @Override
   protected void dispatch(Handler handler) {
-    handler.onRequestEvent(this);
+    handler.onAuthFailure(this);
   }
 }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/LoginWidget.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/LoginWidget.java
new file mode 100644
index 0000000..be5a9cf
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/LoginWidget.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.sample.gaerequest.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.gaerequest.shared.GaeUser;
+import com.google.gwt.sample.gaerequest.shared.GaeUserServiceRequest;
+import com.google.gwt.sample.gaerequest.shared.MakesGaeRequests;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Window.Location;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A simple widget which displays info about the user and a logout link. In real
+ * life you'd probably blow this up into MVP parts.
+ * <p>
+ * On the other hand, it's pleasant that this widget is completely self
+ * contained, taking care of its own RPC needs when awoken by being attached to
+ * the dom. Hopefully that's not too magical.
+ */
+public class LoginWidget extends Composite {
+  interface Binder extends UiBinder<Widget, LoginWidget> {
+  }
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+
+  private final MakesGaeRequests requests;
+
+  @UiField
+  SpanElement name;
+  @UiField
+  Anchor logoutLink;
+
+  public LoginWidget(MakesGaeRequests requests) {
+    this.requests = requests;
+    initWidget(BINDER.createAndBindUi(this));
+  }
+
+  @Override
+  protected void onLoad() {
+    GaeUserServiceRequest request = requests.userServiceRequest();
+
+    request.createLogoutURL(Location.getHref()).to(new Receiver<String>() {
+      public void onSuccess(String response) {
+        setLogoutUrl(response);
+      }
+    });
+    request.getCurrentUser().to(new Receiver<GaeUser>() {
+      @Override
+      public void onSuccess(GaeUser response) {
+        setUserName(response.getNickname());
+      }
+    });
+    request.fire();
+  }
+
+  public void setUserName(String userName) {
+    name.setInnerText(userName);
+  }
+
+  public void setLogoutUrl(String url) {
+    logoutLink.setHref(url);
+  }
+
+  /**
+   * Squelch clicks of the logout link if no href has been set.
+   */
+  @UiHandler("logoutLink")
+  void handleClick(ClickEvent e) {
+    if ("".equals(logoutLink.getHref())) {
+      e.stopPropagation();
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/LoginWidget.ui.xml b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/LoginWidget.ui.xml
similarity index 60%
rename from user/src/com/google/gwt/requestfactory/ui/client/LoginWidget.ui.xml
rename to samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/LoginWidget.ui.xml
index 13259d7..3acf351 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/LoginWidget.ui.xml
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/LoginWidget.ui.xml
@@ -2,14 +2,16 @@
   xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:style>
     .link {
-       cursor: pointer;
+       /* make it look like text */
+       color: inherit;
+       text-decoration: inherit;
      }
   </ui:style>
   
   <g:HTMLPanel>
     <div>
       <span ui:field="name">Not logged in</span> |
-      <g:InlineLabel ui:field="logoutLink" addStyleNames="{style.link}">Sign out</g:InlineLabel>
+      <g:Anchor ui:field="logoutLink" addStyleNames="{style.link}">Sign out</g:Anchor>
     </div>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/ReloadOnAuthenticationFailure.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/ReloadOnAuthenticationFailure.java
new file mode 100644
index 0000000..8e5f52f
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/client/ReloadOnAuthenticationFailure.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.sample.gaerequest.client;
+
+import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Window.Location;
+
+/**
+ * A minimal auth failure handler which takes the user a login page.
+ */
+public class ReloadOnAuthenticationFailure implements
+    GaeAuthenticationFailureEvent.Handler {
+  
+  public HandlerRegistration register(EventBus eventBus) {
+    return GaeAuthenticationFailureEvent.register(eventBus, this);
+  }
+  
+  public void onAuthFailure(GaeAuthenticationFailureEvent requestEvent) {
+    Location.replace(requestEvent.getLoginUrl());
+  }
+}
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/GaeAuthFilter.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/GaeAuthFilter.java
new file mode 100644
index 0000000..96337a5
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/GaeAuthFilter.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.sample.gaerequest.server;
+
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A servlet filter that handles basic GAE user authentication.
+ */
+public class GaeAuthFilter implements Filter {
+
+  public void destroy() {
+  }
+
+  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+      FilterChain filterChain) throws IOException, ServletException {
+    UserService userService = UserServiceFactory.getUserService();
+    HttpServletRequest request = (HttpServletRequest) servletRequest;
+    HttpServletResponse response = (HttpServletResponse) servletResponse;
+    
+    if (!userService.isUserLoggedIn()) {
+      response.setHeader("login", userService.createLoginURL(request.getRequestURI()));
+      response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+      return; 
+    } 
+
+    filterChain.doFilter(request, response);
+  }
+
+  public void init(FilterConfig config) {
+  }
+}
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/UserServiceLocator.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/UserServiceLocator.java
new file mode 100644
index 0000000..6181b17
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/UserServiceLocator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.gaerequest.server;
+
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.gwt.requestfactory.shared.ServiceLocator;
+
+/**
+ * Gives a RequestFactory system access to the Google AppEngine UserService.
+ */
+public class UserServiceLocator implements ServiceLocator {
+  public UserServiceWrapper getInstance(Class<?> clazz) {
+    final UserService service = UserServiceFactory.getUserService();
+    return new UserServiceWrapper() {
+
+      public String createLoginURL(String destinationURL) {
+        return service.createLoginURL(destinationURL);
+      }
+
+      public String createLogoutURL(String destinationURL) {
+        return service.createLogoutURL(destinationURL);
+      }
+
+      public User getCurrentUser() {
+        return service.getCurrentUser();
+      }
+    };
+  }
+}
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/UserServiceWrapper.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/UserServiceWrapper.java
new file mode 100644
index 0000000..2c49262
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/server/UserServiceWrapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.gaerequest.server;
+
+import com.google.appengine.api.users.User;
+
+/**
+ * Service object that reduces the visible api of
+ * {@link com.google.appengine.api.users.UserService}. Needed to work around a
+ * limitation of RequestFactory, which cannot yet handle overloaded service
+ * methods.
+ */
+public interface UserServiceWrapper {
+  public String createLoginURL(String destinationURL);
+
+  public String createLogoutURL(String destinationURL);
+
+  public User getCurrentUser();
+}
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/GaeUser.java
similarity index 61%
copy from user/src/com/google/gwt/activity/shared/AbstractActivity.java
copy to samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/GaeUser.java
index 59e596c..28895c3 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/GaeUser.java
@@ -13,21 +13,16 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.activity.shared;
+package com.google.gwt.sample.gaerequest.shared;
+
+import com.google.gwt.requestfactory.shared.ProxyForName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
 
 /**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
+ * Client visible proxy of Google AppEngine User class.
  */
-public abstract class AbstractActivity implements Activity {
-
-  public String mayStop() {
-    return null;
-  }
-
-  public void onCancel() {
-  }
-
-  public void onStop() {
-  }
+@ProxyForName("com.google.appengine.api.users.User")
+public interface GaeUser extends ValueProxy {
+  String getNickname();
+  String getEmail();
 }
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/GaeUserServiceRequest.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/GaeUserServiceRequest.java
new file mode 100644
index 0000000..f1217ab
--- /dev/null
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/GaeUserServiceRequest.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.sample.gaerequest.shared;
+
+import com.google.gwt.requestfactory.shared.Request;
+import com.google.gwt.requestfactory.shared.RequestContext;
+import com.google.gwt.requestfactory.shared.ServiceName;
+
+/**
+ * Makes requests of the Google AppEngine UserService.
+ */
+@ServiceName(value = "com.google.gwt.sample.gaerequest.server.UserServiceWrapper", 
+    locator = "com.google.gwt.sample.gaerequest.server.UserServiceLocator")
+public interface GaeUserServiceRequest extends RequestContext {
+  public Request<String> createLoginURL(String destinationURL);
+
+  public Request<String> createLogoutURL(String destinationURL);
+
+  public Request<GaeUser> getCurrentUser();
+}
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/MakesGaeRequests.java
similarity index 63%
copy from user/src/com/google/gwt/activity/shared/AbstractActivity.java
copy to samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/MakesGaeRequests.java
index 59e596c..63d0c9e 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/gaerequest/shared/MakesGaeRequests.java
@@ -1,33 +1,28 @@
 /*
  * 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.activity.shared;
+package com.google.gwt.sample.gaerequest.shared;
 
 /**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
+ * Implemented by {@link com.google.gwt.requestfactory.shared.RequestFactory}s
+ * that vend AppEngine requests.
  */
-public abstract class AbstractActivity implements Activity {
+public interface MakesGaeRequests {
 
-  public String mayStop() {
-    return null;
-  }
-
-  public void onCancel() {
-  }
-
-  public void onStop() {
-  }
+  /**
+   * Return a request selector.
+   */
+  GaeUserServiceRequest userServiceRequest();
 }
diff --git a/samples/expenses/src/main/resources/log4j.properties b/samples/expenses/src/main/resources/log4j.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/expenses/src/main/resources/log4j.properties
diff --git a/samples/expenses/src/main/webapp/WEB-INF/web.xml b/samples/expenses/src/main/webapp/WEB-INF/web.xml
index 44791a1..9c7305d 100644
--- a/samples/expenses/src/main/webapp/WEB-INF/web.xml
+++ b/samples/expenses/src/main/webapp/WEB-INF/web.xml
@@ -4,57 +4,153 @@
     "http://java.sun.com/dtd/web-app_2_3.dtd">
 
 <web-app>
+  <security-constraint>
+    <display-name>
+      Redirect to the login page if needed before showing
+      any html pages
+    </display-name>
+    <web-resource-collection>
+      <url-pattern>*.html</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>*</role-name>
+    </auth-constraint>
+  </security-constraint>
 
-	<!-- Servlets -->
-	<servlet>
-		<servlet-name>requestFactoryServlet</servlet-name>
-		<servlet-class>com.google.gwt.requestfactory.server.RequestFactoryServlet</servlet-class>
-	</servlet>
+  <!-- 
+  
+    Expenses.html and ExpensesMobile.html rely upon RequestFactoryServlet
 
-	<servlet>
-		<servlet-name>dataGeneration</servlet-name>
-		<servlet-class>com.google.gwt.sample.expenses.server.DataGenerationServiceImpl</servlet-class>
-	</servlet>
+   -->
 
-	<!-- Enable remote API on Java (for datastore bulkloader). You also need 
-		to add appengine-tools-api.jar from the appengine plugin directory to war/WEB-INF/lib -->
-	<servlet>
-		<servlet-name>remoteapi</servlet-name>
-		<servlet-class>com.google.apphosting.utils.remoteapi.RemoteApiServlet</servlet-class>
-	</servlet>
+  <servlet>
+    <servlet-name>requestFactoryServlet</servlet-name>
+    <servlet-class>com.google.gwt.requestfactory.server.RequestFactoryServlet</servlet-class>
+  </servlet>
 
-	<servlet-mapping>
-		<servlet-name>remoteapi</servlet-name>
-		<url-pattern>/remote_api</url-pattern>
-	</servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>requestFactoryServlet</servlet-name>
+    <url-pattern>/gwtRequest</url-pattern>
+  </servlet-mapping>
 
-	<servlet-mapping>
-		<servlet-name>requestFactoryServlet</servlet-name>
-		<url-pattern>/gwtRequest</url-pattern>
-	</servlet-mapping>
+  <filter>
+    <filter-name>GaeAuthFilter</filter-name>
+    <filter-class>com.google.gwt.sample.gaerequest.server.GaeAuthFilter</filter-class>
+    <description>
+      This filter demonstrates making GAE authentication
+      services visible to a RequestFactory client.
+    </description>
+  </filter>
+  <filter-mapping>
+    <filter-name>GaeAuthFilter</filter-name>
+    <url-pattern>/gwtRequest/*</url-pattern>
+  </filter-mapping>
 
-	<servlet-mapping>
-		<servlet-name>dataGeneration</servlet-name>
-		<url-pattern>/loadexpensesdb/dataGeneration</url-pattern>
-	</servlet-mapping>
 
-	<!-- AppStats -->
-	<!-- <servlet> <servlet-name>appstats</servlet-name> <servlet-class>com.google.appengine.tools.appstats.AppstatsServlet</servlet-class> 
-		<init-param> <param-name>requireAdminAuthentication</param-name> <param-value>false</param-value> 
-		</init-param> </servlet> <servlet-mapping> <servlet-name>appstats</servlet-name> 
-		<url-pattern>/appstats/*</url-pattern> </servlet-mapping> <filter> <filter-name>appstats</filter-name> 
-		<filter-class>com.google.appengine.tools.appstats.AppstatsFilter</filter-class> 
-		<init-param> <param-name>logMessage</param-name> <param-value>Appstats available: 
-		/appstats/details?time={ID}</param-value> </init-param> <init-param> <param-name>basePath</param-name> 
-		<param-value>/appstats/</param-value> </init-param> </filter> <filter-mapping> 
-		<filter-name>appstats</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> -->
+  <!-- 
+  
+    LoadExpensesDB.html uses GWT RPC to implement its DataGenerationService
 
-	<!-- <security-constraint> <web-resource-collection> <web-resource-name>remoteapi</web-resource-name> 
-		<url-pattern>/remote_api</url-pattern> </web-resource-collection> <auth-constraint> 
-		<role-name>admin</role-name> </auth-constraint> </security-constraint> -->
+   -->
 
-	<!-- Require login. -->
-	<!-- <security-constraint> <web-resource-collection> <web-resource-name>Access</web-resource-name> 
-		<url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> 
-		<role-name>admin</role-name> </auth-constraint> </security-constraint> -->
+  <servlet>
+    <servlet-name>dataGeneration</servlet-name>
+    <servlet-class>com.google.gwt.sample.expenses.server.DataGenerationServiceImpl</servlet-class>
+    <description>
+      GWT RPC service used by LoadExpensesDB.html
+    </description>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>dataGeneration</servlet-name>
+    <url-pattern>/loadexpensesdb/dataGeneration</url-pattern>
+  </servlet-mapping>
+
+  <!--<security-constraint> 
+    <display-name>
+      Require admin access for LoadExpensesDB.html and its servlet
+    </display-name>
+    <web-resource-collection> 
+      <url-pattern>/loadexpensesdb/*</url-pattern> 
+      <url-pattern>/LoadExpensesDB.html</url-pattern>
+    </web-resource-collection> 
+    <auth-constraint> 
+      <role-name>admin</role-name> 
+    </auth-constraint> 
+  </security-constraint>-->
+
+
+	<!-- 
+  
+    AppStats 
+    
+    Uncomment to use GAE's App Stats
+    http://code.google.com/appengine/docs/java/tools/appstats.html
+    
+    Visualize the stats with Speed Tracer
+    http://code.google.com/webtoolkit/speedtracer/server-side-tracing.html
+    
+  -->
+  
+  <!--<servlet>
+    <servlet-name>appstats</servlet-name>
+    <servlet-class>com.google.appengine.tools.appstats.AppstatsServlet</servlet-class>
+    <init-param>
+      <param-name>requireAdminAuthentication</param-name>
+      <param-value>false</param-value>
+    </init-param>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>appstats</servlet-name>
+    <url-pattern>/appstats/*</url-pattern>
+  </servlet-mapping>
+  <filter>
+    <filter-name>appstats</filter-name>
+    <filter-class>com.google.appengine.tools.appstats.AppstatsFilter</filter-class>
+    <init-param>
+      <param-name>logMessage</param-name>
+      <param-value>Appstats available:
+        /appstats/details?time={ID}</param-value>
+    </init-param>
+    <init-param>
+      <param-name>basePath</param-name>
+      <param-value>/appstats/</param-value>
+    </init-param>
+  </filter>
+  <filter-mapping>
+    <filter-name>appstats</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>-->
+
+
+  <!-- 
+  
+    GAE Remote API provides a bulk uploader and other goodies
+    
+   -->
+
+  <!--  <servlet>
+    <servlet-name>remoteapi</servlet-name>
+    <servlet-class>com.google.apphosting.utils.remoteapi.RemoteApiServlet</servlet-class>
+    <description>
+      Provides access to the GAE datastore bulkloader, requires appengine-tools-api.jar
+      (which is commented out in pom.xml, qv)
+    </description>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>remoteapi</servlet-name>
+    <url-pattern>/remote_api</url-pattern>
+  </servlet-mapping>
+
+  <security-constraint> 
+    <display-name>
+      Require admin access for remoteapi
+    </display-name>
+    <web-resource-collection> 
+      <url-pattern>/remote_api</url-pattern> 
+    </web-resource-collection> 
+    <auth-constraint> 
+      <role-name>admin</role-name> 
+    </auth-constraint> 
+  </security-constraint>-->
+
 </web-app>
diff --git a/tools/api-checker/config/gwt21_22userApi.conf b/tools/api-checker/config/gwt21_22userApi.conf
index 3e4f820..8271107 100644
--- a/tools/api-checker/config/gwt21_22userApi.conf
+++ b/tools/api-checker/config/gwt21_22userApi.conf
@@ -41,6 +41,9 @@
 :com/google/gwt/resources/rg/**\
 :com/google/gwt/requestfactory/client/impl/FindRequest.java\
 :com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
+:com/google/gwt/requestfactory/shared/UserInformationProxy.java\
+:com/google/gwt/requestfactory/shared/UserInformationRequest.java\
+:com/google/gwt/requestfactory/ui/client/LoginWidget.java\
 :com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
 :com/google/gwt/rpc/client/impl/EscapeUtil.java\
 :com/google/gwt/soyc/**\
@@ -108,6 +111,18 @@
 # when adding to the white-list, include comments as to why the addition is
 # being made.
 
+# Changes to make Activity api evolvable in 2.1.1
+com.google.gwt.activity.shared.AbstractActivity MISSING
+com.google.gwt.activity.shared.Activity STATIC_REMOVED
+
+# RequestFactory tweaks in 2.1.1
+com.google.gwt.requestfactory.client.DefaultRequestTransport::DefaultRequestTransport(Lcom/google/gwt/event/shared/EventBus;) MISSING
+com.google.gwt.requestfactory.shared.RequestEvent MISSING
+com.google.gwt.requestfactory.shared.RequestEvent.Handler MISSING
+com.google.gwt.requestfactory.shared.RequestEvent.State MISSING
+com.google.gwt.requestfactory.shared.ServerFailure::ServerFailure(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;) MISSING
+com.google.gwt.requestfactory.ui.client.AuthenticationFailureHandler MISSING
+
 # AutoBean packages have been moved since 2.1
 com.google.gwt.editor.client.AutoBean MISSING
 com.google.gwt.editor.client.AutoBeanFactory MISSING
diff --git a/user/src/com/google/gwt/activity/shared/Activity.java b/user/src/com/google/gwt/activity/shared/Activity.java
index f141171..209f350 100644
--- a/user/src/com/google/gwt/activity/shared/Activity.java
+++ b/user/src/com/google/gwt/activity/shared/Activity.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -19,45 +19,51 @@
 import com.google.gwt.user.client.ui.AcceptsOneWidget;
 
 /**
- * Implemented by objects that control a piece of user interface, with a life
- * cycle managed by an {@link ActivityManager}, in response to
- * {@link com.google.gwt.place.shared.PlaceChangeEvent} events as the user
- * navigates through the app.
+ * Object that controls a piece of user interface, with a life cycle managed by
+ * an {@link ActivityManager}.
+ * <p>
+ * Ideally this would be an interface rather than an abstract class, but we
+ * expect its api will need to evolve (slightly) in the near term. When it
+ * settles down, an interface may be introduced. To this end, future versions of
+ * this class should not introduce non-trivial behavior.
+ * <p>
+ * For composition, see {@link IsActivity}.
  */
-public interface Activity {
+public abstract class Activity {
+
   /**
    * Called when the user is trying to navigate away from this activity.
-   *
+   * 
    * @return A message to display to the user, e.g. to warn of unsaved work, or
    *         null to say nothing
    */
-  String mayStop();
+  public abstract String mayStop();
 
   /**
    * Called when {@link #start} has not yet replied to its callback, but the
    * user has lost interest.
    */
-  void onCancel();
+  public abstract void onCancel();
 
   /**
    * Called when the Activity's widget has been removed from view. All event
    * handlers it registered will have been removed before this method is called.
    */
-  void onStop();
+  public abstract void onStop();
 
   /**
    * Called when the Activity should ready its widget for the user. When the
    * widget is ready (typically after an RPC response has been received),
    * receiver should present it by calling
-   * {@link AcceptsOneWidget#setWidget(IsWidget)} on the given panel.
+   * {@link AcceptsOneWidget#setWidget()} on the given panel.
    * <p>
    * Any handlers attached to the provided event bus will be de-registered when
    * the activity is stopped, so activities will rarely need to hold on to the
    * {@link com.google.gwt.event.shared.HandlerRegistration HandlerRegistration}
    * instances returned by {@link EventBus#addHandler}.
-   *
+   * 
    * @param panel the panel to display this activity's widget when it is ready
    * @param eventBus the event bus
    */
-  void start(AcceptsOneWidget panel, EventBus eventBus);
+  public abstract void start(AcceptsOneWidget panel, EventBus eventBus);
 }
diff --git a/user/src/com/google/gwt/activity/shared/ActivityManager.java b/user/src/com/google/gwt/activity/shared/ActivityManager.java
index 7043665..0d0c7de 100644
--- a/user/src/com/google/gwt/activity/shared/ActivityManager.java
+++ b/user/src/com/google/gwt/activity/shared/ActivityManager.java
@@ -54,7 +54,7 @@
     }
   }
 
-  private static final Activity NULL_ACTIVITY = new AbstractActivity() {
+  private static final Activity NULL_ACTIVITY = new SimpleActivity() {
     public void start(AcceptsOneWidget panel, EventBus eventBus) {
     }
   };
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/user/src/com/google/gwt/activity/shared/IsActivity.java
similarity index 69%
copy from user/src/com/google/gwt/activity/shared/AbstractActivity.java
copy to user/src/com/google/gwt/activity/shared/IsActivity.java
index 59e596c..bc06054 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/user/src/com/google/gwt/activity/shared/IsActivity.java
@@ -15,19 +15,14 @@
  */
 package com.google.gwt.activity.shared;
 
+
 /**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
+ * Implemented by objects that can return an {@link Activity} aspect.
  */
-public abstract class AbstractActivity implements Activity {
-
-  public String mayStop() {
-    return null;
-  }
-
-  public void onCancel() {
-  }
-
-  public void onStop() {
-  }
+public interface IsActivity {
+  
+  /**
+   * Return the {@link Activity} aspect of this object.
+   */
+  Activity asActivity();
 }
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/user/src/com/google/gwt/activity/shared/SimpleActivity.java
similarity index 79%
rename from user/src/com/google/gwt/activity/shared/AbstractActivity.java
rename to user/src/com/google/gwt/activity/shared/SimpleActivity.java
index 59e596c..8ed2ed4 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/user/src/com/google/gwt/activity/shared/SimpleActivity.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,19 +15,28 @@
  */
 package com.google.gwt.activity.shared;
 
-/**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
- */
-public abstract class AbstractActivity implements Activity {
 
+/**
+ * Simple base implementation of {@link Activity}.
+ */
+public abstract class SimpleActivity extends Activity {
+
+  /**
+   * Return null.
+   */
   public String mayStop() {
     return null;
   }
 
+  /**
+   * No-op.
+   */
   public void onCancel() {
   }
 
+  /**
+   * No-op.
+   */
   public void onStop() {
   }
 }
diff --git a/user/src/com/google/gwt/cell/client/IconCellDecorator.java b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
index 3e0c7da..8f67fb1 100644
--- a/user/src/com/google/gwt/cell/client/IconCellDecorator.java
+++ b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
@@ -38,7 +38,7 @@
 public class IconCellDecorator<C> implements Cell<C> {
 
   interface Template extends SafeHtmlTemplates {
-    @Template("<div style=\"position:relative;padding-{0}:{1}px;\">{2}<div>{3}</div></div>")
+    @Template("<div style=\"position:relative;padding-{0}:{1}px;zoom:1;\">{2}<div>{3}</div></div>")
     SafeHtml outerDiv(String direction, int width, SafeHtml icon,
         SafeHtml cellContents);
 
diff --git a/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java b/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
index f8e555c..6cc5306 100644
--- a/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
@@ -18,16 +18,14 @@
 import static com.google.gwt.user.client.rpc.RpcRequestBuilder.STRONG_NAME_HEADER;
 
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.http.client.Request;
 import com.google.gwt.http.client.RequestBuilder;
 import com.google.gwt.http.client.RequestCallback;
 import com.google.gwt.http.client.RequestException;
 import com.google.gwt.http.client.Response;
-import com.google.gwt.requestfactory.shared.RequestEvent;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.RequestTransport;
-import com.google.gwt.requestfactory.shared.RequestEvent.State;
+import com.google.gwt.requestfactory.shared.ServerFailure;
 import com.google.gwt.user.client.Window.Location;
 
 import java.util.logging.Level;
@@ -38,6 +36,7 @@
  * {@link RequestBuilder}.
  */
 public class DefaultRequestTransport implements RequestTransport {
+  private static final String SERVER_ERROR = "Server Error";
 
   /**
    * The default URL for a DefaultRequestTransport is
@@ -52,29 +51,13 @@
    * happen every time a request is made from the server should be logged to
    * this logger.
    */
-  private static Logger wireLogger = Logger.getLogger("WireActivityLogger");
-  private static final String SERVER_ERROR = "Server Error";
+  private static final Logger wireLogger = Logger.getLogger("WireActivityLogger");
 
-  private final EventBus eventBus;
   private String requestUrl = GWT.getHostPageBaseURL() + URL;
 
   /**
-   * Construct a DefaultRequestTransport.
-   * 
-   * @param eventBus the same EventBus passed into
-   *          {@link RequestFactory#initialize(EventBus)
-   *          RequestFactory.initialize}.
-   */
-  public DefaultRequestTransport(EventBus eventBus) {
-    if (eventBus == null) {
-      throw new IllegalArgumentException("eventBus must not be null");
-    }
-    this.eventBus = eventBus;
-  }
-
-  /**
    * Returns the current URL used by this transport.
-   *
+   * 
    * @return the URL as a String
    * @see #setRequestUrl(String)
    */
@@ -92,7 +75,6 @@
     try {
       wireLogger.finest("Sending fire request");
       builder.send();
-      postRequestEvent(State.SENT, null);
     } catch (RequestException e) {
       wireLogger.log(Level.SEVERE, SERVER_ERROR + " (" + e.getMessage() + ")",
           e);
@@ -111,7 +93,7 @@
 
   /**
    * Override to change the headers sent in the HTTP request.
-   *
+   * 
    * @param builder a {@link RequestBuilder} instance
    */
   protected void configureRequestBuilder(RequestBuilder builder) {
@@ -134,7 +116,7 @@
    * Creates a RequestCallback that maps the HTTP response onto the
    * {@link com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver
    * TransportReceiver} interface.
-   *
+   * 
    * @param receiver a {@link TransportReceiver}
    * @return a {@link RequestCallback} instance
    */
@@ -143,40 +125,22 @@
     return new RequestCallback() {
 
       public void onError(Request request, Throwable exception) {
-        postRequestEvent(State.RECEIVED, null);
         wireLogger.log(Level.SEVERE, SERVER_ERROR, exception);
-        receiver.onTransportFailure(exception.getMessage());
+        receiver.onTransportFailure(new ServerFailure(exception.getMessage()));
       }
 
       public void onResponseReceived(Request request, Response response) {
         wireLogger.finest("Response received");
-        try {
-          if (200 == response.getStatusCode()) {
-            String text = response.getText();
-            receiver.onTransportSuccess(text);
-          } else if (Response.SC_UNAUTHORIZED == response.getStatusCode()) {
-            String message = "Need to log in";
-            wireLogger.finest(message);
-            receiver.onTransportFailure(message);
-          } else if (response.getStatusCode() > 0) {
-            /*
-             * During the redirection for logging in, we get a response with no
-             * status code, but it's not an error, so we only log errors with
-             * bad status codes here.
-             */
-            String message = SERVER_ERROR + " " + response.getStatusCode()
-                + " " + response.getText();
-            wireLogger.severe(message);
-            receiver.onTransportFailure(message);
-          }
-        } finally {
-          postRequestEvent(State.RECEIVED, response);
+        if (Response.SC_OK == response.getStatusCode()) {
+          String text = response.getText();
+          receiver.onTransportSuccess(text);
+        } else {
+          String message = SERVER_ERROR + " " + response.getStatusCode() + " "
+              + response.getText();
+          wireLogger.severe(message);
+          receiver.onTransportFailure(new ServerFailure(message));
         }
       }
     };
   }
-
-  private void postRequestEvent(State received, Response response) {
-    eventBus.fireEvent(new RequestEvent(received, response));
-  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
index 2e6a312..c38fba9 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
@@ -26,6 +26,6 @@
     AbstractRequestFactory {
   @Override
   public void initialize(EventBus eventBus) {
-    initialize(eventBus, new DefaultRequestTransport(eventBus));
+    initialize(eventBus, new DefaultRequestTransport());
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
index 068fa77..77b901b 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
@@ -24,6 +24,6 @@
 public class DefaultExceptionHandler implements ExceptionHandler {
   public ServerFailure createServerFailure(Throwable throwable) {
     return new ServerFailure("Server Error: "
-        + (throwable == null ? null : throwable.getMessage()), null, null);
+        + (throwable == null ? null : throwable.getMessage()), null, null, true);
   }
 }
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index f034b1c..6b64aea 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -127,6 +127,102 @@
   }
 
   /**
+   * Improves error messages by providing context for the user.
+   * <p>
+   * Visible for testing.
+   */
+  static class ErrorContext {
+    private final Logger logger;
+    private final ErrorContext parent;
+    private Type currentType;
+    private Method currentMethod;
+    private RequestFactoryInterfaceValidator validator;
+
+    public ErrorContext(Logger logger) {
+      this.logger = logger;
+      this.parent = null;
+    }
+
+    protected ErrorContext(ErrorContext parent) {
+      this.logger = parent.logger;
+      this.parent = parent;
+      this.validator = parent.validator;
+    }
+    
+    public void poison(String msg, Object... args) {
+      poison();
+      logger.logp(Level.SEVERE, currentType(), currentMethod(),
+          String.format(msg, args));
+      validator.poisoned = true;
+    }
+
+    public void poison(String msg, Throwable t) {
+      poison();
+      logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
+      validator.poisoned = true;
+    }
+
+    public ErrorContext setMethod(Method method) {
+      ErrorContext toReturn = fork();
+      toReturn.currentMethod = method;
+      return toReturn;
+    }
+
+    public ErrorContext setType(Type type) {
+      ErrorContext toReturn = fork();
+      toReturn.currentType = type;
+      return toReturn;
+    }
+    
+    public void spam(String msg, Object... args) {
+      logger.logp(Level.FINEST, currentType(), currentMethod(),
+          String.format(msg, args));
+    }
+
+    protected ErrorContext fork() {
+      return new ErrorContext(this);
+    }
+
+    void setValidator(RequestFactoryInterfaceValidator validator) {
+      assert this.validator == null : "Cannot set validator twice";
+      this.validator = validator;
+    }
+
+    private String currentMethod() {
+      if (currentMethod != null) {
+        return print(currentMethod);
+      }
+      if (parent != null) {
+        return parent.currentMethod();
+      }
+      return null;
+    }
+
+    private String currentType() {
+      if (currentType != null) {
+        return print(currentType);
+      }
+      if (parent != null) {
+        return parent.currentType();
+      }
+      return null;
+    }
+
+    /**
+     * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
+     * current context.
+     */
+    private void poison() {
+      if (currentType != null) {
+        validator.badTypes.add(currentType.getClassName());
+      }
+      if (parent != null) {
+        parent.poison();
+      }
+    }
+  }
+
+  /**
    * Used internally as a placeholder for types that cannot be mapped to a
    * domain object.
    */
@@ -206,12 +302,14 @@
           @Override
           public void visit(String name, Object value) {
             String sourceName;
-            if ("value".equals(name) || "locator".equals(name)) {
+            boolean locatorRequired = "locator".equals(name);
+            boolean valueRequired = "value".equals(name);
+            if (valueRequired || locatorRequired) {
               sourceName = (String) value;
             } else {
               return;
             }
-
+            
             /*
              * The input is a source name, so we need to convert it to an
              * internal name. We'll do this by substituting dollar signs for the
@@ -222,17 +320,25 @@
               logger.spam("Did not find " + desc.toString());
               int idx = desc.lastIndexOf("/");
               if (idx == -1) {
+                if (locatorRequired) {
+                  logger.poison("Cannot find locator named %s", value);
+                } else if (valueRequired) {
+                  logger.poison("Cannot find domain type named %s", value);
+                }
                 return;
               }
               desc.setCharAt(idx, '$');
             }
 
-            if ("locator".equals(name)) {
+            if (locatorRequired) {
               locatorInternalName = desc.toString();
-            } else {
+              logger.spam(locatorInternalName);
+            } else if (valueRequired) {
               domainInternalName = desc.toString();
+              logger.spam(domainInternalName);
+            } else {
+              throw new RuntimeException("Should not reach here");
             }
-            logger.spam(domainInternalName);
           }
         };
       }
@@ -254,89 +360,6 @@
   }
 
   /**
-   * Improves error messages by providing context for the user.
-   */
-  private class ErrorContext {
-    private final Logger logger;
-    private final ErrorContext parent;
-    private Type currentType;
-    private Method currentMethod;
-
-    public ErrorContext(Logger logger) {
-      this.logger = logger;
-      this.parent = null;
-    }
-
-    private ErrorContext(ErrorContext parent) {
-      this.logger = parent.logger;
-      this.parent = parent;
-    }
-
-    public void poison(String msg, Object... args) {
-      poison();
-      logger.logp(Level.SEVERE, currentType(), currentMethod(),
-          String.format(msg, args));
-      poisoned = true;
-    }
-
-    public void poison(String msg, Throwable t) {
-      poison();
-      logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
-      poisoned = true;
-    }
-
-    public ErrorContext setMethod(Method method) {
-      ErrorContext toReturn = new ErrorContext(this);
-      toReturn.currentMethod = method;
-      return toReturn;
-    }
-
-    public ErrorContext setType(Type type) {
-      ErrorContext toReturn = new ErrorContext(this);
-      toReturn.currentType = type;
-      return toReturn;
-    }
-
-    public void spam(String msg, Object... args) {
-      logger.logp(Level.FINEST, currentType(), currentMethod(),
-          String.format(msg, args));
-    }
-
-    private String currentMethod() {
-      if (currentMethod != null) {
-        return print(currentMethod);
-      }
-      if (parent != null) {
-        return parent.currentMethod();
-      }
-      return null;
-    }
-
-    private String currentType() {
-      if (currentType != null) {
-        return print(currentType);
-      }
-      if (parent != null) {
-        return parent.currentType();
-      }
-      return null;
-    }
-
-    /**
-     * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
-     * current context.
-     */
-    private void poison() {
-      if (currentType != null) {
-        badTypes.add(currentType.getClassName());
-      }
-      if (parent != null) {
-        parent.poison();
-      }
-    }
-  }
-
-  /**
    * Collects information about domain objects. This visitor is intended to be
    * iteratively applied to collect all methods in a type hierarchy.
    */
@@ -623,19 +646,32 @@
    * Contains vaue types (e.g. Integer).
    */
   private final Set<Type> valueTypes = new HashSet<Type>();
-
+  
   /**
    * Maps a domain object to the type returned from its getId method.
    */
   private final Map<Type, Type> unresolvedKeyTypes = new HashMap<Type, Type>();
 
-  public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
-    this.parentLogger = new ErrorContext(logger);
-    this.loader = loader;
+  {
     for (Class<?> clazz : VALUE_TYPES) {
       valueTypes.add(Type.getType(clazz));
     }
   }
+  
+  public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
+    this.parentLogger = new ErrorContext(logger);
+    parentLogger.setValidator(this);
+    this.loader = loader;
+  }
+  
+  /**
+   * Visible for testing.
+   */
+  RequestFactoryInterfaceValidator(ErrorContext errorContext, Loader loader) {
+    this.parentLogger = errorContext;
+    this.loader = loader;
+    errorContext.setValidator(this);
+  }
 
   /**
    * Reset the poisoned status of the validator so that it may be reused without
@@ -1332,7 +1368,8 @@
     return true;
   }
 
-  private boolean isCollectionType(ErrorContext logger, Type type) {
+  private boolean isCollectionType(@SuppressWarnings("unused") ErrorContext logger, Type type) {
+    // keeping the logger arg just for internal consistency for our small minds
     return "java/util/List".equals(type.getInternalName())
         || "java/util/Set".equals(type.getInternalName());
   }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index 4ad67c1..cfac4b4 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -29,24 +29,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * Handles GWT RequestFactory JSON requests. Does user authentication on every
- * request, returning SC_UNAUTHORIZED if authentication fails, as well as a
- * header named "login" which contains the URL the user should be sent in to
- * login. Note that the servlet expects a "pageurl" header in the request,
- * indicating the page to redirect to after authentication. If authentication
- * succeeds, a header named "userId" is returned, which will be unique to the
- * user (so the app can react if the signed in user has changed).
- * 
- * Configured via servlet init params.
- * <p>
- * e.g. - in order to use GAE authentication:
- * 
- * <pre>  &lt;init-param>
-    &lt;param-name>userInfoClass&lt;/param-name>
-    &lt;param-value>com.google.gwt.sample.expenses.server.domain.GaeUserInformation&lt;/param-value>
-  &lt;/init-param>
-
- * </pre>
+ * Handles GWT RequestFactory JSON requests. 
  */
 @SuppressWarnings("serial")
 public class RequestFactoryServlet extends HttpServlet {
@@ -132,24 +115,16 @@
       }
 
       try {
-        // Check that user is logged in before proceeding
-        UserInformation userInfo = UserInformation.getCurrentUserInformation(request.getHeader("pageurl"));
-        if (!userInfo.isUserLoggedIn()) {
-          response.setHeader("login", userInfo.getLoginUrl());
-          response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-        } else {
-          String payload = processor.process(jsonRequestString);
-          if (DUMP_PAYLOAD) {
-            System.out.println("<<< " + payload);
-          }
-          response.setHeader("userId", String.format("%s", userInfo.getId()));
-          response.setStatus(HttpServletResponse.SC_OK);
-          response.setContentType(RequestFactory.JSON_CONTENT_TYPE_UTF8);
-          // The Writer must be obtained after setting the content type
-          PrintWriter writer = response.getWriter();
-          writer.print(payload);
-          writer.flush();
+        String payload = processor.process(jsonRequestString);
+        if (DUMP_PAYLOAD) {
+          System.out.println("<<< " + payload);
         }
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType(RequestFactory.JSON_CONTENT_TYPE_UTF8);
+        // The Writer must be obtained after setting the content type
+        PrintWriter writer = response.getWriter();
+        writer.print(payload);
+        writer.flush();
       } catch (RuntimeException e) {
         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
         log.log(Level.SEVERE, "Unexpected error", e);
@@ -161,15 +136,6 @@
   }
 
   private void ensureConfig() {
-    // Instantiate a class for authentication, using either the default
-    // UserInfo class, or a subclass if the web.xml specifies one. This allows
-    // clients to use a Google App Engine based authentication class without
-    // adding GAE dependencies to GWT.
-    String userInfoClass = getServletConfig().getInitParameter("userInfoClass");
-    if (userInfoClass != null) {
-      UserInformation.setUserInformationImplClass(userInfoClass);
-    }
-
     String symbolMapsDirectory = getServletConfig().getInitParameter(
         "symbolMapsDirectory");
     if (symbolMapsDirectory != null) {
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index 07fe1f5..2835056 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -186,7 +186,7 @@
     processOperationMessages(state, message);
     List<Object> decoded = decodeInvocationArguments(state,
         message.getInvocations().get(0).getParameters(),
-        new Class<?>[] {proxyType}, new Type[] {domainClass});
+        new Class<?>[]{proxyType}, new Type[]{domainClass});
 
     @SuppressWarnings("unchecked")
     List<T> toReturn = (List<T>) decoded;
@@ -240,6 +240,7 @@
     msg.setExceptionType(failure.getExceptionType());
     msg.setMessage(failure.getMessage());
     msg.setStackTrace(failure.getStackTraceString());
+    msg.setFatal(failure.isFatal());
     return bean;
   }
 
@@ -272,10 +273,16 @@
       Splittable version = null;
       if (writeOperation == WriteOperation.PERSIST
           || writeOperation == WriteOperation.UPDATE) {
+        /*
+         * If we're sending an operation, the domain object must be persistent.
+         * This means that it must also have a non-null version.
+         */
         Object domainVersion = service.getVersion(domainObject);
-        if (domainVersion != null) {
-          version = returnState.flatten(domainVersion);
+        if (domainVersion == null) {
+          throw new UnexpectedException("The persisted entity with id "
+              + service.getId(domainObject) + " has a null version", null);
         }
+        version = returnState.flatten(domainVersion);
       }
 
       boolean inResponse = bean.getTag(Constants.IN_RESPONSE) != null;
@@ -345,7 +352,7 @@
    * is not static, the instance object will be in the 0th position.
    */
   private List<Object> decodeInvocationArguments(RequestState source,
-      InvocationMessage invocation, Method contextMethod, Method domainMethod) {
+      InvocationMessage invocation, Method contextMethod) {
     boolean isStatic = Request.class.isAssignableFrom(contextMethod.getReturnType());
     int baseLength = contextMethod.getParameterTypes().length;
     int length = baseLength + (isStatic ? 0 : 1);
@@ -374,7 +381,8 @@
   private List<Object> decodeInvocationArguments(RequestState source,
       List<Splittable> parameters, Class<?>[] contextArgs, Type[] genericArgs) {
     if (parameters == null) {
-      return Collections.emptyList();
+      // Can't return Collections.emptyList() because this must be mutable
+      return new ArrayList<Object>();
     }
 
     assert parameters.size() == contextArgs.length;
@@ -420,7 +428,7 @@
 
         // Compute the arguments
         List<Object> args = decodeInvocationArguments(state, invocation,
-            contextMethod, domainMethod);
+            contextMethod);
         // Possibly use a ServiceLocator
         if (service.requiresServiceLocator(contextMethod, domainMethod)) {
           Object serviceInstance = service.createServiceInstance(contextMethod,
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformation.java b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
deleted file mode 100644
index 4d3ac97..0000000
--- a/user/src/com/google/gwt/requestfactory/server/UserInformation.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.requestfactory.server;
-
-/**
- * A base class for providing authentication related information about the user.
- * Services that want real authentication should subclass this class with a
- * matching constructor, and set their class name via
- * {@link #setUserInformationImplClass(String)}.
- */
-public abstract class UserInformation {
-
-  /**
-   * Reset by {@link #getCurrentUserInformation}, which is called by
-   * {@link RequestFactoryServlet#doPost} at the start of each request, so
-   * shouldn't leak between re-used threads.
-   */
-  private static final ThreadLocal<UserInformation> currentUser = new ThreadLocal<UserInformation>();
-
-  private static String userInformationImplClass = "";
-
-  /**
-   * Instance finder method required by RequestFactory. Returns the last
-   * UserInformation established for this thread by a call to
-   * getCurrentUserInformation, or null if non has been set.
-   * 
-   * @param id ignored, required by RequestFactoryServlet
-   */
-  public static UserInformation findUserInformation(Long id) {
-    return currentUser.get();
-  }
-
-  /**
-   * Called by {@link RequestFactoryServlet#doPost} at the start of each request
-   * received. Establishes the current user information for this request, and
-   * notes a redirect url to be provided back to the client if the user's bona
-   * fides cannot be established. All succeeding calls to
-   * {@link #findUserInformation(Long)} made from the same thread will return
-   * the same UserInfo instance.
-   * <p>
-   * If {@link #setUserInformationImplClass(String)} has been called with a
-   * class name, that class is used to gather the information by calling a
-   * (String) constructor. If the impl class name is "", or if the class cannont
-   * be instantiated, dummy user info is returned.
-   * 
-   * @param redirectUrl the redirect URL as a String
-   * @return a {@link UserInformation} instance
-   */
-  public static UserInformation getCurrentUserInformation(String redirectUrl) {
-    currentUser.remove();
-    if (!"".equals(userInformationImplClass)) {
-      try {
-        currentUser.set((UserInformation) Class.forName(
-            userInformationImplClass).getConstructor(String.class).newInstance(
-            redirectUrl));
-      } catch (Exception e) {
-        e.printStackTrace();
-      }
-    }
-    if (currentUser.get() == null) {
-      currentUser.set(new UserInformationSimpleImpl(redirectUrl));
-    }
-    return currentUser.get();
-  }
-
-  /**
-   * Sets the implementation class to be used to gather user information in
-   * {@link #getCurrentUserInformation(String)}.
-   * 
-   * @param clazz a class name
-   */
-  public static void setUserInformationImplClass(String clazz) {
-    userInformationImplClass = clazz;
-  }
-
-  /**
-   * The redirect URL as a String.
-   */
-  protected String redirectUrl = "";
-  private Integer version = 0;
-
-  /**
-   * Constructs a new {@link UserInformation} instance.
-   * 
-   * @param redirectUrl the redirect URL as a String
-   */
-  public UserInformation(String redirectUrl) {
-    if (redirectUrl != null) {
-      this.redirectUrl = redirectUrl;
-    }
-  }
-
-  /**
-   * Returns the user's email address.
-   * 
-   * @return the user's email address as a String
-   */
-  public abstract String getEmail();
-
-  /**
-   * Returns the user's id.
-   * 
-   * @return the user's id as a Long
-   * @see #setId(Long)
-   */
-  public abstract Long getId();
-
-  /**
-   * Returns the user's login URL.
-   * 
-   * @return the user's login URL as a String
-   */
-  public abstract String getLoginUrl();
-
-  /**
-   * Returns the user's logout URL.
-   * 
-   * @return the user's logout URL as a String
-   */
-  public abstract String getLogoutUrl();
-
-  /**
-   * Returns the user's name.
-   * 
-   * @return the user's name as a String
-   */
-  public abstract String getName();
-
-  /**
-   * Returns the version of this instance.
-   * 
-   * @return an Integer version number
-   * @see #setVersion(Integer)
-   */
-  public Integer getVersion() {
-    return this.version;
-  }
-
-  /**
-   * Returns whether the user is logged in.
-   * 
-   * @return {@code true} if the user is logged in
-   */
-  public abstract boolean isUserLoggedIn();
-
-  /**
-   * Sets the id for this user.
-   * 
-   * @param id a String id
-   * @see #getId()
-   */
-  public abstract void setId(Long id);
-
-  /**
-   * Sets the version of this instance.
-   * 
-   * @param version an Integer version number
-   * @see #getVersion()
-   */
-  public void setVersion(Integer version) {
-    this.version = version;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java b/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java
deleted file mode 100644
index c9a66eb..0000000
--- a/user/src/com/google/gwt/requestfactory/server/UserInformationSimpleImpl.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.server;
-
-/**
- * This implementation treats the user as constantly signed in, and without any
- * information.
- */
-public class UserInformationSimpleImpl extends UserInformation {
-
-  private Long id = 0L;
-
-  /**
-   * Constructs an UserInformationSimpleImpl object.
-   * 
-   * @param redirectUrl the redirect URL as a String
-   */
-  public UserInformationSimpleImpl(String redirectUrl) {
-    super(redirectUrl);
-  }
-
-  @Override
-  public String getEmail() {
-    return "Dummy Email";
-  }
-
-  @Override
-  public Long getId() {
-    return this.id;
-  }
-
-  @Override
-  public String getLoginUrl() {
-    return "";
-  }
-
-  @Override
-  public String getLogoutUrl() {
-    return "";
-  }
-
-  @Override
-  public String getName() {
-    return "Dummy User";
-  }
-
-  @Override
-  public boolean isUserLoggedIn() {
-    return true;
-  }
-
-  @Override
-  public void setId(Long id) {
-    this.id = id;
-  }
-}
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
index a10adb2..cf713be 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.requestfactory.server.SimpleRequestProcessor;
 import com.google.gwt.requestfactory.shared.RequestTransport;
+import com.google.gwt.requestfactory.shared.ServerFailure;
 
 /**
  * A RequesTransports that calls a {@link SimpleRequestProcessor}. This test can
@@ -57,7 +58,7 @@
       receiver.onTransportSuccess(result);
     } catch (RuntimeException e) {
       e.printStackTrace();
-      receiver.onTransportFailure(e.getMessage());
+      receiver.onTransportFailure(new ServerFailure(e.getMessage()));
     }
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/Receiver.java b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
index 33fabe2..a1a4fc6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Receiver.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
@@ -18,24 +18,28 @@
 import java.util.Set;
 
 /**
- * Implemented by objects that display values.
+ * Callback object for {@link Request#fire(Receiver)} and
+ * {@link RequestContext#fire(Receiver)}.
  * 
  * @param <V> value type
  */
 public abstract class Receiver<V> {
   /**
-   * Receives general failure notifications. The default implementation throws a
-   * RuntimeException with the provided error message.
+   * Receives general failure notifications. The default implementation looks at
+   * {@link ServerFailure#isFatal()}, and throws a runtime exception with the
+   * failure object's error message if it is true.
    * 
    * @param error a {@link ServerFailure} instance
    */
   public void onFailure(ServerFailure error) {
-    throw new RuntimeException(error.getMessage());
+    if (error.isFatal()) {
+      throw new RuntimeException(error.getMessage());
+    }
   }
 
   /**
    * Called when a Request has been successfully executed on the server.
-   *
+   * 
    * @param response a response of type V
    */
   public abstract void onSuccess(V response);
@@ -44,14 +48,13 @@
    * Called if an object sent to the server could not be validated. The default
    * implementation calls {@link #onFailure(ServerFailure)} if <code>errors
    * </code> is not empty.
-   *
+   * 
    * @param errors a Set of {@link Violation} instances
    */
   public void onViolation(Set<Violation> errors) {
     if (!errors.isEmpty()) {
       onFailure(new ServerFailure(
-          "The call failed on the server due to a ConstraintViolation",
-          "", ""));
+          "The call failed on the server due to a ConstraintViolation"));
     }
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java b/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
index 3e30f4f..b3947cb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
@@ -28,22 +28,22 @@
   public interface TransportReceiver {
     /**
      * Called when the transmission succeeds.
-     *
+     * 
      * @param payload the String payload
      */
     void onTransportSuccess(String payload);
 
     /**
-     * Called when the transmission fails.
-     *
+     * Called to report a transmission failure as a ServerFailure.
+     * 
      * @param message the String error message
      */
-    void onTransportFailure(String message);
+    void onTransportFailure(ServerFailure failure);
   }
 
   /**
    * Called by the RequestFactory implementation.
-   *
+   * 
    * @param payload the String payload
    * @param receiver a {@link TransportReceiver} instance
    */
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
index d57bf53..91f2fa8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
@@ -17,36 +17,49 @@
 
 /**
  * Describes a request failure on the server.
+ * <p>
+ * This error reporting mechanism is adequate at best. When RequestFactory is
+ * extended to handle polymorphic types, this class will likely be replaced with
+ * something more expressive.
  */
 public class ServerFailure {
   private final String message;
   private final String stackTraceString;
   private final String exceptionType;
+  private final boolean fatal;
 
   /**
-   * Constructs a ServerFailure with a null message.
+   * Constructs a ServerFailure with null properties.
    */
   public ServerFailure() {
-    this(null, null, null);
+    this(null);
+  }
+
+  /**
+   * Constructs a fatal ServerFailure with null type and null stack trace.
+   */
+  public ServerFailure(String message) {
+    this(message, null, null, true);
   }
 
   /**
    * Constructs a ServerFailure object.
-   *
+   * 
    * @param message a String containing the failure message
    * @param exceptionType a String containing the exception type
    * @param stackTraceString a String containing the stack trace
    */
   public ServerFailure(String message, String exceptionType,
-      String stackTraceString) {
+      String stackTraceString, boolean fatal) {
     this.message = message;
     this.exceptionType = exceptionType;
     this.stackTraceString = stackTraceString;
+    this.fatal = fatal;
   }
 
   /**
-   * Returns the exception type.
-   *
+   * Return the exception type.
+   * 
    * @return the exception type as a String
    */
   public String getExceptionType() {
@@ -54,8 +67,8 @@
   }
 
   /**
-   * Returns the failure message.
-   *
+   * Return the failure message.
+   * 
    * @return the message as a String
    */
   public String getMessage() {
@@ -63,11 +76,21 @@
   }
 
   /**
-   * Returns the failure stack trace.
-   *
+   * Return the failure stack trace.
+   * 
    * @return the stack trace as a String
    */
   public String getStackTraceString() {
     return stackTraceString;
   }
+
+  /**
+   * Return true if this is a fatal error. The default implementation of
+   * {@link Receiver#onFailure} throws a runtime exception for fatal failures.
+   * 
+   * @return whether this is a fatal failure
+   */
+  public boolean isFatal() {
+    return fatal;
+  }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/UserInformationProxy.java b/user/src/com/google/gwt/requestfactory/shared/UserInformationProxy.java
deleted file mode 100644
index 0dd84a0..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/UserInformationProxy.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.requestfactory.shared;
-
-import com.google.gwt.requestfactory.server.UserInformation;
-
-/**
- * "API Generated" DTO interface based on {@link UserInformation}.
- */
-@ProxyFor(UserInformation.class)
-public interface UserInformationProxy extends EntityProxy  {
-  /**
-   * Returns the user's email address.
-   *
-   * @return the user's email address as a String
-   */
-  String getEmail();
-  
-  /**
-   * Returns the user's login url.
-   *
-   * @return the user's login url as a String
-   */
-  String getLoginUrl();
-  
-  /**
-   * Returns the user's logout url.
-   *
-   * @return the user's logout url as a String
-   */
-  String getLogoutUrl();
-  
-  /**
-   * Returns the user's name.
-   *
-   * @return the user's name as a String
-   */
-  String getName();
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/UserInformationRequest.java b/user/src/com/google/gwt/requestfactory/shared/UserInformationRequest.java
deleted file mode 100644
index 463cb36..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/UserInformationRequest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.requestfactory.shared;
-
-import com.google.gwt.requestfactory.server.UserInformation;
-
-/**
- * "API Generated" request selector interface implemented by objects that give
- * client access to the methods of {@link UserInformation}.
- */
-@Service(UserInformation.class)
-public interface UserInformationRequest extends RequestContext {
-
-  /**
-   * Returns the current user information given a redirect URL.
-   *
-   * @param redirectUrl the redirect UR as a String
-   * @return an instance of {@link Request}&lt;{@link UserInformationProxy}&gt;
-   */
-  Request<UserInformationProxy> getCurrentUserInformation(String redirectUrl);
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 839f4ee..3027693 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -114,7 +114,10 @@
   }
 
   private static final String PARENT_OBJECT = "parentObject";
-
+  private static final WriteOperation[] PERSIST_AND_UPDATE = {
+      WriteOperation.PERSIST, WriteOperation.UPDATE};
+  private static final WriteOperation[] DELETE_ONLY = {WriteOperation.DELETE};
+  private static final WriteOperation[] UPDATE_ONLY = {WriteOperation.UPDATE};
   private final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
   private boolean locked;
   private final AbstractRequestFactory requestFactory;
@@ -148,8 +151,8 @@
   public <T extends BaseProxy> T create(Class<T> clazz) {
     checkLocked();
 
-    AutoBean<T> created = requestFactory.createProxy(clazz,
-        requestFactory.allocateId(clazz));
+    SimpleProxyId<T> id = requestFactory.allocateId(clazz);
+    AutoBean<T> created = requestFactory.createProxy(clazz, id);
     return takeOwnership(created);
   }
 
@@ -601,16 +604,8 @@
 
     String payload = makePayload();
     requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
-      public void onTransportFailure(String message) {
-        ServerFailure failure = new ServerFailure(message, null, null);
-        reuse();
-        for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(
-            invocations)) {
-          request.onFail(failure);
-        }
-        if (receiver != null) {
-          receiver.onFailure(failure);
-        }
+      public void onTransportFailure(ServerFailure failure) {
+        fail(receiver, failure);
       }
 
       public void onTransportSuccess(String payload) {
@@ -619,16 +614,10 @@
         if (response.getGeneralFailure() != null) {
           ServerFailureMessage failure = response.getGeneralFailure();
           ServerFailure fail = new ServerFailure(failure.getMessage(),
-              failure.getExceptionType(), failure.getStackTrace());
+              failure.getExceptionType(), failure.getStackTrace(),
+              failure.isFatal());
 
-          reuse();
-          for (AbstractRequest<?> invocation : new ArrayList<AbstractRequest<?>>(
-              invocations)) {
-            invocation.onFail(fail);
-          }
-          if (receiver != null) {
-            receiver.onFailure(fail);
-          }
+          fail(receiver, fail);
           return;
         }
 
@@ -663,7 +652,8 @@
                 response.getInvocationResults().get(i)).as();
             invocations.get(i).onFail(
                 new ServerFailure(failure.getMessage(),
-                    failure.getExceptionType(), failure.getStackTrace()));
+                    failure.getExceptionType(), failure.getStackTrace(),
+                    failure.isFatal()));
           }
         }
 
@@ -675,6 +665,17 @@
         invocations.clear();
         returnedProxies.clear();
       }
+
+      private void fail(Receiver<Void> receiver, ServerFailure failure) {
+        reuse();
+        for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(
+            invocations)) {
+          request.onFail(failure);
+        }
+        if (receiver != null) {
+          receiver.onFailure(failure);
+        }
+      }
     });
   }
 
@@ -780,23 +781,36 @@
    * Process an array of OperationMessages.
    */
   private void processReturnOperations(ResponseMessage response) {
-    List<OperationMessage> records = response.getOperations();
+    List<OperationMessage> ops = response.getOperations();
 
     // If there are no observable effects, this will be null
-    if (records == null) {
+    if (ops == null) {
       return;
     }
 
-    for (OperationMessage op : records) {
+    for (OperationMessage op : ops) {
       SimpleProxyId<?> id = getId(op);
-      if (id.isEphemeral()) {
-        processReturnOperation(id, op);
-      } else if (id.wasEphemeral()) {
-        processReturnOperation(id, op, WriteOperation.PERSIST,
-            WriteOperation.UPDATE);
-      } else {
-        processReturnOperation(id, op, WriteOperation.UPDATE);
+      WriteOperation[] toPropagate = null;
+
+      // May be null if the server is returning an unpersisted object
+      WriteOperation effect = op.getOperation();
+      if (effect != null) {
+        switch (effect) {
+          case DELETE:
+            toPropagate = DELETE_ONLY;
+            break;
+          case PERSIST:
+            toPropagate = PERSIST_AND_UPDATE;
+            break;
+          case UPDATE:
+            toPropagate = UPDATE_ONLY;
+            break;
+          default:
+            // Should never reach here
+            throw new RuntimeException(effect.toString());
+        }
       }
+      processReturnOperation(id, op, toPropagate);
     }
   }
 
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
index b5ffeda..9616776 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -82,7 +82,7 @@
       protected RequestData makeRequestData() {
         return new RequestData(
             "com.google.gwt.requestfactory.shared.impl.FindRequest::find",
-            new Object[] {proxyId}, propertyRefs, proxyId.getProxyClass(), null);
+            new Object[]{proxyId}, propertyRefs, proxyId.getProxyClass(), null);
       }
     };
   }
@@ -142,6 +142,8 @@
    */
   protected boolean hasVersionChanged(SimpleProxyId<?> id,
       String observedVersion) {
+    assert id != null : "id";
+    assert observedVersion != null : "observedVersion";
     String key = getHistoryToken(id);
     String existingVersion = version.get(key);
     // Return true if we haven't seen this before or the versions differ
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
index f196adf..27a5007 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
@@ -24,6 +24,7 @@
   String EXCEPTION_TYPE = "X";
   String MESSAGE = "M";
   String STACK_TRACE = "S";
+  String FATAL = "F";
 
   @PropertyName(EXCEPTION_TYPE)
   String getExceptionType();
@@ -33,13 +34,19 @@
 
   @PropertyName(STACK_TRACE)
   String getStackTrace();
+  
+  @PropertyName(FATAL)
+  boolean isFatal();
 
   @PropertyName(EXCEPTION_TYPE)
   void setExceptionType(String exceptionType);
 
+  @PropertyName(FATAL)
+  void setFatal(boolean significant);
+
   @PropertyName(MESSAGE)
   void setMessage(String message);
-
+  
   @PropertyName(STACK_TRACE)
   void setStackTrace(String stackTrace);
 }
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/AuthenticationFailureHandler.java b/user/src/com/google/gwt/requestfactory/ui/client/AuthenticationFailureHandler.java
deleted file mode 100644
index beb0f41..0000000
--- a/user/src/com/google/gwt/requestfactory/ui/client/AuthenticationFailureHandler.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.requestfactory.ui.client;
-
-import com.google.gwt.http.client.Response;
-import com.google.gwt.requestfactory.shared.RequestEvent;
-import com.google.gwt.requestfactory.shared.RequestEvent.State;
-import com.google.gwt.user.client.Window.Location;
-
-/**
- * A request event handler which listens to every request and reacts if there
- * is an authentication problem. Note that the server side code is responsible
- * for making sure that no sensitive information is returned in case of
- * authentication issues. This handler is just responsible for making such
- * failures user friendly.
- */
-public class AuthenticationFailureHandler implements RequestEvent.Handler {
-  private String lastSeenUser = null;
-  
-  public void onRequestEvent(RequestEvent requestEvent) {
-    if (requestEvent.getState() == State.RECEIVED) {
-      Response response = requestEvent.getResponse();
-      if (response == null) {
-        // We should only get to this state if the RPC failed, in which
-        // case we went through the RequestCallback.onError() code path
-        // already and we don't need to do any additional error handling
-        // here, but we don't want to throw further exceptions.
-        return;
-      }
-      if (Response.SC_UNAUTHORIZED == response.getStatusCode()) {
-        String loginUrl = response.getHeader("login");
-        Location.replace(loginUrl);
-      }
-      String newUser = response.getHeader("userId");
-      if (lastSeenUser == null) {
-        lastSeenUser = newUser;
-      } else if (!lastSeenUser.equals(newUser)) {
-        // A new user has logged in, just reload the app and start over
-        Location.reload();
-      }
-    }
-  }
-
-}
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/LoginWidget.java b/user/src/com/google/gwt/requestfactory/ui/client/LoginWidget.java
deleted file mode 100644
index 0362693..0000000
--- a/user/src/com/google/gwt/requestfactory/ui/client/LoginWidget.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.google.gwt.requestfactory.ui.client;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.SpanElement;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.requestfactory.shared.UserInformationProxy;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.Window.Location;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.Widget;
-
-/**
- * A simple widget which displays info about the user and a logout link.
- */
-public class LoginWidget extends Composite {
-  interface Binder extends UiBinder<Widget, LoginWidget> { }
-  private static final Binder BINDER = GWT.create(Binder.class);
-
-  @UiField SpanElement name;
-  String logoutUrl = "";
-  
-  public LoginWidget() {
-    initWidget(BINDER.createAndBindUi(this));
-  }
-  
-  /**
-   * Sets the user information using a {@link UserInformationProxy}.
-   *
-   * @param info a {@link UserInformationProxy} instance
-   */
-  public void setUserInformation(UserInformationProxy info) {
-    name.setInnerText(info.getName());
-    logoutUrl = info.getLogoutUrl();
-  }
-  
-  @UiHandler("logoutLink")
-  void handleClick(@SuppressWarnings("unused") ClickEvent e) {
-    if (logoutUrl != "") {
-      Location.replace(logoutUrl);
-    }
-  }
-}
diff --git a/user/src/com/google/gwt/user/cellview/CellView.gwt.xml b/user/src/com/google/gwt/user/cellview/CellView.gwt.xml
index d848672..00dacb0 100644
--- a/user/src/com/google/gwt/user/cellview/CellView.gwt.xml
+++ b/user/src/com/google/gwt/user/cellview/CellView.gwt.xml
@@ -54,12 +54,4 @@
       <when-property-is name="user.agent" value="ie8"/>
     </any>
   </replace-with>
-
-  <!-- IE6-specific CellTree implementation. -->
-  <replace-with class="com.google.gwt.user.cellview.client.CellTree.ImplIE6">
-    <when-type-is class="com.google.gwt.user.cellview.client.CellTree.Impl"/>
-    <any>
-      <when-property-is name="user.agent" value="ie6"/>
-    </any>
-  </replace-with>
 </module>
diff --git a/user/src/com/google/gwt/user/cellview/client/CellBrowser.css b/user/src/com/google/gwt/user/cellview/client/CellBrowser.css
index 8739e95..727930b 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellBrowser.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellBrowser.css
@@ -27,10 +27,12 @@
 
 .cellBrowserEvenItem {
   padding: 8px;
+  zoom: 1;
 }
 
 .cellBrowserOddItem {
   padding: 8px;
+  zoom: 1;
 }
 
 .cellBrowserKeyboardSelectedItem {
diff --git a/user/src/com/google/gwt/user/cellview/client/CellList.css b/user/src/com/google/gwt/user/cellview/client/CellList.css
index cd7546e..48364d5 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellList.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellList.css
@@ -19,10 +19,12 @@
 
 .cellListEvenItem {
   cursor: pointer;
+  zoom: 1;
 }
 
 .cellListOddItem {
   cursor: pointer;
+  zoom: 1;
 }
 
 .cellListKeyboardSelectedItem {
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.css b/user/src/com/google/gwt/user/cellview/client/CellTree.css
index 23c83d1..8d521c3 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.css
@@ -27,6 +27,7 @@
   padding-bottom: 4px;
   cursor: hand;
   cursor: pointer;
+  zoom: 1;
 }
 
 .cellTreeItemImage {
@@ -34,7 +35,7 @@
 }
 
 .cellTreeItemImageValue {
-  
+  zoom: 1;
 }
 
 .cellTreeItemValue {
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTree.java b/user/src/com/google/gwt/user/cellview/client/CellTree.java
index 9460785..a3a7de6 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTree.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTree.java
@@ -443,11 +443,6 @@
         + "width:{2}px;height:{3}px;\">{4}</div>")
     SafeHtml imageWrapper(String classes, String direction, int width,
         int height, SafeHtml image);
-
-    @Template("<div class=\"{0}\" style=\"position:absolute;{1}:-{2}px;"
-        + "width:{2}px;height:{3}px;\">{4}</div>")
-    SafeHtml imageWrapperIE6(String classes, String direction, int width,
-        int height, SafeHtml image);
   }
 
   /**
@@ -464,30 +459,6 @@
   }
 
   /**
-   * Implementation of {@link CellTable} used by IE6.
-   */
-  @SuppressWarnings("unused")
-  private static class ImplIE6 extends Impl {
-    @Override
-    public SafeHtml imageWrapper(String classes, String direction, int width,
-        int height, SafeHtml image) {
-      /*
-       * In IE6, left/right positions are relative to the inside of the padding
-       * instead of the outside of the padding. The bug does not happen on IE7,
-       * which maps to the IE6 user agent, so we need a runtime check for IE6.
-       */
-      if (isIe6()) {
-        return template.imageWrapperIE6(classes, direction, width, height, image);
-      }
-      return super.imageWrapper(classes, direction, width, height, image);
-    }
-
-    private native boolean isIe6() /*-{
-      return @com.google.gwt.dom.client.DOMImplIE6::isIE6()();
-    }-*/;
-  }
-
-  /**
    * The default number of children to show under a tree node.
    */
   private static final int DEFAULT_LIST_SIZE = 25;
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css b/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css
index f008a26..6ff00d7 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css
+++ b/user/src/com/google/gwt/user/cellview/client/CellTreeBasic.css
@@ -27,6 +27,7 @@
   padding-bottom: 4px;
   cursor: hand;
   cursor: pointer;
+  zoom: 1;
 }
 
 .cellTreeItemImage {
@@ -34,7 +35,7 @@
 }
 
 .cellTreeItemImageValue {
-  
+  zoom: 1;
 }
 
 .cellTreeItemValue {
diff --git a/user/test/com/google/gwt/activity/shared/ActivityManagerTest.java b/user/test/com/google/gwt/activity/shared/ActivityManagerTest.java
index 0f25f8b..16de717 100644
--- a/user/test/com/google/gwt/activity/shared/ActivityManagerTest.java
+++ b/user/test/com/google/gwt/activity/shared/ActivityManagerTest.java
@@ -81,7 +81,7 @@
       return null;
     }
   }
-  private static class SyncActivity implements Activity {
+  private static class SyncActivity extends Activity {
     boolean canceled = false;
     boolean stopped = false;
     AcceptsOneWidget display;
diff --git a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
index 054b751..438bdf0 100644
--- a/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
+++ b/user/test/com/google/gwt/cell/client/IconCellDecoratorTest.java
@@ -67,7 +67,7 @@
     cell.render(context, "helloworld", sb);
 
     // Compare the expected render string.
-    String expected = "<div style=\"position:relative;padding-left:64px;\">";
+    String expected = "<div style=\"position:relative;padding-left:64px;zoom:1;\">";
     expected += cell.getImageHtml(images.prettyPiccy(),
         HasVerticalAlignment.ALIGN_MIDDLE, true).asString();
     expected += "<div>helloworld</div>";
@@ -123,7 +123,7 @@
   @Override
   protected String getExpectedInnerHtml() {
     IconCellDecorator<String> cell = createCell();
-    String html = "<div style=\"position:relative;padding-left:64px;\">";
+    String html = "<div style=\"position:relative;padding-left:64px;zoom:1;\">";
     html += cell.getIconHtml("helloworld").asString();
     html += "<div>helloworld</div>";
     html += "</div>";
@@ -133,7 +133,7 @@
   @Override
   protected String getExpectedInnerHtmlNull() {
     IconCellDecorator<String> cell = createCell();
-    String html = "<div style=\"position:relative;padding-left:64px;\">";
+    String html = "<div style=\"position:relative;padding-left:64px;zoom:1;\">";
     html += cell.getIconHtml("helloworld").asString();
     html += "<div></div>";
     html += "</div>";
diff --git a/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java b/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java
index 6285434..1967839 100644
--- a/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/FindServiceTest.java
@@ -23,6 +23,7 @@
 import com.google.gwt.requestfactory.shared.SimpleBarProxy;
 import com.google.gwt.requestfactory.shared.SimpleBarRequest;
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
+import com.google.gwt.requestfactory.shared.SimpleFooRequest;
 import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
 
 /**
@@ -138,6 +139,37 @@
         });
   }
 
+  public void testFetchsAfterCreateDontUpdate() {
+    final int[] count = {0};
+    final HandlerRegistration registration = EntityProxyChange.registerForProxyType(
+        req.getEventBus(), SimpleFooProxy.class,
+        new EntityProxyChange.Handler<SimpleFooProxy>() {
+          public void onProxyChange(EntityProxyChange<SimpleFooProxy> event) {
+            count[0]++;
+          }
+        });
+    delayTestFinish(TEST_DELAY);
+    SimpleFooRequest context = req.simpleFooRequest();
+    SimpleFooProxy proxy = context.create(SimpleFooProxy.class);
+    context.persistAndReturnSelf().using(proxy).fire(
+        new Receiver<SimpleFooProxy>() {
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            // Persist and Update events
+            assertEquals(2, count[0]);
+            req.find(response.stableId()).fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy response) {
+                // No new events
+                assertEquals(2, count[0]);
+                registration.removeHandler();
+                finishTestAndReset();
+              }
+            });
+          }
+        });
+  }
+
   /**
    * Demonstrates behavior when fetching an unpersisted id. The setup is
    * analagous to saving a future id into a cookie and then trying to fetch it
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 5d0cb95..1763de6 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -28,7 +28,6 @@
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooRequest;
 import com.google.gwt.requestfactory.shared.SimpleValueProxy;
-import com.google.gwt.requestfactory.shared.UserInformationProxy;
 import com.google.gwt.requestfactory.shared.Violation;
 import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
 
@@ -198,69 +197,6 @@
     }
   }
 
-  /**
-   * Test that removing a parent entity and implicitly removing the child sends
-   * an event to the client that the child was removed.
-   * 
-   * TODO(rjrjr): Should cascading deletes be detected?
-   */
-  public void disableTestMethodWithSideEffectDeleteChild() {
-    delayTestFinish(DELAY_TEST_FINISH);
-
-    // Persist bar.
-    SimpleBarRequest context = req.simpleBarRequest();
-    final SimpleBarProxy bar = context.create(SimpleBarProxy.class);
-    context.persistAndReturnSelf().using(bar).fire(
-        new Receiver<SimpleBarProxy>() {
-          @Override
-          public void onSuccess(SimpleBarProxy persistentBar) {
-            persistentBar = checkSerialization(persistentBar);
-            // Persist foo with bar as a child.
-            SimpleFooRequest context = req.simpleFooRequest();
-            SimpleFooProxy foo = context.create(SimpleFooProxy.class);
-            final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
-                foo);
-            foo = context.edit(foo);
-            foo.setUserName("John");
-            foo.setBarField(bar);
-            persistRequest.fire(new Receiver<SimpleFooProxy>() {
-              @Override
-              public void onSuccess(SimpleFooProxy persistentFoo) {
-                persistentFoo = checkSerialization(persistentFoo);
-                // Handle changes to SimpleFooProxy.
-                final SimpleFooEventHandler<SimpleFooProxy> fooHandler = new SimpleFooEventHandler<SimpleFooProxy>();
-                EntityProxyChange.registerForProxyType(req.getEventBus(),
-                    SimpleFooProxy.class, fooHandler);
-
-                // Handle changes to SimpleBarProxy.
-                final SimpleFooEventHandler<SimpleBarProxy> barHandler = new SimpleFooEventHandler<SimpleBarProxy>();
-                EntityProxyChange.registerForProxyType(req.getEventBus(),
-                    SimpleBarProxy.class, barHandler);
-
-                // Delete bar.
-                SimpleFooRequest context = req.simpleFooRequest();
-                final Request<Void> deleteRequest = context.deleteBar().using(
-                    persistentFoo);
-                SimpleFooProxy editable = context.edit(persistentFoo);
-                editable.setBarField(bar);
-                deleteRequest.fire(new Receiver<Void>() {
-                  @Override
-                  public void onSuccess(Void response) {
-                    assertEquals(1, fooHandler.updateEventCount); // set bar to
-                    // null
-                    assertEquals(1, fooHandler.totalEventCount);
-
-                    assertEquals(1, barHandler.deleteEventCount); // deleted bar
-                    assertEquals(1, barHandler.totalEventCount);
-                    finishTestAndReset();
-                  }
-                });
-              }
-            });
-          }
-        });
-  }
-
   @Override
   public String getModuleName() {
     return "com.google.gwt.requestfactory.RequestFactorySuite";
@@ -778,6 +714,17 @@
     });
   }
 
+  public void testInstanceServiceRequestByName() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    req.instanceServiceRequestByName().add(5).fire(new Receiver<Integer>() {
+      @Override
+      public void onSuccess(Integer response) {
+        assertEquals(10, (int) response);
+        finishTestAndReset();
+      }
+    });
+  }
+
   /**
    * Make sure our stock RF logging service keeps receiving.
    */
@@ -800,6 +747,68 @@
     });
   }
 
+  /**
+   * Test that removing a parent entity and implicitly removing the child sends
+   * an event to the client that the child was removed.
+   */
+  public void testMethodWithSideEffectDeleteChild() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    // Handle changes to SimpleFooProxy.
+    final SimpleFooEventHandler<SimpleFooProxy> fooHandler = new SimpleFooEventHandler<SimpleFooProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleFooProxy.class, fooHandler);
+
+    // Handle changes to SimpleBarProxy.
+    final SimpleFooEventHandler<SimpleBarProxy> barHandler = new SimpleFooEventHandler<SimpleBarProxy>();
+    EntityProxyChange.registerForProxyType(req.getEventBus(),
+        SimpleBarProxy.class, barHandler);
+
+    // Persist bar.
+    SimpleBarRequest context = req.simpleBarRequest();
+    final SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+    context.persistAndReturnSelf().using(bar).fire(
+        new Receiver<SimpleBarProxy>() {
+          @Override
+          public void onSuccess(SimpleBarProxy persistentBar) {
+            persistentBar = checkSerialization(persistentBar);
+            // Persist foo with bar as a child.
+            SimpleFooRequest context = req.simpleFooRequest();
+            SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+            final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+                foo).with("barField");
+            foo = context.edit(foo);
+            foo.setUserName("John");
+            foo.setBarField(bar);
+            persistRequest.fire(new Receiver<SimpleFooProxy>() {
+              @Override
+              public void onSuccess(SimpleFooProxy persistentFoo) {
+                persistentFoo = checkSerialization(persistentFoo);
+
+                // Delete bar.
+                SimpleFooRequest context = req.simpleFooRequest();
+                final Request<Void> deleteRequest = context.deleteBar().using(
+                    persistentFoo);
+                deleteRequest.fire(new Receiver<Void>() {
+                  @Override
+                  public void onSuccess(Void response) {
+                    assertEquals(1, fooHandler.persistEventCount);
+                    assertEquals(2, fooHandler.updateEventCount);
+                    assertEquals(3, fooHandler.totalEventCount);
+
+                    assertEquals(1, barHandler.persistEventCount);
+                    assertEquals(1, barHandler.updateEventCount);
+                    assertEquals(1, barHandler.deleteEventCount);
+                    assertEquals(3, barHandler.totalEventCount);
+                    finishTestAndReset();
+                  }
+                });
+              }
+            });
+          }
+        });
+  }
+
   /*
    * tests that (a) any method can have a side effect that is handled correctly.
    * (b) instance methods are handled correctly and (c) a request cannot be
@@ -998,7 +1007,7 @@
    */
   public void testNullValueInIntegerListRequest() {
     delayTestFinish(DELAY_TEST_FINISH);
-    List<Integer> list = Arrays.asList(new Integer[] {1, 2, null});
+    List<Integer> list = Arrays.asList(new Integer[]{1, 2, null});
     final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInIntegerList(
         list);
     fooReq.fire(new Receiver<Void>() {
@@ -1014,7 +1023,7 @@
    */
   public void testNullValueInStringListRequest() {
     delayTestFinish(DELAY_TEST_FINISH);
-    List<String> list = Arrays.asList(new String[] {"nonnull", "null", null});
+    List<String> list = Arrays.asList(new String[]{"nonnull", "null", null});
     final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInStringList(
         list);
     fooReq.fire(new Receiver<Void>() {
@@ -1060,6 +1069,27 @@
     });
   }
 
+  /**
+   * Test that the server code will not allow a persisted entity to be returned
+   * if it has a null version property.
+   */
+  public void testPersistedEntityWithNullVersion() {
+    delayTestFinish(DELAY_TEST_FINISH);
+    simpleFooRequest().getSimpleFooWithNullVersion().fire(
+        new Receiver<SimpleFooProxy>() {
+
+          @Override
+          public void onFailure(ServerFailure error) {
+            finishTestAndReset();
+          }
+
+          @Override
+          public void onSuccess(SimpleFooProxy response) {
+            fail();
+          }
+        });
+  }
+
   public void testPersistExistingEntityExistingRelation() {
     delayTestFinish(DELAY_TEST_FINISH);
 
@@ -1791,21 +1821,6 @@
     });
   }
 
-  public void testPrimitiveListBooleanAsParameter() {
-    delayTestFinish(DELAY_TEST_FINISH);
-
-    Request<Boolean> procReq = simpleFooRequest().processBooleanList(
-        Arrays.asList(true, false));
-
-    procReq.fire(new Receiver<Boolean>() {
-      @Override
-      public void onSuccess(Boolean response) {
-        assertEquals(true, (boolean) response);
-        finishTestAndReset();
-      }
-    });
-  }
-
   public void testPrimitiveListBigDecimalAsParameter() {
     delayTestFinish(DELAY_TEST_FINISH);
 
@@ -1846,6 +1861,21 @@
         });
   }
 
+  public void testPrimitiveListBooleanAsParameter() {
+    delayTestFinish(DELAY_TEST_FINISH);
+
+    Request<Boolean> procReq = simpleFooRequest().processBooleanList(
+        Arrays.asList(true, false));
+
+    procReq.fire(new Receiver<Boolean>() {
+      @Override
+      public void onSuccess(Boolean response) {
+        assertEquals(true, (boolean) response);
+        finishTestAndReset();
+      }
+    });
+  }
+
   @SuppressWarnings("deprecation")
   public void testPrimitiveListDateAsParameter() {
     delayTestFinish(DELAY_TEST_FINISH);
@@ -2280,28 +2310,6 @@
         });
   }
 
-  /**
-   * We provide a simple UserInformation class to give GAE developers a hand,
-   * and other developers a hint. Make sure RF doesn't break it (it relies on
-   * server side upcasting, and a somewhat sleazey reflective lookup mechanism
-   * in a static method on UserInformation).
-   */
-  public void testUserInfo() {
-    delayTestFinish(DELAY_TEST_FINISH);
-    req.userInformationRequest().getCurrentUserInformation("").fire(
-        new Receiver<UserInformationProxy>() {
-          @Override
-          public void onSuccess(UserInformationProxy response) {
-            response = checkSerialization(response);
-            assertEquals("Dummy Email", response.getEmail());
-            assertEquals("Dummy User", response.getName());
-            assertEquals("", response.getLoginUrl());
-            assertEquals("", response.getLogoutUrl());
-            finishTestAndReset();
-          }
-        });
-  }
-
   public void testValueObjectCreateSetRetrieveUpdate() {
     delayTestFinish(DELAY_TEST_FINISH);
     SimpleFooRequest req = simpleFooRequest();
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryUnicodeEscapingTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryUnicodeEscapingTest.java
index 44de4ed..afdc2a3 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryUnicodeEscapingTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryUnicodeEscapingTest.java
@@ -89,7 +89,7 @@
             verifyStringContainingCharacterRange(current,
                 Math.min(end, current + size), response);
           } catch (InvalidCharacterException e) {
-            fails.add(new ServerFailure(e.getMessage(), null, null));
+            fails.add(new ServerFailure(e.getMessage()));
           }
           nextBatch();
         }
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
index a5d3c1a..1bdcc6d 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
@@ -26,7 +26,7 @@
     super(new ExceptionHandler() {
       public ServerFailure createServerFailure(Throwable throwable) {
         return new ServerFailure(throwable.getMessage(),
-            throwable.getClass().getName(), "my stack trace");
+            throwable.getClass().getName(), "my stack trace", true);
       }
     });
   }
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 1c6c003..e6a4e10 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -32,6 +32,7 @@
 
 import junit.framework.TestCase;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 import java.util.logging.Level;
@@ -195,6 +196,14 @@
   interface LocatorEntityProxy extends EntityProxy {
   }
 
+  @ProxyForName(value = "com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntity", locator = "badLocator")
+  interface LocatorEntityProxyWithBadLocator extends EntityProxy {
+  }
+
+  @ProxyForName(value = "badDomainType", locator = "com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntityProxyWithBadServiceName")
+  interface LocatorEntityProxyWithBadServiceName extends EntityProxy {
+  }
+
   @ProxyFor(value = Value.class)
   interface MyValueProxy extends ValueProxy {
   }
@@ -262,10 +271,44 @@
   static class Value {
   }
 
-  RequestFactoryInterfaceValidator v;
+  static class VisibleErrorContext extends
+      RequestFactoryInterfaceValidator.ErrorContext {
+    final List<String> logs;
+
+    public VisibleErrorContext(Logger logger) {
+      super(logger);
+      logs = new ArrayList<String>();
+    }
+
+    public VisibleErrorContext(VisibleErrorContext that) {
+      super(that);
+      this.logs = that.logs;
+    }
+
+    @Override
+    public void poison(String msg, Object... args) {
+      logs.add(String.format(msg, args));
+      super.poison(msg, args);
+    }
+
+    @Override
+    public void poison(String msg, Throwable t) {
+      logs.add(msg);
+      super.poison(msg, t);
+    }
+
+    @Override
+    protected VisibleErrorContext fork() {
+      return new VisibleErrorContext(this);
+    }
+  }
+
+  RequestFactoryInterfaceValidator v;;
 
   private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");;
 
+  private VisibleErrorContext errors;
+
   /**
    * Ensure that calling {@link RequestFactoryInterfaceValidator#antidote()}
    * doesn't cause information to be lost.
@@ -277,7 +320,19 @@
     assertFalse(v.isPoisoned());
     v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
     assertTrue(v.isPoisoned());
-  };
+  }
+
+  public void testBadLocatorName() {
+    v.validateEntityProxy(LocatorEntityProxyWithBadLocator.class.getName());
+    assertTrue(v.isPoisoned());
+    assertTrue(errors.logs.contains("Cannot find locator named badLocator"));
+  }
+
+  public void testBadServiceName() {
+    v.validateEntityProxy(LocatorEntityProxyWithBadServiceName.class.getName());
+    assertTrue(v.isPoisoned());
+    assertTrue(errors.logs.contains("Cannot find domain type named badDomainType"));
+  }
 
   /**
    * Test that subclasses of {@code java.util.Date} are not transportable.
@@ -393,7 +448,8 @@
   protected void setUp() throws Exception {
     Logger logger = Logger.getLogger("");
     logger.setLevel(DUMP_PAYLOAD ? Level.ALL : Level.OFF);
-    v = new RequestFactoryInterfaceValidator(logger, new ClassLoaderLoader(
+    errors = new VisibleErrorContext(logger);
+    v = new RequestFactoryInterfaceValidator(errors, new ClassLoaderLoader(
         Thread.currentThread().getContextClassLoader()));
   }
 }
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 1ac5987..afcd18e 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -124,6 +124,16 @@
     return list;
   }
 
+  /**
+   * This tests that the server detects and disallows the use of persisted
+   * objects with a null version property.
+   */
+  public static SimpleFoo getSimpleFooWithNullVersion() {
+    SimpleFoo foo = new SimpleFoo();
+    foo.setVersion(null);
+    return foo;
+  }
+
   public static SimpleFoo getSimpleFooWithSubPropertyCollection() {
     SimpleFoo foo = new SimpleFoo();
     SimpleFoo subFoo = new SimpleFoo();
@@ -447,9 +457,11 @@
 
   public void deleteBar() {
     if (barField != null) {
+      isChanged = true;
       barField.delete();
     }
     barField = null;
+    persist();
   }
 
   public SimpleBar getBarField() {
diff --git a/user/test/com/google/gwt/requestfactory/shared/BasicRequestFactory.java b/user/test/com/google/gwt/requestfactory/shared/BasicRequestFactory.java
index 8b02b3f..8b50964 100644
--- a/user/test/com/google/gwt/requestfactory/shared/BasicRequestFactory.java
+++ b/user/test/com/google/gwt/requestfactory/shared/BasicRequestFactory.java
@@ -20,8 +20,5 @@
  * RequestFactory interfaces works correctly.
  */
 public interface BasicRequestFactory extends RequestFactory {
-
   LoggingRequest loggingRequest();
-
-  UserInformationRequest userInformationRequest();
 }
diff --git a/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java b/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java
index 60af61d..701a276 100644
--- a/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java
@@ -67,8 +67,8 @@
       return key;
     }
 
-    public Void getVersion() {
-      return null;
+    public Integer getVersion() {
+      return 0;
     }
   }
 
@@ -101,8 +101,8 @@
       return key;
     }
 
-    public Void getVersion() {
-      return null;
+    public Integer getVersion() {
+      return 0;
     }
   }
 
@@ -129,8 +129,8 @@
       return key;
     }
 
-    public Void getVersion() {
-      return null;
+    public Integer getVersion() {
+      return 0;
     }
   }
 
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/user/test/com/google/gwt/requestfactory/shared/InstanceServiceRequestByName.java
similarity index 61%
copy from user/src/com/google/gwt/activity/shared/AbstractActivity.java
copy to user/test/com/google/gwt/requestfactory/shared/InstanceServiceRequestByName.java
index 59e596c..0934a97 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/user/test/com/google/gwt/requestfactory/shared/InstanceServiceRequestByName.java
@@ -1,33 +1,25 @@
 /*
  * 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.activity.shared;
+package com.google.gwt.requestfactory.shared;
 
 /**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
+ * Used to test the ServiceLocator extension hook.
  */
-public abstract class AbstractActivity implements Activity {
-
-  public String mayStop() {
-    return null;
-  }
-
-  public void onCancel() {
-  }
-
-  public void onStop() {
-  }
+@ServiceName(value = "com.google.gwt.requestfactory.server.InstanceService", 
+    locator = "com.google.gwt.requestfactory.server.InstanceServiceLocator")
+public interface InstanceServiceRequestByName extends RequestContext {
+  Request<Integer> add(int value);
 }
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
index bbb4c6b..a27ad31 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -49,6 +49,8 @@
 
   Request<Set<Integer>> getNumberSet();
 
+  Request<SimpleFooProxy> getSimpleFooWithNullVersion();
+  
   Request<SimpleFooProxy> getSimpleFooWithSubPropertyCollection();
 
   Request<SimpleFooProxy> getTripletReference();
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
index 7def869..acd0863 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleRequestFactory.java
@@ -23,6 +23,8 @@
   
   InstanceServiceRequest instanceServiceRequest();
 
+  InstanceServiceRequestByName instanceServiceRequestByName();
+
   SimpleBarRequest simpleBarRequest();
 
   SimpleFooRequest simpleFooRequest();