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: amitmanjhi@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7698 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/.classpath b/bikeshed/.classpath
index 3ea5207..4d52a95 100644
--- a/bikeshed/.classpath
+++ b/bikeshed/.classpath
@@ -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>
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java b/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java
index a727ed8..6ee491d 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/Slot.java
@@ -57,6 +57,10 @@
return true;
}
+ public E getEntity() {
+ return entity;
+ }
+
@Override
public int hashCode() {
final int prime = 31;
@@ -66,10 +70,6 @@
return result;
}
- E getEntity() {
- return entity;
- }
-
Property<E, V> getProperty() {
return property;
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeList.java b/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeList.java
new file mode 100644
index 0000000..e0d1021
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/EmployeeList.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java b/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java
index be3a7ba..a65ac23 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/Expenses.java
@@ -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();
- }
- };
-
- 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());
+ 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(employees).fire();
}
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java b/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java
index c83daac..4fd833a 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.java
@@ -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,33 +55,36 @@
@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);
@@ -94,17 +100,21 @@
throw new UnsupportedOperationException();
}
+ private <T> String renderDate(Values<T> values, Property<T, Date> property) {
+ return DateTimeFormat.getShortDateTimeFormat().format(values.get(property));
+ }
+
/**
* @param r
* @param tableRows
* @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));
}
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml
index 75e2294..8d9a6b0 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/Shell.ui.xml
@@ -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 (tbd)</th>
+ <th align='left'>Purpose</th>
</tr>
</table>
</g:HTMLPanel>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java b/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java
index aa11b97..24cf229 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/client/ValuesImpl.java
@@ -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[property.@com.google.gwt.valuestore.shared.Property::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) /*-{
+ date.@java.util.Date::init(D)(millis);
+ return date;
+ }-*/;
+
+ private native <V, P extends Property<T, V>> V nativeGet(P property) /*-{
+ return this[property.@com.google.gwt.valuestore.shared.Property::getName()()];
+ }-*/;
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java
index ad7a0fc..20f360b 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/CreationVisitor.java
@@ -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);
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java
index 91cc2a2..3288d53 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/NullFieldFiller.java
@@ -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());
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipRefreshingVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipRefreshingVisitor.java
new file mode 100644
index 0000000..146f8d5
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipRefreshingVisitor.java
@@ -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;
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipValidationVisitor.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipValidationVisitor.java
new file mode 100644
index 0000000..0b34d82
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/RelationshipValidationVisitor.java
@@ -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));
+ }
+ }
+
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java
index 7bc96af..28af160 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/Report.java
@@ -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
*/
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java
index 313faa2..09af993 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/ReportItem.java
@@ -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;
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
index 8aa5a9c..260c2a4 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
@@ -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
@@ -31,64 +35,168 @@
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 abc = new Employee();
+ abc.setUserName("abc");
+ abc.setDisplayName("Able B. Charlie");
+ abc = storage.persist(abc);
+ abc.setSupervisor(abc);
+ abc = storage.persist(abc);
- Employee e2 = new Employee();
- e2.setUserName("def");
- e2.setDisplayName("Delta E. Foxtrot");
- e2.setSupervisor(e);
- storage.persist(e2);
+ Employee def = new Employee();
+ def.setUserName("def");
+ def.setDisplayName("Delta E. Foxtrot");
+ def.setSupervisor(abc);
+ def = storage.persist(def);
- e2 = new Employee();
- e2.setUserName("ghi");
- e2.setDisplayName("George H. Indigo");
- e2.setSupervisor(e);
- storage.persist(e2);
+ 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 (getDepth == 0) {
+ freshForCurrentGet = new HashMap<Long, Entity>();
}
- if (!previous.getClass().equals(entity.getClass())) {
- throw new IllegalArgumentException(String.format(
- "Type mismatch, fetched %s for %s", entity, previous));
+ 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;
+ }
}
- return (E) previous;
}
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);
@@ -117,12 +225,24 @@
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;
}
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 f452fcc..f982b98 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
@@ -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,26 +45,74 @@
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
* @return
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java b/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java
index c2c24c4..063b081 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/shared/Employee.java
@@ -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;
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java b/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java
index fa2ab39..c5dced9 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/shared/EmployeeRequests.java
@@ -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;
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java b/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java
index 8760dbf..b28130c 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/shared/ExpenseRequestFactory.java
@@ -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);
+ }
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java b/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java
index ee4e809..c5fd431 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/shared/MethodName.java
@@ -19,6 +19,5 @@
* Represents the MethodName.
*/
public enum MethodName {
- FIND_ALL_EMPLOYEES,
- FIND_EMPLOYEE,
+ FIND_ALL_EMPLOYEES, FIND_EMPLOYEE, FIND_REPORTS_BY_EMPLOYEE,
}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/shared/Report.java b/bikeshed/src/com/google/gwt/sample/expenses/shared/Report.java
new file mode 100644
index 0000000..67bad6e
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/shared/Report.java
@@ -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);
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/shared/ReportRequests.java b/bikeshed/src/com/google/gwt/sample/expenses/shared/ReportRequests.java
new file mode 100644
index 0000000..e2cad7c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/shared/ReportRequests.java
@@ -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;
+ }
+ };
+ }
+}
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java b/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java
index d40cf64..f94bcfc 100644
--- a/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java
+++ b/bikeshed/test/com/google/gwt/sample/expenses/domain/NullFieldFillerTest.java
@@ -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;
}
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java b/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java
index ab744b7..3318062 100644
--- a/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java
+++ b/bikeshed/test/com/google/gwt/sample/expenses/domain/StorageTest.java
@@ -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 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);
- Entity v2 = store.persist(delta);
+ 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());
- 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());
+ }
+
+ 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;
}
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;
}
}