Goodbye fake storage, welcome JPA with appEngine. Notes:
1) Currently there are just 2 entities: Employee and Report. Any new entity must be added to the list in persistence.xml
2) Fixed findReportsByEmployee call.
3) A Report entity cannot  refer to an Employee entity -- it instead refers to
the Employee key due to current appEngine implementation
http://code.google.com/appengine/docs/java/datastore/relationships.html#Unowned_Relationships
4) Running the project requires GPE because the appEngine jars are not present in our tools.

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

Review by: rjrjr@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7929 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/eclipse.README b/bikeshed/eclipse.README
index 952072b..5425a63 100644
--- a/bikeshed/eclipse.README
+++ b/bikeshed/eclipse.README
@@ -22,6 +22,7 @@
 bikeshed/war/stocksmobile/
 bikeshed/war/tree/
 bikeshed/war/validation/
+appengine-generated/
 
 * Install the Google Plugin for Eclipse
 * Create a new Java project with trunk/bikeshed/ as its existing source
@@ -31,6 +32,7 @@
   * Google > App Engine > ORM
     * Remove src and test
     * Add server and shared from src/com/google/gwt/sample/bikeshed/stocks
+    * Add com/google/gwt/sample/expenses/server/domain
   * Java Build Path > Libraries > Add Variable > GWT_TOOLS, Extend > redist/json/r2_20080312/json.jar
 * Copy tools/redist/json/r2_20080312/json.jar to bikeshed/war/WEB_INF/lib
 * Right click on the bikeshed project and choose Run as > Web Application. Choose from the various .html files
diff --git a/bikeshed/src/META-INF/persistence.xml b/bikeshed/src/META-INF/persistence.xml
new file mode 100644
index 0000000..f4731b4
--- /dev/null
+++ b/bikeshed/src/META-INF/persistence.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
+        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
+
+    <persistence-unit name="transactions-optional">
+        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
+        <class>com.google.gwt.sample.expenses.server.domain.Employee</class>
+        <class>com.google.gwt.sample.expenses.server.domain.Report</class>
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+        <properties>
+            <property name="datanucleus.NontransactionalRead" value="true"/>
+            <property name="datanucleus.NontransactionalWrite" value="true"/>
+            <property name="datanucleus.ConnectionURL" value="appengine"/>
+        </properties>
+    </persistence-unit>
+
+</persistence>
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index 2d8d929..d65a0e1 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -77,6 +77,8 @@
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
       throws IOException {
 
+    initDb(); // temporary place-holder
+    
     RequestDefinition operation = null;
     try {
       response.setStatus(HttpServletResponse.SC_OK);
@@ -125,6 +127,13 @@
   }
 
   /**
+   * Allow subclass to initialize database.
+   */
+  @SuppressWarnings("unused")
+  protected void initDb() {
+  }
+  
+  /**
    * Allows subclass to provide hack implementation.
    * <p>
    * TODO real reflection based implementation.
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
index 955d748..0ea2229 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
@@ -16,7 +16,6 @@
 package com.google.gwt.sample.expenses.gwt.request;
 
 import com.google.gwt.requestfactory.shared.EntityListRequest;
-import com.google.gwt.requestfactory.shared.LongString;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.ServerOperation;
 import com.google.gwt.valuestore.shared.ValueRef;
@@ -37,7 +36,7 @@
       }
 
       public Class<?>[] getParameterTypes() {
-        return new Class[] { java.lang.Long.class };
+        return new Class[] {java.lang.String.class};
       }
 
       public Class<? extends ValuesKey<?>> getReturnType() {
@@ -69,7 +68,7 @@
    */
   @ServerOperation("FIND_REPORTS_BY_EMPLOYEE")
   EntityListRequest<ReportKey> findReportsByEmployee(
-      @LongString ValueRef<EmployeeKey, String> id);
+      ValueRef<EmployeeKey, String> id);
 
   /**
    * @return a request object
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java b/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
index cc6aa0b..7002766 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.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
@@ -17,14 +17,15 @@
 
 import com.google.gwt.requestfactory.server.RequestFactoryServlet;
 import com.google.gwt.sample.expenses.gwt.request.ReportKey;
+import com.google.gwt.sample.expenses.server.domain.Employee;
 import com.google.gwt.sample.expenses.server.domain.Report;
-import com.google.gwt.sample.expenses.server.domain.Storage;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.PrintWriter;
+import java.util.Date;
 
 /**
  * Dwindling interim servlet that calls our mock storage backend directly
@@ -33,6 +34,59 @@
 public class ExpensesDataServlet extends RequestFactoryServlet {
 
   @Override
+  protected void initDb() {
+    long size = Employee.countEmployees();
+    if (size > 1) {
+      return;
+    }
+    // initialize
+    Employee abc = new Employee();
+    abc.setUserName("abc");
+    abc.setDisplayName("Able B. Charlie");
+    abc.persist();
+
+    Employee def = new Employee();
+    def.setUserName("def");
+    def.setDisplayName("Delta E. Foxtrot");
+    def.setSupervisorKey(abc.getId());
+    def.persist();
+
+    Employee ghi = new Employee();
+    ghi.setUserName("ghi");
+    ghi.setDisplayName("George H. Indigo");
+    ghi.setSupervisorKey(abc.getId());
+    ghi.persist();
+
+    for (String purpose : new String[] {
+        "Spending lots of money", "Team building diamond cutting offsite",
+        "Visit to Istanbul"}) {
+      Report report = new Report();
+      report.setReporterKey(abc.getId());
+      report.setCreated(new Date());
+      report.setPurpose(purpose);
+      report.persist();
+    }
+
+    for (String purpose : new String[] {"Money laundering", "Donut day"}) {
+      Report report = new Report();
+      report.setCreated(new Date());
+      report.setReporterKey(def.getId());
+      report.setPurpose(purpose);
+      report.persist();
+    }
+
+    for (String purpose : new String[] {
+        "ISDN modem for telecommuting", "Sushi offsite",
+        "Baseball card research", "Potato chip cooking offsite"}) {
+      Report report = new Report();
+      report.setCreated(new Date());
+      report.setReporterKey(ghi.getId());
+      report.setPurpose(purpose);
+      report.persist();
+    }
+  }
+
+  @Override
   protected void sync(String content, PrintWriter writer) {
 
     try {
@@ -42,7 +96,7 @@
         JSONObject report = reportArray.getJSONObject(0);
         Report r = Report.findReport(report.getLong(ReportKey.get().getId().getName()));
         r.setPurpose(report.getString(ReportKey.get().getPurpose().getName()));
-        r = Storage.INSTANCE.persist(r);
+        r.persist();
         report.put(ReportKey.get().getVersion().getName(), r.getVersion());
         JSONArray returnArray = new JSONArray();
         // TODO: don't echo back everything.
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/CreationVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/CreationVisitor.java
deleted file mode 100644
index 9f15d15..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/CreationVisitor.java
+++ /dev/null
@@ -1,64 +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;
-
-/**
- * Creates a new entity of the type of the receiver.
- *
- * @param <E> The type of entity to create\
- */
-class CreationVisitor<E extends Entity> implements EntityVisitor<E> {
-  private final long id;
-  private final int version;
-
-  /**
-   * @param entity whose id and version will be copied. Used to create empty
-   *          delta.
-   */
-  public CreationVisitor(E entity) {
-    id = entity.getId();
-    version = entity.getVersion();
-  }
-
-  /**
-   * @param id of the new Entity
-   * @param i
-   */
-  public CreationVisitor(long id, int version) {
-    this.id = id;
-    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);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Currency.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Currency.java
deleted file mode 100644
index 295937c..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Currency.java
+++ /dev/null
@@ -1,88 +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;
-
-/**
- * Models a type of currency.
- */
-// @javax.persistence.Entity
-public class Currency implements Entity {
-//  @javax.validation.constraints.Size(min = 3, max = 3)
-  private String code;
-
-//  @javax.validation.constraints.Size(min = 2, max = 30)
-  private String name;
-
-  private final Long id;
-
-  private final Integer version;
-
-  public Currency() {
-    id = null;
-    version = null;
-  }
-
-  Currency(Long id, Integer version) {
-    this.id = id;
-    this.version = version;
-  }
-
-  public <T> T accept(EntityVisitor<T> visitor) {
-    return visitor.visit(this);
-  }
-
-  /**
-   * @return the code
-   */
-  public String getCode() {
-    return code;
-  }
-
-  /**
-   * @return the id
-   */
-  public Long getId() {
-    return id;
-  }
-
-  /**
-   * @return the name
-   */
-  public String getName() {
-    return name;
-  }
-
-  /**
-   * @return the version
-   */
-  public Integer getVersion() {
-    return version;
-  }
-
-  /**
-   * @param code the code to set
-   */
-  public void setCode(String code) {
-    this.code = code;
-  }
-
-  /**
-   * @param name the name to set
-   */
-  public void setName(String name) {
-    this.name = name;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Status.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/EMF.java
similarity index 61%
rename from bikeshed/src/com/google/gwt/sample/expenses/server/domain/Status.java
rename to bikeshed/src/com/google/gwt/sample/expenses/server/domain/EMF.java
index 1d974ce..96f97f3 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Status.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/EMF.java
@@ -15,14 +15,22 @@
  */
 package com.google.gwt.sample.expenses.server.domain;
 
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
 /**
- * Expense report life cycle states.
+ * Factory for creating EntityManager.
  */
-public enum Status {
-  Draft, // -> submitted
-  Submitted,  // -> approved_manager -> declined
-  Approved_Manager,  // -> approved_accounting
-  Approved_Accounting,  // -> paid -> declined
-  Paid,  // -> finish
-  Declined // -> draft
+public final class EMF {
+    private static final EntityManagerFactory emfInstance =
+        Persistence.createEntityManagerFactory("transactions-optional");
+
+    public static EntityManagerFactory get() {
+        return emfInstance;
+    }
+
+    private EMF() {
+      // nothing 
+    }
 }
+
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
index 7d60262..0be04aa 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
@@ -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,100 +15,169 @@
  */
 package com.google.gwt.sample.expenses.server.domain;
 
+import org.datanucleus.jpa.annotations.Extension;
+
 import java.util.List;
 
-// @javax.persistence.Entity
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Version;
+
 /**
  * The Employee domain object.
  */
-public class Employee implements Entity {
+@Entity
+public class Employee {
+
+  public static long countEmployees() {
+    EntityManager em = entityManager();
+    try {
+      return ((Integer) em.createQuery("select count(o) from Employee o").getSingleResult()).intValue();
+    } finally {
+      em.close();
+    }
+  }
+
+  public static final EntityManager entityManager() {
+    return EMF.get().createEntityManager();
+  }
+
+  @SuppressWarnings("unchecked")
   public static List<Employee> findAllEmployees() {
-    return Storage.INSTANCE.findAllEmployees();
+    EntityManager em = entityManager();
+    try {
+      List<Employee> list = em.createQuery("select o from Employee o").getResultList();
+      // force to get all the employees
+      list.size();
+      return list;
+    } finally {
+      em.close();
+    }
   }
 
   public static Employee findEmployee(Long id) {
-    return Storage.INSTANCE.findEmployee(id);
+    if (id == null) {
+      return null;
+    }
+    EntityManager em = entityManager();
+    try {
+      return em.find(Employee.class, id);
+    } finally {
+      em.close();
+    }
   }
 
-  public static Employee findEmployeeByUserName(String userName) {
-    return Storage.INSTANCE.findEmployeeByUserName(userName);
+  @SuppressWarnings("unchecked")
+  public static List<Employee> findEmployeeEntries(int firstResult,
+      int maxResults) {
+    EntityManager em = entityManager();
+    try {
+      return em.createQuery("select o from Employee o").setFirstResult(
+          firstResult).setMaxResults(maxResults).getResultList();
+    } finally {
+      em.close();
+    }
   }
 
-  private final Long id;
-
-  private final Integer version;
-
-//  @javax.validation.constraints.Size(min = 2, max = 30)
   private String userName;
 
-//  @javax.validation.constraints.Size(min = 2, max = 30)
   private String displayName;
 
-  // @javax.persistence.ManyToOne(targetEntity =
-  // com.google.io.expenses.server.domain.Employee.class)
-  // @javax.persistence.JoinColumn
-  private Employee supervisor;
+  private String password;
 
-  public Employee() {
-    this(null, null);
-  }
+  @JoinColumn
+  private String supervisorKey;
 
-  Employee(Long id, Integer version) {
-    this.id = id;
-    this.version = version;
-  }
+  @Id
+  @Column(name = "id")
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
+  private String id;
 
-  public <T> T accept(EntityVisitor<T> visitor) {
-    return visitor.visit(this);
-  }
+  @Version
+  @Column(name = "version")
+  private Long version;
 
   public String getDisplayName() {
-    return displayName;
+    return this.displayName;
   }
 
-  /**
-   * @return the id
-   */
-  public Long getId() {
-    return id;
+  public String getId() {
+    return this.id;
   }
 
-  /**
-   * @return the supervisor
-   */
-  public Employee getSupervisor() {
-    return supervisor;
+  public String getPassword() {
+    return this.password;
   }
 
-  /**
-   * @return the userName
-   */
+  public String getSupervisor() {
+    return supervisorKey;
+  }
+
   public String getUserName() {
-    return userName;
+    return this.userName;
   }
 
-  /**
-   * @return the version
-   */
-  public Integer getVersion() {
-    return version;
+  public Long getVersion() {
+    return this.version;
+  }
+
+  public void persist() {
+    EntityManager em = entityManager();
+    try {
+      em.persist(this);
+    } finally {
+      em.close();
+    }
+  }
+
+  public void remove() {
+    EntityManager em = entityManager();
+    try {
+      Employee attached = em.find(Employee.class, this.id);
+      em.remove(attached);
+    } finally {
+      em.close();
+    }
   }
 
   public void setDisplayName(String displayName) {
     this.displayName = displayName;
   }
 
-  /**
-   * @param supervisor the supervisor to set
-   */
-  public void setSupervisor(Employee supervisor) {
-    this.supervisor = supervisor;
+  public void setId(String id) {
+    this.id = id;
   }
 
-  /**
-   * @param userName the userName to set
-   */
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  public void setSupervisorKey(String supervisorKey) {
+    this.supervisorKey = supervisorKey;
+  }
+
   public void setUserName(String userName) {
     this.userName = userName;
   }
+
+  public void setVersion(Long version) {
+    this.version = version;
+  }
+
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Id: ").append(getId()).append(", ");
+    sb.append("Version: ").append(getVersion()).append(", ");
+    sb.append("UserName: ").append(getUserName()).append(", ");
+    sb.append("DisplayName: ").append(getDisplayName()).append(", ");
+    sb.append("Password: ").append(getPassword()).append(", ");
+    return sb.toString();
+  }
+
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Entity.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Entity.java
deleted file mode 100644
index 2d460a3..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Entity.java
+++ /dev/null
@@ -1,30 +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;
-
-/**
- * Pale imitation on the stuff JPA entities get baked into them.
- */
-interface Entity {
-  /**
-   * Double dispatch mechanism to ease imitation of framework mechanisms.
-   */
-  <T> T accept(EntityVisitor<T> visitor);
-
-  Long getId();
-
-  Integer getVersion();
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/EntityVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/EntityVisitor.java
deleted file mode 100644
index 63d6e70..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/EntityVisitor.java
+++ /dev/null
@@ -1,29 +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;
-
-/**
- * Double dispatch mechanism to ease imitation of framework mechanisms.
- */
-interface EntityVisitor<T> {
-  T visit(Currency currency);
-
-  T visit(Employee employee);
-
-  T visit(Report report);
-
-  T visit(ReportItem reportItem);
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/NullFieldFiller.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/NullFieldFiller.java
deleted file mode 100644
index eca4607..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/NullFieldFiller.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.server.domain;
-
-/**
- * Does the merging that a persistence framework would have done for us. If
- * persistence frameworks did this "null field means no change" kind of thing.
- * Which seems unlikely. But it was fun to write. D'oh.
- * <p>
- * Class cast exceptions thrown on merge type mismatch.
- */
-final class NullFieldFiller implements EntityVisitor<Void> {
-  private final Entity sparseEntity;
-
-  /**
-   * @param sparseEntity any null fields on this object will be filled from the
-   *          fields of the Entity that accepts this NullFieldFiller
-   */
-  NullFieldFiller(Entity sparseEntity) {
-    this.sparseEntity = sparseEntity;
-  }
-
-  public Void visit(Currency currency) {
-    Currency sparse = ((Currency) sparseEntity);
-    if (null == sparse.getCode()) {
-      sparse.setCode(currency.getCode());
-    }
-    if (null == sparse.getName()) {
-      sparse.setName(currency.getName());
-    }
-    return null;
-  }
-
-  public Void visit(Employee employee) {
-    Employee sparse = ((Employee) sparseEntity);
-    if (null == sparse.getUserName()) {
-      sparse.setUserName(employee.getUserName());
-    }
-    if (null == sparse.getSupervisor()) {
-      sparse.setSupervisor(employee.getSupervisor());
-    }
-    if (null == sparse.getDisplayName()) {
-      sparse.setDisplayName(employee.getDisplayName());
-    }
-    return null;
-  }
-
-  public Void visit(Report report) {
-    Report sparse = ((Report) sparseEntity);
-    if (sparse.getApprovedSupervisor() == null) {
-      sparse.setApprovedSupervisor(report.getApprovedSupervisor());
-    }
-    if (sparse.getCreated() == null) {
-      sparse.setCreated(report.getCreated());
-    }
-    if (sparse.getPurpose() == null) {
-      sparse.setPurpose(report.getPurpose());
-    }
-    if (sparse.getReporter() == null) {
-      sparse.setReporter(report.getReporter());
-    }
-    if (sparse.getStatus() == null) {
-      sparse.setStatus(report.getStatus());
-    }
-    return null;
-  }
-
-  public Void visit(ReportItem reportItem) {
-    ReportItem sparse = ((ReportItem) sparseEntity);
-    if (null == sparse.getAmount()) {
-      sparse.setAmount(reportItem.getAmount());
-    }
-    if (null == sparse.getCurrency()) {
-      sparse.setCurrency(reportItem.getCurrency());
-    }
-    if (null == sparse.getIncurred()) {
-      sparse.setIncurred(reportItem.getIncurred());
-    }
-    if (null == sparse.getPurpose()) {
-      sparse.setPurpose(reportItem.getPurpose());
-    }
-    if (null == sparse.getReport()) {
-      sparse.setReport(reportItem.getReport());
-    }
-    return null;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/RelationshipRefreshingVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/RelationshipRefreshingVisitor.java
deleted file mode 100644
index 2487b0c..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/RelationshipRefreshingVisitor.java
+++ /dev/null
@@ -1,48 +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;
-
-/**
- * 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;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/RelationshipValidationVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/RelationshipValidationVisitor.java
deleted file mode 100644
index 7cd8a55..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/RelationshipValidationVisitor.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.server.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));
-    }
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
index c7ceedc..229b3d5 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
@@ -15,149 +15,194 @@
  */
 package com.google.gwt.sample.expenses.server.domain;
 
+import org.datanucleus.jpa.annotations.Extension;
+
 import java.util.Date;
 import java.util.List;
 
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Query;
+import javax.persistence.Version;
+
 /**
  * Models an expense report.
  */
-// @javax.persistence.Entity
-public class Report implements Entity {
+@Entity
+public class Report {
 
+  public static long countReports() {
+    EntityManager em = entityManager();
+    try {
+      return ((Integer) em.createQuery("select count(o) from Report o").getSingleResult()).intValue();
+    } finally {
+      em.close();
+    }
+  }
+
+  public static final EntityManager entityManager() {
+    return EMF.get().createEntityManager();
+  }
+
+  @SuppressWarnings("unchecked")
   public static List<Report> findAllReports() {
-    return Storage.INSTANCE.findAllReports();
+    EntityManager em = entityManager();
+    try {
+      List<Report> reportList = em.createQuery("select o from Report o").getResultList();
+      // force it to materialize
+      reportList.size();
+      return reportList;
+    } finally {
+      em.close();
+    }
   }
 
   public static Report findReport(Long id) {
-    return Storage.INSTANCE.findReport(id);
+    if (id == null) {
+      return null;
+    }
+    EntityManager em = entityManager();
+    try {
+      return em.find(Report.class, id);
+    } finally {
+      em.close();
+    }
   }
 
-  public static List<Report> findReportsByEmployee(Long id) {
-    return Storage.INSTANCE.findReportsByEmployee(id);
+  @SuppressWarnings("unchecked")
+  public static List<Report> findReportEntries(int firstResult, int maxResults) {
+    EntityManager em = entityManager();
+    try {
+      List<Report> reportList = em.createQuery("select o from Report o").setFirstResult(
+          firstResult).setMaxResults(maxResults).getResultList();
+      // force it to materialize
+      reportList.size();
+      return reportList;
+    } finally {
+      em.close();
+    }
   }
-  private final Long id;
 
-private final Integer version;
+  public static List<Report> findReportsByEmployee(String employeeId) {
+    EntityManager em = entityManager();
+    try {
+      Query query = em.createQuery("select o from Report o where o.reporterKey =:reporterKey");
+      query.setParameter("reporterKey", employeeId);
+      List<Report> reportList = query.getResultList();
+      // force it to materialize
+      reportList.size();
+      return reportList;
+    } finally {
+      em.close();
+    }
+  }
 
-//  @javax.validation.constraints.NotNull
-//  @javax.validation.constraints.Past
-  // @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
-  private java.util.Date created;
+  @Id
+  @Column(name = "id")
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
+  private String id;
 
-//  @javax.validation.constraints.NotNull
-  // @javax.persistence.Enumerated
-  private Status status;
+  @Version
+  @Column(name = "version")
+  private Long version;
 
-//  @javax.validation.constraints.NotNull
-  // @javax.persistence.ManyToOne(targetEntity =
-  // com.google.io.expenses.server.domain.Employee.class)
-  // @javax.persistence.JoinColumn
-  private Employee reporter;
+  private Date created;
 
-  // @javax.persistence.OneToMany(cascade = javax.persistence.CascadeType.ALL,
-  // mappedBy = "report")
-  // private Set<ReportItem> items = new HashSet<ReportItem>();
-
-  //  @javax.validation.constraints.Size(min = 3, max = 100)
   private String purpose;
 
-  // @javax.persistence.ManyToOne(targetEntity =
-  // com.google.io.expenses.server.domain.Employee.class)
-  // @javax.persistence.JoinColumn
-  private Employee approvedSupervisor;
-
-  public Report() {
-    id = null;
-    version = null;
-  }
-
-  Report(Long id, Integer version) {
-    this.id = id;
-    this.version = version;
-  }
-
-  public <T> T accept(EntityVisitor<T> visitor) {
-    return visitor.visit(this);
-  }
-
   /**
-   * @return the approved_supervisor
+   * Store reporter's key instead of reporter because
+   * http://code.google.com/appengine
+   * /docs/java/datastore/relationships.html#Unowned_Relationships
    */
-  public Employee getApprovedSupervisor() {
-    return approvedSupervisor;
+  @JoinColumn
+  @Column(name = "reporter")
+  private String reporterKey;
+
+  @JoinColumn
+  private String approvedSupervisorKey;
+
+  public String getApprovedSupervisorKey() {
+    return approvedSupervisorKey;
   }
 
-  /**
-   * @return the created
-   */
-  public java.util.Date getCreated() {
-    return created;
+  public Date getCreated() {
+    return this.created;
   }
 
-  /**
-   * @return the id
-   */
-  public Long getId() {
-    return id;
+  public String getId() {
+    return this.id;
   }
 
-  /**
-   * @return the purpose
-   */
   public String getPurpose() {
-    return purpose;
+    return this.purpose;
   }
 
-  /**
-   * @return the reporter
-   */
-  public Employee getReporter() {
-    return reporter;
+  public String getReporterKey() {
+    return this.reporterKey;
   }
 
-  /**
-   * @return the status
-   */
-  public Status getStatus() {
-    return status;
+  public Long getVersion() {
+    return this.version;
   }
 
-  /**
-   * @return the version
-   */
-  public Integer getVersion() {
-    return version;
+  public void persist() {
+    EntityManager em = entityManager();
+    try {
+      em.persist(this);
+    } finally {
+      em.close();
+    }
   }
 
-  /**
-   * @param approvedSupervisor the approved_supervisor to set
-   */
-  public void setApprovedSupervisor(Employee approvedSupervisor) {
-    this.approvedSupervisor = approvedSupervisor;
+  public void remove() {
+    EntityManager em = entityManager();
+    try {
+      Report attached = em.find(Report.class, this.id);
+      em.remove(attached);
+    } finally {
+      em.close();
+    }
   }
 
-  public void setCreated(Date date) {
-    this.created = date;
+  public void setApprovedSupervisorKey(String approvedSupervisorKey) {
+    this.approvedSupervisorKey = approvedSupervisorKey;
   }
 
-  /**
-   * @param purpose the purpose to set
-   */
+  public void setCreated(Date created) {
+    this.created = created;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
   public void setPurpose(String purpose) {
     this.purpose = purpose;
   }
 
-  /**
-   * @param reporter the reporter to set
-   */
-  public void setReporter(Employee reporter) {
-    this.reporter = reporter;
+  public void setReporterKey(String reporter) {
+    this.reporterKey = reporter;
   }
 
-  /**
-   * @param status the status to set
-   */
-  public void setStatus(Status status) {
-    this.status = status;
+  public void setVersion(Long version) {
+    this.version = version;
+  }
+
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Id: ").append(getId()).append(", ");
+    sb.append("Version: ").append(getVersion()).append(", ");
+    sb.append("Created: ").append(getCreated()).append(", ");
+    sb.append("Purpose: ").append(getPurpose()).append(", ");
+    sb.append("Reporter: ").append(getReporterKey());
+    sb.append("ApprovedSupervisor: ").append(getApprovedSupervisorKey());
+    return sb.toString();
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/ReportItem.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/ReportItem.java
deleted file mode 100644
index a4df603..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/ReportItem.java
+++ /dev/null
@@ -1,148 +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 java.util.Date;
-
-/**
- * Models a line item in an expense report.
- */
-// @javax.persistence.Entity
-public class ReportItem implements Entity {
-  private Long id;
-
-  private Integer version;
-
-//  @javax.validation.constraints.NotNull
-  // @javax.persistence.ManyToOne(targetEntity = Report.class)
-  // @javax.persistence.JoinColumn
-  private Report report;
-
-//  @javax.validation.constraints.NotNull
-//  @javax.validation.constraints.Past
-  // @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
-  private Date incurred;
-
-//  @javax.validation.constraints.Size(min = 3, max = 100)
-  private String purpose;
-
-//  @javax.validation.constraints.NotNull
-  // @javax.persistence.ManyToOne(targetEntity = Currency.class)
-  // @javax.persistence.JoinColumn
-  private Currency currency;
-
-//  @javax.validation.constraints.NotNull
-//  @javax.validation.constraints.Min(0L)
-  private Float amount;
-
-  public ReportItem() {
-    id = null;
-    version = null;
-  }
-
-  ReportItem(Long id, Integer version) {
-    this.id = id;
-    this.version = version;
-  }
-
-  public <T> T accept(EntityVisitor<T> visitor) {
-    return visitor.visit(this);
-  }
-
-  /**
-   * @return the amount
-   */
-  public Float getAmount() {
-    return amount;
-  }
-
-  /**
-   * @return the currency
-   */
-  public Currency getCurrency() {
-    return currency;
-  }
-
-  /**
-   * @return the id
-   */
-  public Long getId() {
-    return id;
-  }
-
-  /**
-   * @return the incurred
-   */
-  public Date getIncurred() {
-    return incurred;
-  }
-
-  /**
-   * @return the purpose
-   */
-  public String getPurpose() {
-    return purpose;
-  }
-
-  /**
-   * @return the report
-   */
-  public Report getReport() {
-    return report;
-  }
-
-  /**
-   * @return the version
-   */
-  public Integer getVersion() {
-    return version;
-  }
-
-  /**
-   * @param amount the amount to set
-   */
-  public void setAmount(Float amount) {
-    this.amount = amount;
-  }
-
-  /**
-   * @param currency the currency to set
-   */
-  public void setCurrency(Currency currency) {
-    this.currency = currency;
-  }
-
-  /**
-   * @param incurred the incurred to set
-   */
-  public void setIncurred(java.util.Date incurred) {
-    this.incurred = incurred;
-  }
-
-  /**
-   * @param purpose the purpose to set
-   */
-  public void setPurpose(String purpose) {
-    this.purpose = purpose;
-  }
-
-  /**
-   * @param report the report to set
-   */
-  public void setReport(Report report) {
-    this.report = report;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Storage.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Storage.java
deleted file mode 100644
index cd69d13..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Storage.java
+++ /dev/null
@@ -1,317 +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 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
- * frameworks do. For goodness sake don't imitate this for production code.
- */
-public class Storage {
-  public static final Storage INSTANCE;
-  static {
-    INSTANCE = new Storage();
-    fill(INSTANCE);
-  }
-
-  /**
-   * @param storage to fill with demo entities
-   */
-  static void fill(Storage storage) {
-    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;
-
-  public synchronized <E extends Entity> E persist(final E delta) {
-    E next = null;
-    E previous = null;
-
-    if (delta.getId() == null) {
-      next = delta.accept(new CreationVisitor<E>(++serial, 0));
-      delta.accept(new NullFieldFiller(next));
-    } else {
-      previous = get(delta);
-      if (!previous.getVersion().equals(delta.getVersion())) {
-        throw new IllegalArgumentException(String.format(
-            "Version mismatch of %s. Cannot update %d from %d", delta,
-            previous.getVersion(), delta.getVersion()));
-      }
-
-      next = previous.accept(new CreationVisitor<E>(previous.getId(),
-          previous.getVersion() + 1));
-
-      NullFieldFiller filler = new NullFieldFiller(next);
-      // Copy the changed fields into the new version
-      delta.accept(filler);
-      // And copy the old fields into any null fields remaining on the new
-      // version
-      previous.accept(filler);
-    }
-
-    next.accept(new RelationshipValidationVisitor());
-
-    updateIndices(previous, next);
-    soup.put(next.getId(), next);
-    return get(next);
-  }
-
-  synchronized List<Employee> findAllEmployees() {
-    List<Employee> rtn = new ArrayList<Employee>();
-    for (Map.Entry<String, Long> entry : employeeUserNameIndex.entrySet()) {
-      rtn.add(get((Employee) rawGet(entry.getValue())));
-    }
-    return rtn;
-  }
-
-  synchronized List<Report> findAllReports() {
-    List<Report> rtn = new ArrayList<Report>();
-    for (Entity e : soup.values()) {
-      if (e instanceof Report) {
-        rtn.add(get((Report) e));
-      }
-    }
-    return rtn;
-  }
-
-  /**
-   * Returns Employee by id.
-   * @param id
-   * @return
-   */
-  synchronized Employee findEmployee(Long id) {
-    return get((Employee) rawGet(id));
-  }
-
-  synchronized Employee findEmployeeByUserName(String userName) {
-    Long id = employeeUserNameIndex.get(userName);
-    return findEmployee(id);
-  }
-
-  /**
-   * Returns report by id.
-   * @param id
-   * @return
-   */
-  synchronized Report findReport(Long id) {
-    return get((Report) 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) {
-    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;
-      }
-    }
-  }
-
-  /**
-   * @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);
-  }
-
-  private void updateIndices(final Entity previous, final Entity next) {
-    next.accept(new EntityVisitor<Void>() {
-      public Void visit(Currency currency) {
-        return null;
-      }
-
-      public Void visit(Employee employee) {
-        if (null == employee.getUserName()) {
-          return null;
-        }
-        if (previous != null) {
-          Employee prevEmployee = (Employee) previous;
-          if (!prevEmployee.getUserName().equals(next)) {
-            employeeUserNameIndex.remove(prevEmployee.getUserName());
-          }
-        }
-        employeeUserNameIndex.put(employee.getUserName(), employee.getId());
-        return null;
-      }
-
-      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;
-      }
-
-      public Void visit(ReportItem reportItem) {
-        return null;
-      }
-    });
-  }
-}
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/CreationVisitorTest.java b/bikeshed/test/com/google/gwt/sample/expenses/server/domain/CreationVisitorTest.java
deleted file mode 100644
index 197f202..0000000
--- a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/CreationVisitorTest.java
+++ /dev/null
@@ -1,57 +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 junit.framework.TestCase;
-
-/**
- * Eponymous test class.
- */
-public class CreationVisitorTest extends TestCase {
-  EntityTester tester = new EntityTester();
-
-  private void doCreationTest(Entity entity) {
-    Entity created = entity.accept(new CreationVisitor<Entity>(1, 2));
-    assertEquals(entity.getClass(), created.getClass());
-    assertEquals(Long.valueOf(1), created.getId());
-    assertEquals(Integer.valueOf(2), created.getVersion());
-  }
-
-  public void testSimple() {
-    tester.run(new EntityVisitor<Boolean>() {
-
-      public Boolean visit(Currency currency) {
-        doCreationTest(currency);
-        return null;
-      }
-
-      public Boolean visit(Employee employee) {
-        doCreationTest(employee);
-        return null;
-      }
-
-      public Boolean visit(Report report) {
-        doCreationTest(report);
-        return null;
-      }
-
-      public Boolean visit(ReportItem reportItem) {
-        doCreationTest(reportItem);
-        return null;
-      }
-    });
-  }
-}
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/EntityTester.java b/bikeshed/test/com/google/gwt/sample/expenses/server/domain/EntityTester.java
deleted file mode 100644
index b6c03cc..0000000
--- a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/EntityTester.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.server.domain;
-
-class EntityTester {
-  Currency currency;
-  Employee employee;
-  Report report;
-  ReportItem reportItem;
-
-  final Entity[] all;
-  
-  EntityTester() {
-    currency = new Currency();
-    employee = new Employee();
-    report = new Report();
-    reportItem = new ReportItem();
-
-    all = new Entity[] {currency, employee, report, reportItem};
-  }
-
-  void run(EntityVisitor<?> v) {
-    for (Entity e : all) {
-      e.accept(v);
-    }
-  }
-}
\ No newline at end of file
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/NullFieldFillerTest.java b/bikeshed/test/com/google/gwt/sample/expenses/server/domain/NullFieldFillerTest.java
deleted file mode 100644
index 0b94645..0000000
--- a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/NullFieldFillerTest.java
+++ /dev/null
@@ -1,103 +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 junit.framework.TestCase;
-
-import java.util.Date;
-
-/**
- * Eponymous test class.
- */
-public class NullFieldFillerTest extends TestCase {
-  public void testEmpty() {
-    final EntityTester tester = new EntityTester();
-    tester.run(new EntityVisitor<Void>() {
-
-      public Void visit(Currency currency) {
-        Currency full = new Currency(1L, 2);
-        full.setCode("USD");
-        full.setName("U.S. Dollars");
-        
-        doFillAndVerify(currency, full);
-
-        assertEquals("USD", currency.getCode());
-        assertEquals("U.S. Dollars", currency.getName());
-        return null;
-      }
-
-      public Void visit(Employee employee) {
-        Employee full = new Employee(1L, 2);
-        full.setUserName("ldap");
-        full.setDisplayName("Lawrence D. A. Pimrose");
-        
-        doFillAndVerify(employee, full);
-
-        assertEquals("ldap", employee.getUserName());
-        assertEquals("Lawrence D. A. Pimrose", employee.getDisplayName());
-        return null;
-      }
-
-      public Void visit(Report report) {
-        Report full = new Report(1L, 2);
-        full.setApprovedSupervisor(tester.employee);
-        full.setPurpose("purpose");
-        full.setReporter(tester.employee);
-        full.setStatus(Status.Paid);
-        full.setCreated(new Date(1234567890));
-        
-        doFillAndVerify(report, full);
-        
-        assertSame(tester.employee, report.getApprovedSupervisor());
-        assertEquals("purpose", report.getPurpose());
-        assertSame(tester.employee, report.getReporter());
-        assertEquals(Status.Paid, report.getStatus());
-        assertEquals(new Date(1234567890), report.getCreated());
-
-        return null;
-      }
-
-      public Void visit(ReportItem reportItem) {
-        ReportItem full = new ReportItem(1L, 2);
-        full.setAmount(123.45f);
-        full.setCurrency(tester.currency);
-        full.setIncurred(new Date(1234567890));
-        full.setPurpose("purpose");
-        full.setReport(tester.report);
-        
-        doFillAndVerify(reportItem, full);
-        
-        assertEquals(123.45f, reportItem.getAmount());
-        assertEquals(tester.currency, reportItem.getCurrency());
-        assertEquals(new Date(1234567890), reportItem.getIncurred());
-        assertEquals("purpose", reportItem.getPurpose());
-        assertEquals(tester.report, reportItem.getReport());
-
-        return null;
-      }
-    });
-  }
-  
-  private void assertNullEntityTag(Entity employee) {
-    assertNull(employee.getId());
-    assertNull(employee.getVersion());
-  }
-
-  private void doFillAndVerify(Entity sparse, Entity full) {
-    full.accept(new NullFieldFiller(sparse));
-    assertNullEntityTag(sparse);
-  }
-}
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/StorageTest.java b/bikeshed/test/com/google/gwt/sample/expenses/server/domain/StorageTest.java
deleted file mode 100644
index 7d97cc4..0000000
--- a/bikeshed/test/com/google/gwt/sample/expenses/server/domain/StorageTest.java
+++ /dev/null
@@ -1,142 +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 junit.framework.TestCase;
-
-import java.util.List;
-
-/**
- * Eponymous unit test.
- */
-public class StorageTest extends TestCase {
-  Storage store = new Storage();
-
-  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 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 testUserNameIndex() {
-    Storage s = new Storage();
-    Storage.fill(s);
-
-    Employee abc = s.findEmployeeByUserName("abc");
-    assertEquals("Able B. Charlie", abc.getDisplayName());
-    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());
-    assertEquals(abc.getVersion(), xyz.getVersion());
-  }
-
-  public void testVersioning() {
-    final EntityTester tester = new EntityTester();
-
-    tester.run(new EntityVisitor<Boolean>() {
-
-      public Boolean visit(Currency currency) {
-        doTestSparseEdit(doTestNew(currency));
-        return null;
-      }
-
-      public Boolean visit(Employee employee) {
-        doTestSparseEdit(doTestNew(employee));
-        return null;
-      }
-
-      public Boolean visit(Report report) {
-        doTestSparseEdit(doTestNew(report));
-        return null;
-      }
-
-      public Boolean visit(ReportItem reportItem) {
-        doTestFullEdit(doTestSparseEdit(doTestNew(reportItem)));
-        return null;
-      }
-    });
-  }
-
-  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 doTestNew(Entity e) {
-    Entity v1 = store.persist(e);
-    assertEquals(Integer.valueOf(0), v1.getVersion());
-    assertNotNull(v1.getId());
-    assertNotSame(v1, store.get(Storage.startSparseEdit(v1)));
-    return v1;
-  }
-
-  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());
-    Entity anotherV2 = store.get(v2);
-    assertNotSame(v2, anotherV2);
-    assertEquals(v1.getId(), anotherV2.getId());
-    assertEquals(v2.getVersion(), anotherV2.getVersion());
-    return anotherV2;
-  }
-}