Revision: 7698
Author: [email protected]
Date: Wed Mar 10 17:23:17 2010
Log: Here's the first ever request with a parameter, and the parameter is provided by
an object received from a previous RPC call.

The bulk of this is actually two fixes to the mock persistence thingy: entities
pointing to entities weren't working; and get was returning the canonical
objects, rather than making defensive copies of them.

On to CRUD!

http://gwt-code-reviews.appspot.com/168801

Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=7698

Added:
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeList.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipRefreshingVisitor.java /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipValidationVisitor.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/Report.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/ReportRequests.java
Modified:
 /trunk/bikeshed/.classpath
 /trunk/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java
 /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java
/trunk/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java
 /trunk/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java

=======================================
--- /dev/null
+++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeList.java Wed Mar 10 17:23:17 2010
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.client;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.sample.expenses.shared.Employee;
+import com.google.gwt.user.client.ui.HasValueList;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.valuestore.shared.Values;
+
+import java.util.List;
+
+/**
+ * Manages the Employee ListBox. This shoudl grow into a proper View, with a
+ * corresponding Presenter factored out of {...@link Expenses}
+ */
+public final class EmployeeList implements HasValueList<Values<Employee>> {
+  interface Listener {
+    void onEmployeeSelected(Employee e);
+  }
+
+  private final class MyChangeHandler implements ChangeHandler {
+    public void onChange(ChangeEvent event) {
+      int selectedIndex = listBox.getSelectedIndex();
+      Values<Employee> values = employeeValues.get(selectedIndex);
+      Employee e = values.getPropertyHolder();
+      listener.onEmployeeSelected(e);
+    }
+  }
+
+  private final ListBox listBox;
+  private List<Values<Employee>> employeeValues;
+  private Listener listener;
+
+  /**
+   * @param shell
+   * @param requestFactory
+   */
+  public EmployeeList(ListBox listBox) {
+    this.listBox = listBox;
+    listBox.addChangeHandler(new MyChangeHandler());
+  }
+
+  public void editValueList(boolean replace, int index,
+      List<Values<Employee>> newValues) {
+    throw new UnsupportedOperationException();
+  }
+
+  public void setListener(Listener listener) {
+    this.listener = listener;
+  }
+
+  public void setValueList(List<Values<Employee>> newValues) {
+    this.employeeValues = newValues;
+    listBox.clear();
+    for (int i = 0; i < employeeValues.size(); i++) {
+      Values<Employee> values = employeeValues.get(i);
+      listBox.addItem(values.get(Employee.DISPLAY_NAME));
+    }
+  }
+
+  public void setValueListSize(int size, boolean exact) {
+    throw new UnsupportedOperationException();
+  }
+}
=======================================
--- /dev/null
+++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipRefreshingVisitor.java Wed Mar 10 17:23:17 2010
@@ -0,0 +1,48 @@
+/*
+ * 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.domain;
+
+/**
+ * Used by {...@link Storage#get(Entity)} to refreshes fields that point to other
+ * entities.
+ */
+class RelationshipRefreshingVisitor implements EntityVisitor<Void> {
+  private final Storage s;
+
+  public RelationshipRefreshingVisitor(Storage s) {
+    this.s = s;
+  }
+
+  public Void visit(Currency currency) {
+    return null;
+  }
+
+  public Void visit(Employee employee) {
+    employee.setSupervisor(s.get(employee.getSupervisor()));
+    return null;
+  }
+
+  public Void visit(Report report) {
+    report.setApprovedSupervisor(s.get(report.getApprovedSupervisor()));
+    report.setReporter(s.get(report.getReporter()));
+    return null;
+  }
+
+  public Void visit(ReportItem reportItem) {
+    reportItem.setReport(s.get(reportItem.getReport()));
+    return null;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipValidationVisitor.java Wed Mar 10 17:23:17 2010
@@ -0,0 +1,54 @@
+/*
+ * 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.domain;
+
+/**
+ * Used by {...@link Storage#persist(Entity)} to ensure relationships are valid
+ * (can't point to an Entity with no id);
+ */
+public class RelationshipValidationVisitor implements EntityVisitor<Void> {
+  public Void visit(Currency currency) {
+    return null;
+  }
+
+  public Void visit(Employee employee) {
+    validate(employee, employee.getSupervisor());
+    return null;
+  }
+
+  public Void visit(Report report) {
+    validate(report, report.getApprovedSupervisor());
+    validate(report, report.getReporter());
+    return null;
+  }
+
+  public Void visit(ReportItem reportItem) {
+    validate(reportItem, reportItem.getReport());
+    return null;
+  }
+
+  /**
+   * @param supervisor
+   */
+  private void validate(Entity from, Entity to) {
+    if ((to != null) && (to.getId() == null)) {
+      throw new IllegalArgumentException(String.format(
+ "Attempt to point from %s " + "to invalid (null id) entity %s", from,
+          to));
+    }
+  }
+
+}
=======================================
--- /dev/null
+++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/Report.java Wed Mar 10 17:23:17 2010
@@ -0,0 +1,61 @@
+/*
+ * 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.shared;
+
+import com.google.gwt.requestfactory.shared.Entity;
+import com.google.gwt.requestfactory.shared.Slot;
+import com.google.gwt.valuestore.shared.Property;
+
+import java.util.Date;
+
+/**
+ * "Generated" proxy of {...@link com.google.gwt.sample.expenses.domain.Report
+ * domain.Report}.
+ */
+public class Report implements Entity<Report> {
+
+ public static final Property<Report, String> ID = new Property<Report, String>(
+      Report.class, String.class, "ID");
+
+ public static final Property<Report, Integer> VERSION = new Property<Report, Integer>(
+      Report.class, Integer.class, "VERSION");
+
+ public static final Property<Report, Date> CREATED = new Property<Report, Date>(
+      Report.class, Date.class, "CREATED");
+
+ public static final Property<Report, String> PURPOSE = new Property<Report, String>(
+      Report.class, String.class, "PURPOSE");
+
+  private final String id;
+  private final Integer version;
+
+  Report(String id, Integer version) {
+    this.id = id;
+    this.version = version;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public Integer getVersion() {
+    return version;
+  }
+
+  public <V> Slot<Report, V> slot(Property<Report, V> property) {
+    return new Slot<Report, V>(this, property);
+  }
+}
=======================================
--- /dev/null
+++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/ReportRequests.java Wed Mar 10 17:23:17 2010
@@ -0,0 +1,111 @@
+/*
+ * 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.shared;
+
+import com.google.gwt.core.client.JsArray;
+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.EntityListRequest;
+import com.google.gwt.requestfactory.shared.Slot;
+import com.google.gwt.sample.expenses.client.ValuesImpl;
+import com.google.gwt.user.client.ui.HasValueList;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.ValueStore;
+import com.google.gwt.valuestore.shared.Values;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * "Generated" from static methods of
+ * {...@link com.google.gwt.sample.expenses.domain.Employee}
+ */
+public class ReportRequests {
+
+  public ReportRequests(ValueStore values) {
+  }
+
+  public EntityListRequest<Report> findReportsByEmployee(
+      final Slot<Employee, String> id) {
+
+    return new EntityListRequest<Report>() {
+ Set<Property<Report, ?>> properties = new HashSet<Property<Report, ?>>();
+      private HasValueList<Values<Report>> watcher;
+
+      public void fire() {
+
+ // TODO: need some way to track that this request has been issued so that + // we don't issue another request that arrives while we are waiting for
+        // the response.
+        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
+            "/expenses/data?methodName="
+                + MethodName.FIND_REPORTS_BY_EMPLOYEE.name() + "&id="
+                + id.getEntity().getId());
+        builder.setCallback(new RequestCallback() {
+
+          public void onError(Request request, Throwable exception) {
+            // shell.error.setInnerText(SERVER_ERROR);
+          }
+
+ public void onResponseReceived(Request request, Response response) {
+            if (200 == response.getStatusCode()) {
+              String text = response.getText();
+ JsArray<ValuesImpl<Report>> valueArray = ValuesImpl.arrayFromJson(text); + List<Values<Report>> valueList = new ArrayList<Values<Report>>(
+                  valueArray.length());
+              for (int i = 0; i < valueArray.length(); i++) {
+                ValuesImpl<Report> values = valueArray.get(i);
+                String id2 = values.get(Report.ID);
+                Integer version = values.get(Report.VERSION);
+                values.setPropertyHolder(new Report(id2,
+                    version));
+                valueList.add(values);
+              }
+              watcher.setValueList(valueList);
+            } else {
+              // shell.error.setInnerText(SERVER_ERROR + " ("
+              // + response.getStatusText() + ")");
+            }
+          }
+        });
+
+        try {
+          builder.send();
+        } catch (RequestException e) {
+ // shell.error.setInnerText(SERVER_ERROR + " (" + e.getMessage() +
+          // ")");
+        }
+
+        // values.subscribe(watcher, future, properties);
+      }
+
+ public EntityListRequest<Report> forProperty(Property<Report, ?> property) {
+        properties.add(property);
+        return this;
+      }
+
+ public EntityListRequest<Report> to(HasValueList<Values<Report>> watcher) {
+        this.watcher = watcher;
+        return this;
+      }
+    };
+  }
+}
=======================================
--- /trunk/bikeshed/.classpath  Tue Mar  9 10:30:26 2010
+++ /trunk/bikeshed/.classpath  Wed Mar 10 17:23:17 2010
@@ -3,9 +3,9 @@
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-       <classpathentry kind="lib" path="war/WEB-INF/lib/gwt-servlet.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> - <classpathentry kind="var" path="GWT_TOOLS/redist/json/r2_20080312/json.jar"/> + <classpathentry kind="var" path="GWT_TOOLS/redist/json/r2_20080312/json.jar" sourcepath="/GWT_TOOLS/redist/json/r2_20080312/json-src.jar"/> <classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
+       <classpathentry kind="lib" path="war/WEB-INF/lib/gwt-servlet.jar"/>
        <classpathentry kind="output" path="war/WEB-INF/classes"/>
 </classpath>
=======================================
--- /trunk/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java Wed Mar 10 17:23:17 2010
@@ -56,6 +56,10 @@
     }
     return true;
   }
+
+  public E getEntity() {
+    return entity;
+  }

   @Override
   public int hashCode() {
@@ -65,10 +69,6 @@
     result = prime * result + property.hashCode();
     return result;
   }
-
-  E getEntity() {
-    return entity;
-  }

   Property<E, V> getProperty() {
     return property;
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java Mon Mar 8 13:39:42 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java Wed Mar 10 17:23:17 2010
@@ -17,16 +17,10 @@

 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
 import com.google.gwt.sample.expenses.shared.Employee;
 import com.google.gwt.sample.expenses.shared.ExpenseRequestFactory;
-import com.google.gwt.user.client.ui.HasValueList;
+import com.google.gwt.sample.expenses.shared.Report;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.valuestore.shared.Values;
-
-import java.util.List;

 /**
  * Entry point classes define <code>onModuleLoad()</code>.
@@ -49,40 +43,22 @@
     RootLayoutPanel root = RootLayoutPanel.get();

     final Shell shell = new Shell();
+    final EmployeeList employees = new EmployeeList(shell.users);
+
     root.add(shell);

- final HasValueList<Values<Employee>> employees = new HasValueList<Values<Employee>>() {
-
-      public void editValueList(boolean replace, int index,
-          List<Values<Employee>> newValues) {
-        throw new UnsupportedOperationException();
-      }
-
-      public void setValueList(List<Values<Employee>> newValues) {
-        shell.users.clear();
-        for (Values<Employee> values : newValues) {
-          shell.users.addItem(values.get(Employee.DISPLAY_NAME),
-              values.get(Employee.USER_NAME));
-        }
-      }
-
-      public void setValueListSize(int size, boolean exact) {
-        throw new UnsupportedOperationException();
-      }
-    };
+    employees.setListener(new EmployeeList.Listener() {
+      public void onEmployeeSelected(Employee e) {
+        requestFactory.reportRequest().//
+        findReportsByEmployee(e.slot(Employee.ID)).//
+        forProperty(Report.CREATED).//
+        forProperty(Report.PURPOSE).//
+        to(shell).//
+        fire();
+      }
+    });

     requestFactory.employeeRequest().findAllEmployees().forProperty(
- Employee.DISPLAY_NAME).forProperty(Employee.USER_NAME).to(shell).fire();
-
-    // TODO(rjrjr) now get details
-    final TextBox nameHolder = new TextBox();
-
-    shell.users.addChangeHandler(new ChangeHandler() {
-      public void onChange(ChangeEvent event) {
-        nameHolder.setText("gesundheit");
-        // Remember the slots
- // requestFactory.employeeRequest().findEmployee(literal(shell.users.getValue());
-      }
-    });
+ Employee.DISPLAY_NAME).forProperty(Employee.USER_NAME).to(employees).fire();
   }
 }
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java Mon Mar 8 13:39:42 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java Wed Mar 10 17:23:17 2010
@@ -22,22 +22,25 @@
 import com.google.gwt.dom.client.TableCellElement;
 import com.google.gwt.dom.client.TableElement;
 import com.google.gwt.dom.client.TableRowElement;
-import com.google.gwt.sample.expenses.shared.Employee;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.sample.expenses.shared.Report;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HasValueList;
 import com.google.gwt.user.client.ui.ListBox;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Values;

+import java.util.Date;
 import java.util.List;

 /**
- * UI shell for expenses sample app.
+ * UI shell for expenses sample app. A horrible clump of stuff that should be
+ * refactored into proper MVP pieces.
  */
-public class Shell extends Composite implements HasValueList<Values<Employee>> { +public class Shell extends Composite implements HasValueList<Values<Report>> {

   interface ShellUiBinder extends UiBinder<Widget, Shell> {
   }
@@ -52,35 +55,38 @@
   @UiField
   ListBox users;

-  private Command refresh;
-
   public Shell() {
     initWidget(uiBinder.createAndBindUi(this));
   }

   public void editValueList(boolean replace, int index,
-      List<Values<Employee>> newValues) {
+      List<Values<Report>> newValues) {
     throw new UnsupportedOperationException();
   }

-  public void setValueList(List<Values<Employee>> newValues) {
+  public void setValueList(List<Values<Report>> newValues) {
     int r = 1; // skip header
     NodeList<TableRowElement> tableRows = table.getRows();
     for (int i = 0; i < newValues.size(); i++) {
-      Values<Employee> valueRow = newValues.get(i);
+      Values<Report> valueRow = newValues.get(i);
+
       if (r < tableRows.getLength()) {
         reuseRow(r, tableRows, valueRow);
       } else {
         TableRowElement tableRow = Document.get().createTRElement();

         TableCellElement tableCell = Document.get().createTDElement();
-        tableCell.setInnerText(valueRow.get(Employee.USER_NAME));
+        tableCell.setInnerText(renderDate(valueRow, Report.CREATED));
         tableRow.appendChild(tableCell);

         tableCell = Document.get().createTDElement();
-        tableCell.setInnerText(valueRow.get(Employee.DISPLAY_NAME));
+        /* status goes here */
         tableRow.appendChild(tableCell);

+        tableCell = Document.get().createTDElement();
+        tableCell.setInnerText(valueRow.get(Report.PURPOSE));
+        tableRow.appendChild(tableCell);
+
         table.appendChild(tableRow);
       }
       r++;
@@ -93,6 +99,10 @@
   public void setValueListSize(int size, boolean exact) {
     throw new UnsupportedOperationException();
   }
+
+ private <T> String renderDate(Values<T> values, Property<T, Date> property) { + return DateTimeFormat.getShortDateTimeFormat().format(values.get(property));
+  }

   /**
    * @param r
@@ -100,11 +110,11 @@
    * @param valueRow
    */
   private void reuseRow(int r, NodeList<TableRowElement> tableRows,
-      Values<Employee> valueRow) {
+      Values<Report> valueRow) {
     TableRowElement tableRow = tableRows.getItem(r);
     NodeList<TableCellElement> tableCells = tableRow.getCells();

-    tableCells.getItem(0).setInnerText(valueRow.get(Employee.USER_NAME));
- tableCells.getItem(0).setInnerText(valueRow.get(Employee.DISPLAY_NAME)); + // tableCells.getItem(0).setInnerText(valueRow.get(Report.CREATED).toString());
+    tableCells.getItem(2).setInnerText(valueRow.get(Report.PURPOSE));
   }
 }
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml Mon Mar 8 13:39:42 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml Wed Mar 10 17:23:17 2010
@@ -47,13 +47,14 @@

     <g:center>
       <g:HTMLPanel width='100%' height='100%'>
-        <h1>Employees</h1>
+        <h1>Expenses</h1>
         <table ui:field='table' class='{style.reports}' width='100%'>
-          <col width='0%'></col>
+          <col width='0%' span='2'></col>
           <col width='100%'></col>
           <tr ui:field='header'>
-            <th>UID</th>
-            <th align='left'>Pretty Name</th>
+            <th>Created</th>
+            <th>Status&nbsp;(tbd)</th>
+            <th align='left'>Purpose</th>
           </tr>
         </table>
       </g:HTMLPanel>
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java Wed Mar 10 17:23:17 2010
@@ -15,13 +15,17 @@
  */
 package com.google.gwt.sample.expenses.client;

+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Values;

+import java.util.Date;
+
 /**
  * JSO implementation of {...@link Values}.
+ *
  * @param <T> value type
  */
public final class ValuesImpl<T> extends JavaScriptObject implements Values<T> {
@@ -37,15 +41,47 @@
   protected ValuesImpl() {
   }

-  public native <V, P extends Property<T, V>> V get(P property) /*-{
- return this[[email protected]::getName()()];
-  }-*/;
+  @SuppressWarnings("unchecked")
+  public <V, P extends Property<T, V>> V get(P property) {
+
+    if (Integer.class.equals(property.getValueType())) {
+      return (V) Integer.valueOf(getInt(property.getName()));
+    }
+    if (Date.class.equals(property.getValueType())) {
+      double millis = getDouble(property.getName());
+      if (GWT.isScript()) {
+        return (V) initDate(new Date(), millis);
+      } else {
+        // In dev mode, we're using real JRE dates
+        return (V) new Date((long) millis);
+      }
+    }
+
+    return nativeGet(property);
+  }

   public native T getPropertyHolder() /*-{
     return this.propertyHolder;
   }-*/;
-
+
   public native void setPropertyHolder(T propertyHolder) /*-{
     this.propertyHolder = propertyHolder;
   }-*/;
-}
+
+  private native int getInt(String name) /*-{
+    return this[name];
+  }-*/;
+
+  private native double getDouble(String name) /*-{
+    return this[name];
+  }-*/;
+
+  private native Date initDate(Date date, double millis) /*-{
+    [email protected]::init(D)(millis);
+    return date;
+  }-*/;
+
+  private native <V, P extends Property<T, V>> V nativeGet(P property) /*-{
+ return this[[email protected]::getName()()];
+  }-*/;
+}
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java Wed Mar 10 17:23:17 2010
@@ -20,8 +20,7 @@
  *
  * @param <E> The type of entity to create.
  */
-//We guarantee same type at runtime
-public class CreationVisitor<E extends Entity> implements EntityVisitor<E> {
+class CreationVisitor<E extends Entity> implements EntityVisitor<E> {
   private final long id;
   private final int version;

@@ -43,18 +42,22 @@
     this.version = version;
   }

+  @SuppressWarnings("unchecked")
   public E visit(Currency currency) {
     return (E) new Currency(id, version);
   }

+  @SuppressWarnings("unchecked")
   public E visit(Employee employee) {
     return (E) new Employee(id, version);
   }

+  @SuppressWarnings("unchecked")
   public E visit(Report report) {
     return (E) new Report(id, version);
   }

+  @SuppressWarnings("unchecked")
   public E visit(ReportItem reportItem) {
     return (E) new ReportItem(id, version);
   }
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java Wed Mar 10 17:23:17 2010
@@ -63,6 +63,9 @@
     if (sparse.getApprovedSupervisor() == null) {
       sparse.setApprovedSupervisor(report.getApprovedSupervisor());
     }
+    if (sparse.getCreated() == null) {
+      sparse.setCreated(report.getCreated());
+    }
     if (sparse.getPurpose() == null) {
       sparse.setPurpose(report.getPurpose());
     }
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java Tue Mar 9 10:30:26 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java Wed Mar 10 17:23:17 2010
@@ -16,19 +16,24 @@
 package com.google.gwt.sample.expenses.domain;

 import java.util.Date;
+import java.util.List;

 /**
  * Models an expense report.
  */
 // @javax.persistence.Entity
 public class Report implements Entity {
-  private final Long id;
-
+  public static List<Report> findReportsByEmployee(long id) {
+    return Storage.INSTANCE.findReportsByEmployee(id);
+  }
+
+  private final Long id;
   private final Integer version;
+
 //  @javax.validation.constraints.NotNull
 //  @javax.validation.constraints.Past
   // @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
-  private java.util.Date created = new Date();
+  private java.util.Date created;

 //  @javax.validation.constraints.NotNull
   // @javax.persistence.Enumerated
@@ -43,15 +48,15 @@
 //  @javax.validation.constraints.Size(min = 3, max = 100)
   private String purpose;

+ // @javax.persistence.OneToMany(cascade = javax.persistence.CascadeType.ALL,
+  // mappedBy = "report")
+  // private Set<ReportItem> items = new HashSet<ReportItem>();
+
   // @javax.persistence.ManyToOne(targetEntity =
   // com.google.io.expenses.server.domain.Employee.class)
   // @javax.persistence.JoinColumn
   private Employee approvedSupervisor;

- // @javax.persistence.OneToMany(cascade = javax.persistence.CascadeType.ALL,
-  // mappedBy = "report")
-  // private Set<ReportItem> items = new HashSet<ReportItem>();
-
   public Report() {
     id = null;
     version = null;
@@ -121,6 +126,10 @@
   public void setApprovedSupervisor(Employee approvedSupervisor) {
     this.approvedSupervisor = approvedSupervisor;
   }
+
+  public void setCreated(Date date) {
+    this.created = date;
+  }

   /**
    * @param purpose the purpose to set
@@ -135,7 +144,7 @@
   public void setReporter(Employee reporter) {
     this.reporter = reporter;
   }
-
+
   /**
    * @param status the status to set
    */
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java Tue Mar 9 10:30:26 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java Wed Mar 10 17:23:17 2010
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.sample.expenses.domain;

+import java.util.Date;
+
 /**
  * Models a line item in an expense report.
  */
@@ -32,7 +34,7 @@
 //  @javax.validation.constraints.NotNull
 //  @javax.validation.constraints.Past
   // @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
-  private java.util.Date incurred;
+  private Date incurred;

 //  @javax.validation.constraints.Size(min = 3, max = 100)
   private String purpose;
@@ -84,7 +86,7 @@
   /**
    * @return the incurred
    */
-  public java.util.Date getIncurred() {
+  public Date getIncurred() {
     return incurred;
   }

=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java Tue Mar 9 10:30:26 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java Wed Mar 10 17:23:17 2010
@@ -16,9 +16,13 @@
 package com.google.gwt.sample.expenses.domain;

 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;

 /**
* Pretend pool of domain objects, trying to act more or less like persistence
@@ -30,65 +34,169 @@
     INSTANCE = new Storage();
     fill(INSTANCE);
   }
-
-  public static <E extends Entity> E edit(E v1) {
-    return v1.accept(new CreationVisitor<E>(v1));
-  }

   /**
    * @param storage to fill with demo entities
    */
   static void fill(Storage storage) {
-    Employee e = new Employee();
-    e.setUserName("abc");
-    e.setDisplayName("Able B. Charlie");
-    e.setSupervisor(e);
-    storage.persist(e);
-
-    Employee e2 = new Employee();
-    e2.setUserName("def");
-    e2.setDisplayName("Delta E. Foxtrot");
-    e2.setSupervisor(e);
-    storage.persist(e2);
-
-    e2 = new Employee();
-    e2.setUserName("ghi");
-    e2.setDisplayName("George H. Indigo");
-    e2.setSupervisor(e);
-    storage.persist(e2);
+    Employee abc = new Employee();
+    abc.setUserName("abc");
+    abc.setDisplayName("Able B. Charlie");
+    abc = storage.persist(abc);
+    abc.setSupervisor(abc);
+    abc = storage.persist(abc);
+
+    Employee def = new Employee();
+    def.setUserName("def");
+    def.setDisplayName("Delta E. Foxtrot");
+    def.setSupervisor(abc);
+    def = storage.persist(def);
+
+    Employee ghi = new Employee();
+    ghi.setUserName("ghi");
+    ghi.setDisplayName("George H. Indigo");
+    ghi.setSupervisor(abc);
+    ghi = storage.persist(ghi);
+
+    Report abc1 = new Report();
+    abc1.setReporter(abc);
+    abc1.setCreated(new Date());
+    abc1.setPurpose("Spending lots of money");
+    abc1 = storage.persist(abc1);
+
+    Report abc2 = new Report();
+    abc2.setReporter(abc);
+    abc2.setCreated(new Date());
+    abc2.setPurpose("Team building diamond cutting offsite");
+    abc2 = storage.persist(abc2);
+
+    Report abc3 = new Report();
+    abc3.setReporter(abc);
+    abc3.setCreated(new Date());
+    abc3.setPurpose("Visit to Istanbul");
+    storage.persist(abc3);
+
+    Report def1 = new Report();
+    def1.setReporter(def);
+    def1.setCreated(new Date());
+    def1.setPurpose("Money laundering");
+    def1 = storage.persist(def1);
+
+    Report def2 = new Report();
+    def2.setReporter(def);
+    def2.setCreated(new Date());
+    def2.setPurpose("Donut day");
+    storage.persist(def2);
+
+    Report ghi1 = new Report();
+    ghi1.setReporter(ghi);
+    ghi1.setCreated(new Date());
+    ghi1.setPurpose("ISDN modem for telecommuting");
+    storage.persist(ghi1);
+
+    Report ghi2 = new Report();
+    ghi2.setReporter(ghi);
+    ghi2.setCreated(new Date());
+    ghi2.setPurpose("Sushi offsite");
+    ghi2 = storage.persist(ghi2);
+
+    Report ghi3 = new Report();
+    ghi3.setReporter(ghi);
+    ghi3.setCreated(new Date());
+    ghi3.setPurpose("Baseball card research");
+    ghi3 = storage.persist(ghi3);
+
+    Report ghi4 = new Report();
+    ghi4.setReporter(ghi);
+    ghi4.setCreated(new Date());
+    ghi4.setPurpose("Potato chip cooking offsite");
+    ghi4 = storage.persist(ghi4);
+  }
+
+  /**
+ * Useful for making a surgical update to an entity, e.g. in response to a web
+   * update.
+   * <p>
+ * Given an entity, returns an empty copy: all fields are null except id and + * version. When this copy is later persisted, only non-null fields will be
+   * changed.
+   */
+  static <E extends Entity> E startSparseEdit(E v1) {
+    return v1.accept(new CreationVisitor<E>(v1));
   }

   private final Map<Long, Entity> soup = new HashMap<Long, Entity>();
private final Map<String, Long> employeeUserNameIndex = new HashMap<String, Long>();
-
+ private final Map<Long, Set<Long>> reportsByEmployeeIndex = new HashMap<Long, Set<Long>>();
+
+  private Map<Long, Entity> freshForCurrentGet;
+  private int getDepth = 0;
   private long serial = 0;

   synchronized List<Employee> findAllEmployees() {
     List<Employee> rtn = new ArrayList<Employee>();
for (Map.Entry<String, Long> entry : employeeUserNameIndex.entrySet()) {
-      rtn.add((Employee) get(entry.getValue()));
+      rtn.add(get((Employee) rawGet(entry.getValue())));
     }
     return rtn;
   }

   synchronized Employee findEmployeeByUserName(String userName) {
     Long id = employeeUserNameIndex.get(userName);
-    return (Employee) get(id);
+    return get((Employee) rawGet(id));
   }

+  synchronized List<Report> findReportsByEmployee(long id) {
+    Set<Long> reportIds = reportsByEmployeeIndex.get(id);
+    if (reportIds == null) {
+      return Collections.emptyList();
+    }
+    List<Report> reports = new ArrayList<Report>(reportIds.size());
+    for (Long reportId : reportIds) {
+      reports.add(get((Report) rawGet(reportId)));
+    }
+    return reports;
+  }
+
+  /**
+   * @return An up to date copy of the given entity, safe for editing.
+   */
   @SuppressWarnings("unchecked")
   // We make runtime checks that return type matches in type
   synchronized <E extends Entity> E get(final E entity) {
-    Entity previous = soup.get(entity.getId());
-    if (null == previous) {
- throw new IllegalArgumentException(String.format("In %s, unknown id %d",
-          entity, entity.getId()));
-    }
-    if (!previous.getClass().equals(entity.getClass())) {
-      throw new IllegalArgumentException(String.format(
-          "Type mismatch, fetched %s for %s", entity, previous));
-    }
-    return (E) previous;
+    if (getDepth == 0) {
+      freshForCurrentGet = new HashMap<Long, Entity>();
+    }
+    getDepth++;
+    try {
+      if (entity == null) {
+        return null;
+      }
+      Entity previous = rawGet(entity.getId());
+      if (null == previous) {
+        throw new IllegalArgumentException(String.format(
+            "In %s, unknown id %d", entity, entity.getId()));
+      }
+      if (!previous.getClass().equals(entity.getClass())) {
+        throw new IllegalArgumentException(String.format(
+            "Type mismatch, fetched %s for %s", entity, previous));
+      }
+
+      Entity rtn = freshForCurrentGet.get(previous.getId());
+      if (rtn == null) {
+        // Make a defensive copy
+        rtn = copy(previous);
+        freshForCurrentGet.put(previous.getId(), rtn);
+        // Make sure it has fresh copies of related entities
+        rtn.accept(new RelationshipRefreshingVisitor(this));
+      }
+      return (E) rtn;
+    } finally {
+      getDepth--;
+      if (getDepth == 0) {
+        freshForCurrentGet = null;
+      }
+    }
   }

   synchronized <E extends Entity> E persist(final E delta) {
@@ -106,7 +214,7 @@
             previous.getVersion(), delta.getVersion()));
       }

-      next = previous.accept(new CreationVisitor<E>(++serial,
+      next = previous.accept(new CreationVisitor<E>(previous.getId(),
           previous.getVersion() + 1));

       NullFieldFiller filler = new NullFieldFiller(next);
@@ -116,13 +224,25 @@
       // version
       previous.accept(filler);
     }
+
+    next.accept(new RelationshipValidationVisitor());

     updateIndices(previous, next);
     soup.put(next.getId(), next);
-    return next;
+    return get(next);
   }

-  private synchronized Entity get(Long id) {
+  /**
+   * @param original Entity to copy
+   * @return copy of original
+   */
+  private Entity copy(Entity original) {
+    Entity copy = original.accept(new CreationVisitor<Entity>(original));
+    original.accept(new NullFieldFiller(copy));
+    return copy;
+  }
+
+  private synchronized Entity rawGet(Long id) {
     return soup.get(id);
   }

@@ -146,6 +266,17 @@
       }

       public Void visit(Report report) {
+        Employee reporter = report.getReporter();
+        if (reporter == null) {
+          return null;
+        }
+        Long employeeId = reporter.getId();
+        Set<Long> reportIds = reportsByEmployeeIndex.get(employeeId);
+        if (reportIds == null) {
+          reportIds = new LinkedHashSet<Long>();
+          reportsByEmployeeIndex.put(employeeId, reportIds);
+        }
+        reportIds.add(report.getId());
         return null;
       }

=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java Mon Mar 8 13:39:42 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java Wed Mar 10 17:23:17 2010
@@ -16,6 +16,7 @@
 package com.google.gwt.sample.expenses.server;

 import com.google.gwt.sample.expenses.domain.Employee;
+import com.google.gwt.sample.expenses.domain.Report;
 import com.google.gwt.sample.expenses.shared.MethodName;

 import org.json.JSONArray;
@@ -30,7 +31,7 @@
 import javax.servlet.http.HttpServletResponse;

 /**
- *
+ *
  */
 public class ExpensesDataServlet extends HttpServlet {

@@ -44,25 +45,73 @@
     PrintWriter writer = response.getWriter();
     switch (methodName) {
       case FIND_ALL_EMPLOYEES:
-        JSONArray jsonArray = new JSONArray();
-        for (Employee e : Employee.findAllEmployees()) {
-          try {
-            JSONObject jsonObject = new JSONObject();
-            jsonObject.put("USER_NAME", e.getUserName());
-            jsonObject.put("DISPLAY_NAME", e.getDisplayName());
-            jsonArray.put(jsonObject);
-          } catch (JSONException ex) {
-            System.err.println("Unable to create a JSON object " + ex);
-          }
-        }
-        writer.print(jsonArray.toString());
+        findAllEmployees(writer);
         break;
       case FIND_EMPLOYEE:
         // TODO
         break;
+      case FIND_REPORTS_BY_EMPLOYEE:
+        findReportsByEmployee(request, writer);
+        break;
     }
     writer.flush();
   }
+
+  private void findAllEmployees(PrintWriter writer) {
+    JSONArray jsonArray = new JSONArray();
+    for (Employee e : Employee.findAllEmployees()) {
+      try {
+        // TODO should only be getting requested properties
+ // TODO clearly there should be centralized code for these conversions
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put(
+            com.google.gwt.sample.expenses.shared.Employee.ID.getName(),
+            Long.toString(e.getId()));
+        jsonObject.put(
+ com.google.gwt.sample.expenses.shared.Employee.VERSION.getName(),
+            e.getVersion().intValue());
+        jsonObject.put(
+ com.google.gwt.sample.expenses.shared.Employee.USER_NAME.getName(),
+            e.getUserName());
+        jsonObject.put(
+ com.google.gwt.sample.expenses.shared.Employee.DISPLAY_NAME.getName(),
+            e.getDisplayName());
+        jsonArray.put(jsonObject);
+      } catch (JSONException ex) {
+        System.err.println("Unable to create a JSON object " + ex);
+      }
+    }
+    writer.print(jsonArray.toString());
+  }
+
+  private void findReportsByEmployee(HttpServletRequest request,
+      PrintWriter writer) {
+    JSONArray jsonArray = new JSONArray();
+    Long id = Long.valueOf(request.getParameter("id"));
+    for (Report r : Report.findReportsByEmployee(id)) {
+      try {
+        // TODO should only be getting requested properties
+ // TODO clearly there should be centralized code for these conversions
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put(
+            com.google.gwt.sample.expenses.shared.Employee.ID.getName(),
+            Long.toString(r.getId()));
+        jsonObject.put(
+            com.google.gwt.sample.expenses.shared.Report.VERSION.getName(),
+            r.getVersion().intValue());
+        jsonObject.put(
+            com.google.gwt.sample.expenses.shared.Report.CREATED.getName(),
+            Double.valueOf(r.getCreated().getTime()));
+        jsonObject.put(
+            com.google.gwt.sample.expenses.shared.Report.PURPOSE.getName(),
+            r.getPurpose());
+        jsonArray.put(jsonObject);
+      } catch (JSONException ex) {
+        System.err.println("Unable to create a JSON object " + ex);
+      }
+    }
+    writer.print(jsonArray.toString());
+  }

   /**
    * @param request
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java Wed Mar 10 17:23:17 2010
@@ -20,19 +20,25 @@
 import com.google.gwt.valuestore.shared.Property;

 /**
- * The employee proxy object, would be auto-generated.
- *
+ * "Generated" proxy of {...@link com.google.gwt.sample.expenses.domain.Employee
+ * domain.Employee}.
  */
 public class Employee implements Entity<Employee> {
-
- public static final Property<Employee, String> DISPLAY_NAME = new Property<Employee, String>(
+
+ public static final Property<Employee, String> ID = new Property<Employee, String>(
+      Employee.class, String.class, "ID");
+
+ public static final Property<Employee, String> DISPLAY_NAME = new Property<Employee, String>(
       Employee.class, String.class, "DISPLAY_NAME");
public static final Property<Employee, Employee> SUPERVISOR = new Property<Employee, Employee>(
       Employee.class, Employee.class, "SUPERVISOR");

public static final Property<Employee, String> USER_NAME = new Property<Employee, String>(
       Employee.class, String.class, "USER_NAME");
-
+
+ public static final Property<Employee, Integer> VERSION = new Property<Employee, Integer>(
+      Employee.class, Integer.class, "VERSION");
+
   private final String id;
   private final Integer version;

@@ -40,7 +46,7 @@
     this.id = id;
     this.version = version;
   }
-
+
   public String getId() {
     return id;
   }
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java Mon Mar 8 13:39:42 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java Wed Mar 10 17:23:17 2010
@@ -21,7 +21,6 @@
 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.Entity;
 import com.google.gwt.requestfactory.shared.EntityListRequest;
 import com.google.gwt.sample.expenses.client.ValuesImpl;
 import com.google.gwt.user.client.ui.HasValueList;
@@ -30,11 +29,7 @@
 import com.google.gwt.valuestore.shared.Values;

 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;

 /**
  * "Generated" from static methods of
@@ -42,21 +37,11 @@
  */
 public class EmployeeRequests {

-  private final ValueStore values;
-
- private final Map<Object, Entity<?>> futures = new HashMap<Object, Entity<?>>();
-
-  /**
-   * @param dataService
-   * @param deltas
-   */
   public EmployeeRequests(ValueStore values) {
-    this.values = values;
   }

   public EntityListRequest<Employee> findAllEmployees() {
     return new EntityListRequest<Employee>() {
- Set<Property<Employee, ?>> properties = new HashSet<Property<Employee, ?>>();
       private HasValueList<Values<Employee>> watcher;

       public void fire() {
@@ -79,7 +64,10 @@
List<Values<Employee>> valueList = new ArrayList<Values<Employee>>(
                   valueArray.length());
               for (int i = 0; i < valueArray.length(); i++) {
-                valueList.add(valueArray.get(i));
+                ValuesImpl<Employee> values = valueArray.get(i);
+ values.setPropertyHolder(new Employee(values.get(Employee.ID),
+                    values.get(Employee.VERSION)));
+                valueList.add(values);
               }
               watcher.setValueList(valueList);
             } else {
@@ -97,15 +85,10 @@
         }

         // values.subscribe(watcher, future, properties);
-
- // TODO(rjrjr) now make the call, and in the callback replace the future
-        // with the real id. No no no, no need for the future thing.
-        // Just make the subscription in the callback.
       }

       public EntityListRequest<Employee> forProperty(
           Property<Employee, ?> property) {
-        properties.add(property);
         return this;
       }

=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java Wed Mar 10 17:23:17 2010
@@ -69,4 +69,12 @@
   public DeltaValueStore newDeltaStore() {
     return values.edit();
   }
-}
+
+  public ReportRequests reportRequest() {
+    return new ReportRequests(values);
+  }
+
+  public ReportRequests reportRequest(DeltaValueStore deltas) {
+    return new ReportRequests(deltas);
+  }
+}
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java Mon Mar 8 13:39:42 2010 +++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java Wed Mar 10 14:02:29 2010
@@ -19,6 +19,5 @@
  * Represents the MethodName.
  */
 public enum MethodName {
-  FIND_ALL_EMPLOYEES,
-  FIND_EMPLOYEE,
-}
+  FIND_ALL_EMPLOYEES, FIND_EMPLOYEE, FIND_REPORTS_BY_EMPLOYEE,
+}
=======================================
--- /trunk/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java Wed Mar 10 14:02:29 2010
@@ -57,6 +57,7 @@
         full.setPurpose("purpose");
         full.setReporter(tester.employee);
         full.setStatus(Status.Paid);
+        full.setCreated(new Date(1234567890));

         doFillAndVerify(report, full);

@@ -64,6 +65,7 @@
         assertEquals("purpose", report.getPurpose());
         assertSame(tester.employee, report.getReporter());
         assertEquals(Status.Paid, report.getStatus());
+        assertEquals(new Date(1234567890), report.getCreated());

         return null;
       }
=======================================
--- /trunk/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java Thu Mar 4 14:11:39 2010 +++ /trunk/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java Wed Mar 10 14:02:29 2010
@@ -17,22 +17,65 @@

 import junit.framework.TestCase;

+import java.util.List;
+
 /**
  * Eponymous unit test.
  */
 public class StorageTest extends TestCase {
   Storage store = new Storage();
-
-  public void testUserNameIndex() {
+
+  public void testReportsByEmployeeIndex() {
+    Storage s = new Storage();
+    Storage.fill(s);
+
+    Employee abc = s.findEmployeeByUserName("abc");
+    List<Report> reports = s.findReportsByEmployee(abc.getId());
+    assertEquals(3, reports.size());
+
+    Report report = new Report();
+    report.setReporter(abc);
+    report = s.persist(report);
+
+    reports = s.findReportsByEmployee(abc.getId());
+    assertEquals(4, reports.size());
+    Report latestReport = reports.get(3);
+    assertEquals(report.getId(), latestReport.getId());
+    assertEquals(report.getVersion(), latestReport.getVersion());
+  }
+
+  public void testFreshRelationships() {
     Storage s = new Storage();
     Storage.fill(s);
-
+
+    Employee abc = s.findEmployeeByUserName("abc");
+    List<Report> reports = s.findReportsByEmployee(abc.getId());
+    for (Report report : reports) {
+      assertEquals(abc.getVersion(), report.getReporter().getVersion());
+    }
+
+    abc.setDisplayName("Herbert");
+    s.persist(abc);
+    List<Report> fresherReports = s.findReportsByEmployee(abc.getId());
+    assertEquals(reports.size(), fresherReports.size());
+    Integer expectedVersion = abc.getVersion() + 1;
+    for (Report report : fresherReports) {
+      assertEquals(abc.getId(), report.getReporter().getId());
+      assertEquals(expectedVersion, report.getReporter().getVersion());
+      assertEquals("Herbert", report.getReporter().getDisplayName());
+    }
+  }
+
+  public void testUserNameIndex() {
+    Storage s = new Storage();
+    Storage.fill(s);
+
     Employee abc = s.findEmployeeByUserName("abc");
     assertEquals("Able B. Charlie", abc.getDisplayName());
-    abc = Storage.edit(abc);
+    abc = Storage.startSparseEdit(abc);
     abc.setUserName("xyz");
     abc = s.persist(abc);
-
+
     assertNull(s.findEmployeeByUserName("abc"));
     Employee xyz = s.findEmployeeByUserName("xyz");
     assertEquals("Able B. Charlie", xyz.getDisplayName());
@@ -45,39 +88,55 @@
     tester.run(new EntityVisitor<Boolean>() {

       public Boolean visit(Currency currency) {
-        doTestEdit(doTestNew(currency));
+        doTestSparseEdit(doTestNew(currency));
         return null;
       }

       public Boolean visit(Employee employee) {
-        doTestEdit(doTestNew(employee));
+        doTestSparseEdit(doTestNew(employee));
         return null;
       }

       public Boolean visit(Report report) {
-        doTestEdit(doTestNew(report));
+        doTestSparseEdit(doTestNew(report));
         return null;
       }

       public Boolean visit(ReportItem reportItem) {
-        doTestEdit(doTestNew(reportItem));
+        doTestFullEdit(doTestSparseEdit(doTestNew(reportItem)));
         return null;
       }
     });
   }

-  private void doTestEdit(Entity v1) {
-    Entity delta = Storage.edit(v1);
+  private void doTestFullEdit(Entity v1) {
+    v1 = store.get(v1);
+    Entity v2 = store.persist(v1);
+    assertEquals(v1.getId(), v2.getId());
+    assertEquals(Integer.valueOf(v1.getVersion() + 1), v2.getVersion());
+    Entity anotherV2 = store.get(v2);
+    assertNotSame(v2, anotherV2);
+    assertEquals(v1.getId(), anotherV2.getId());
+    assertEquals(v2.getVersion(), anotherV2.getVersion());
+  }
+
+  private Entity doTestSparseEdit(Entity v1) {
+    Entity delta = Storage.startSparseEdit(v1);
     Entity v2 = store.persist(delta);
+    assertEquals(v1.getId(), v2.getId());
     assertEquals(Integer.valueOf(v1.getVersion() + 1), v2.getVersion());
-    assertSame(v2, store.get(Storage.edit(v2)));
+    Entity anotherV2 = store.get(v2);
+    assertNotSame(v2, anotherV2);
+    assertEquals(v1.getId(), anotherV2.getId());
+    assertEquals(v2.getVersion(), anotherV2.getVersion());
+    return anotherV2;
   }

   private Entity doTestNew(Entity e) {
     Entity v1 = store.persist(e);
     assertEquals(Integer.valueOf(0), v1.getVersion());
     assertNotNull(v1.getId());
-    assertSame(v1, store.get(Storage.edit(v1)));
+    assertNotSame(v1, store.get(Storage.startSparseEdit(v1)));
     return v1;
   }
 }

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to