Extends DynaTableRF with a client call to Person#persist.

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

Review by: robertvawter@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8526 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
index f7d3f56..61d5ea2 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
@@ -18,6 +18,18 @@
 
   <inherits name='com.google.gwt.user.User' />
   <inherits name='com.google.gwt.requestfactory.RequestFactory' />
+  
+  <inherits name='com.google.gwt.logging.Logging'/>
+  <set-property name="gwt.logging.enabled" value="TRUE"/> 
+  <set-property name="gwt.logging.logLevel" value="INFO"/>
+  <set-property name="gwt.logging.consoleHandler" value="ENABLED" />
+  <set-property name="gwt.logging.developmentModeHandler" value="DISABLED" />
+  <set-property name="gwt.logging.firebugHandler" value="ENABLED" />
+  <set-property name="gwt.logging.hasWidgetsHandler" value="DISABLED" />
+  <set-property name="gwt.logging.popupHandler" value="DISABLED" />
+  <set-property name="gwt.logging.systemHandler" value="ENABLED" />
+  <set-property name="gwt.logging.simpleRemoteHandler" value="DISABLED" />
+  
   <entry-point class='com.google.gwt.sample.dynatablerf.client.DynaTableRf' />
   
   <set-configuration-property name="CssResource.obfuscationPrefix" value="empty" />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/CalendarProvider.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/CalendarProvider.java
index 3760c43..23f5118 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/CalendarProvider.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/CalendarProvider.java
@@ -18,6 +18,7 @@
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.sample.dynatablerf.client.events.DataAvailableEvent;
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
 import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
@@ -25,12 +26,15 @@
 
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Logger;
 
 /**
  * A data provider that bridges the provides row level updates from the data
  * available through a <@link SchoolCalendarService>.
  */
 public class CalendarProvider {
+  private static final Logger log = Logger.getLogger(CalendarProvider.class.getName());
+
   private final HandlerManager eventBus = new HandlerManager(this);
 
   private int lastMaxRows = -1;
@@ -69,12 +73,52 @@
             lastMaxRows = maxRows;
             lastPeople = response;
             pushResults(startRow, response);
+
+            if (response.size() > 0) {
+              demoPersist(response.get(0));
+            }
           }
+
         });
   }
 
+  private void demoPersist(PersonProxy someone) {
+    /*
+     * Create a request to call someone's persist method.
+     */
+    RequestObject<Void> request = requests.personRequest().persist(someone);
+
+    someone = request.edit(someone);
+    someone.setName("Ray Ryan");
+    someone.setDescription("Was here");
+
+    request.fire(new Receiver<Void>() {
+      public void onSuccess(Void isNull, /* syncResults going away very soon */
+      Set<SyncResult> syncResults) {
+        /*
+         * A PersonProxyChanged should have fired. By M4, subtypes like
+         * PersonProxyChanged should go away and be replaced by a more general
+         * ProxyUpdateEvent
+         */
+        log.info("The persist call worked, did you see an update event?");
+      }
+
+      /*
+       * Coming soon
+       * 
+       * void onViolation(Set<ConstraintViolation> violations); void onError(
+       * ... tbd ... );
+       * 
+       * But likely this will come first, not sure who is dealing with
+       * serializing ConstraintViolation. Sorry.
+       * 
+       * void onViolation(Set<SyncResult> syncResults) { ... }
+       */
+
+    });
+  }
+
   private void pushResults(int startRow, List<PersonProxy> people) {
-    // TODO(rjrjr) RequestFactory should probably provide this event.
     eventBus.fireEvent(new DataAvailableEvent(startRow, people));
   }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
index 33c6836..5c4e058 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
@@ -48,7 +48,7 @@
 
     CalendarProvider provider = new CalendarProvider(requests);
 
-    calendar = new SchoolCalendarWidget(provider, 15);
+    calendar = new SchoolCalendarWidget(new DynaTableWidget(eventBus, provider, 15));
     filter = new DayFilterWidget(eventBus);
 
     RootLayoutPanel.get().add(
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableWidget.java
index 1628b9b..59326f7 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableWidget.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableWidget.java
@@ -18,10 +18,12 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.sample.dynatablerf.client.events.NavigationEvent;
 import com.google.gwt.sample.dynatablerf.client.events.DataAvailableEvent;
+import com.google.gwt.sample.dynatablerf.client.events.NavigationEvent;
 import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
+import com.google.gwt.sample.dynatablerf.shared.PersonProxyChanged;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -34,13 +36,13 @@
 import com.google.gwt.user.client.ui.Widget;
 
 import java.util.List;
+import java.util.logging.Logger;
 
 /**
  * A composite Widget that implements the main interface for the dynamic table,
  * including the data table, status indicators, and paging buttons.
  */
 public class DynaTableWidget extends Composite {
-
   interface Binder extends UiBinder<Widget, DynaTableWidget> {
   }
 
@@ -70,6 +72,8 @@
     }
   }
 
+  private static final Logger log = Logger.getLogger(DynaTableWidget.class.getName());
+
   // TODO: Re-add error handling
   @SuppressWarnings("unused")
   private static final String NO_CONNECTION_MESSAGE = "<p>The DynaTableRf example uses a "
@@ -95,11 +99,30 @@
 
   private HandlerRegistration rowDataRegistration;
 
-  public DynaTableWidget(CalendarProvider provider, int rowCount) {
+  public DynaTableWidget(HandlerManager eventBus, CalendarProvider provider,
+      int rowCount) {
     this.provider = provider;
     Binder binder = GWT.create(Binder.class);
     initWidget(binder.createAndBindUi(this));
     initTable(rowCount);
+
+    eventBus.addHandler(PersonProxyChanged.TYPE,
+        new PersonProxyChanged.Handler() {
+          public void onPersonChanged(PersonProxyChanged event) {
+            /*
+             * At the moment this proxy includes all the new property values,
+             * but that's an accident. Only its id property should be populated.
+             * 
+             * The correct thing to do, and soon the only thing that will work,
+             * is to fire an appropriate request to pick up the new values. No,
+             * I'm not happy about the extra round trip. Still thinking about
+             * that.
+             */
+            log.info("Look who changed, time to repaint some things: "
+                + event.getRecord());
+          }
+        });
+
   }
 
   public void clearStatusText() {
@@ -114,6 +137,25 @@
     navbar.gotoNext.setEnabled(false);
 
     setStatusText("Please wait...");
+
+    /*
+     * TODO the cell widgets reverse this relationship, to stay async friendly.
+     * 
+     * This widget would implement HasRows, and would not know directly about
+     * its data provider. Instead, the provider would know about the widget via
+     * something like
+     * 
+     * dynaTableWidget.addRangeChangeHandler(calendarProvider)
+     * 
+     * and on response would call
+     * 
+     * dynaTableWidget.setRowValues( ... ) directly
+     * 
+     * ListViewAdapter (soon to be renamed something like DataProvider) exists
+     * to make this convenient when dealing with lists, and to allow one
+     * provider to serve multiple HasData clients
+     * (AbstractListViewAdapter#addView(HasData<T>))
+     */
     provider.updateRowData(startRow, grid.getRowCount() - 1);
   }
 
@@ -167,6 +209,7 @@
     int srcRowIndex = 0;
     int srcRowCount = people.size();
     int destRowIndex = 1; // skip navbar row
+
     for (; srcRowIndex < srcRowCount; ++srcRowIndex, ++destRowIndex) {
       PersonProxy p = people.get(srcRowIndex);
       grid.setText(destRowIndex, 0, p.getName());
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/SchoolCalendarWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/SchoolCalendarWidget.java
index e7c45bc..97b9b42 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/SchoolCalendarWidget.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/SchoolCalendarWidget.java
@@ -31,8 +31,8 @@
 
   private ScheduledCommand pendingRefresh;
 
-  public SchoolCalendarWidget(CalendarProvider provider, int visibleRows) {
-    dynaTable = new DynaTableWidget(provider, visibleRows);
+  public SchoolCalendarWidget(DynaTableWidget dynaTable) {
+    this.dynaTable = dynaTable;
     initWidget(dynaTable);
   }
 
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
index a914982..75ee22a 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
@@ -15,30 +15,46 @@
  */
 package com.google.gwt.sample.dynatablerf.domain;
 
+import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
+
 /**
  * Hold relevant data for Person.
  */
 public abstract class Person {
-  private static Long serial = 1L;
+  /**
+   * The {@link RequestFactory} requires a static finder method for each proxied type.
+   * Soon it should allow you to customize how instances are found.
+   */
+  public static Person findPerson(Long id) {
+    return SchoolCalendarService.findPerson(id);
+  }
 
   private String description = "DESC";
 
   private String name;
-  
-  private final Long id;
+
+  private Long id;
+
+  private Integer version = 0;
 
   public Person() {
-    id = serial++;
   }
 
   public String getDescription() {
     return description;
   }
-  
+
+  /**
+   * The {@link RequestFactory} requires a Long id property for each proxied type.
+   * <p>
+   * The requirement for some kind of id object with proper hash / equals
+   * semantics is not going away, but it should become possible to use types
+   * other than Long, and properties other than "id".
+   */
   public Long getId() {
     return id;
   }
-  
+
   public String getName() {
     return name;
   }
@@ -49,15 +65,42 @@
 
   public abstract String getSchedule(boolean[] daysFilter);
 
+  /**
+   * The {@link RequestFactory} requires an Integer version property for each proxied
+   * type, but makes no good use of it. This requirement will be removed soon.
+   */
   public Integer getVersion() {
-    return 1;
+    return version;
+  }
+
+  /**
+   * When this was written the {@link RequestFactory} required a persist method per type. 
+   * That requirement should be relaxed very soon (and may well have been already
+   * if we forget to update this comment).
+   */
+  public void persist() {
+    SchoolCalendarService.persist(this);
   }
 
   public void setDescription(String description) {
     this.description = description;
   }
 
+  public void setId(Long id) {
+    this.id = id;
+  }
+
   public void setName(String name) {
     this.name = name;
   }
+
+  public void setVersion(Integer version) {
+    this.version = version;
+  }
+
+  @Override
+  public String toString() {
+    return "Person [description=" + description + ", id=" + id + ", name="
+        + name + ", version=" + version + "]";
+  }
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
index 1787f9e..dc7538c 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
@@ -24,13 +24,16 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 
 /**
  * The server side service class.
  */
 public class SchoolCalendarService {
+  private static Long serial = 0L;
 
   private static final String[] FIRST_NAMES = new String[] {
       "Inman", "Sally", "Omar", "Teddy", "Jimmy", "Cathy", "Barney", "Fred",
@@ -55,7 +58,7 @@
 
   private static final int STUDENTS_PER_PROF = 5;
 
-  private static final List<Person> people = new ArrayList<Person>();
+  private static final Map<Long, Person> people = new LinkedHashMap<Long, Person>(); 
 
   private static final Random rnd = new Random(3);
 
@@ -74,16 +77,28 @@
       return Collections.emptyList();
     }
 
-    return people.subList(startIndex, end);
+    return new ArrayList<Person>(people.values()).subList(startIndex, end);
   }
 
   private static void generateRandomPeople() {
     if (people.isEmpty())
       for (int i = 0; i < MAX_PEOPLE; ++i) {
         Person person = generateRandomPerson();
-        people.add(person);
+        persist(person);
       }
   }
+  
+  public static Person findPerson(Long id) {
+    return people.get(id);
+  }
+  
+  public static void persist(Person person) {
+    if (person.getId() == null) {
+      person.setId(++serial);
+    }
+    person.setVersion(person.getVersion() + 1);
+    people.put(person.getId(), person);
+  }
 
   private static Person generateRandomPerson() {
     // 1 out of every so many people is a prof.
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
index 3769203..e2a9c99 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
@@ -15,9 +15,12 @@
  */
 package com.google.gwt.sample.dynatablerf.shared;
 
+import com.google.gwt.requestfactory.shared.Instance;
 import com.google.gwt.requestfactory.shared.RecordListRequest;
 import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.requestfactory.shared.Service;
+import com.google.gwt.sample.dynatablerf.domain.Person;
 import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
 
 /**
@@ -27,12 +30,24 @@
 public interface DynaTableRequestFactory extends RequestFactory {
 
   /**
+   * Source of request objects for the Person class.  
+   */
+  @Service(Person.class)
+  interface PersonRequest {
+    @Instance
+    RequestObject<Void> persist(PersonProxy person);
+  }
+  
+  /**
    * Source of request objects for the SchoolCalendarService.
    */
   @Service(SchoolCalendarService.class)
   interface SchoolCalendarRequest {
+    // TODO(amitmanjhi, cromwellian) RequestObject<List<PersonProxy>>
     RecordListRequest<PersonProxy> getPeople(int startIndex, int maxCount);
   }
-
+  
+  PersonRequest personRequest();
+  
   SchoolCalendarRequest schoolCalendarRequest();
 }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java
index 186ceb1..76a065c 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java
@@ -33,9 +33,13 @@
   Property<String> description = new Property<String>("description", "Description", String.class);
   Property<String> schedule = new Property<String>("schedule", "Schedule", String.class);
 
-  String getSchedule();
-
   String getDescription();
 
   String getName();
+
+  String getSchedule();
+  
+  void setDescription(String description);
+  
+  void setName(String name);
 }
diff --git a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
index f3a6b27..d017f36 100644
--- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
@@ -52,6 +52,9 @@
  * An implementation of RequestProcessor for JSON encoded payloads.
  */
 public class JsonRequestProcessor implements RequestProcessor<String> {
+
+  private static final Logger log = Logger.getLogger(JsonRequestProcessor.class.getName());
+
   // TODO should we consume String, InputStream, or JSONObject?
   /**
    * A class representing the pair of a domain entity and its corresponding
@@ -71,7 +74,7 @@
 
   public static Set<String> initBlackList() {
     Set<String> blackList = new HashSet<String>();
-    for (String str : new String[]{"password"}) {
+    for (String str : new String[] {"password"}) {
       blackList.add(str);
     }
     return Collections.unmodifiableSet(blackList);
@@ -158,8 +161,7 @@
         int ordinal = Integer.parseInt(parameterValue);
         Method valuesMethod = parameterType.getDeclaredMethod("values",
             new Class[0]);
-        Logger.getLogger(this.getClass().getName()).severe(
-            "Type is " + parameterType + " valuesMethod " + valuesMethod);
+        log.severe("Type is " + parameterType + " valuesMethod " + valuesMethod);
 
         if (valuesMethod != null) {
           valuesMethod.setAccessible(true);
@@ -612,7 +614,9 @@
           recordObject.get("id"), propertiesInRecord.get("id"));
 
       // persist
-      Set<ConstraintViolation<Object>> violations = null;
+
+      Set<ConstraintViolation<Object>> violations = Collections.emptySet();
+
       if (writeOperation == WriteOperation.DELETE) {
         entity.getMethod("remove").invoke(entityInstance);
       } else {
@@ -635,10 +639,24 @@
         }
 
         // validations check..
-        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
-        Validator validator = validatorFactory.getValidator();
+        Validator validator = null;
+        try {
+          ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
+          validator = validatorFactory.getValidator();
+        } catch (Exception e) {
+          /*
+           * This is JBoss's clumsy way of telling us that the system has not
+           * been configured.
+           */
+          log.info(String.format(
+              "Ingnoring exception caught initializing bean validation framework. "
+                  + "It is probably unconfigured or misconfigured. [%s] %s ",
+              e.getClass().getName(), e.getLocalizedMessage()));
+        }
 
-        violations = validator.validate(entityInstance);
+        if (validator != null) {
+          violations = validator.validate(entityInstance);
+        }
         if (violations.isEmpty()) {
           entity.getMethod("persist").invoke(entityInstance);
         }
@@ -648,6 +666,8 @@
       return getReturnRecord(writeOperation, entityInstance, recordObject,
           violations);
     } catch (Exception ex) {
+      log.severe(String.format("Caught exception [%s] %s",
+          ex.getClass().getName(), ex.getLocalizedMessage()));
       return getReturnRecordForException(writeOperation, recordObject, ex);
     }
   }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index ef3be44..a52862d 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -58,7 +58,6 @@
   private static final String JSON_CHARSET = "UTF-8";
   private static final String JSON_CONTENT_TYPE = "application/json";
   
-  @SuppressWarnings("unchecked")
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
       throws IOException, ServletException {
diff --git a/user/src/com/google/gwt/valuestore/shared/SyncResult.java b/user/src/com/google/gwt/valuestore/shared/SyncResult.java
index 7161c32..6c2e6cc 100644
--- a/user/src/com/google/gwt/valuestore/shared/SyncResult.java
+++ b/user/src/com/google/gwt/valuestore/shared/SyncResult.java
@@ -29,6 +29,7 @@
   // TODO: move violations out of the SyncResult...
   boolean hasViolations();
   
+  // TODO: futureId isn't working out so well, leaving soon
   Long getFutureId();
 
   Record getRecord();