Adds partial support for related fields to RequestFactory. Right now, getters of such fields always return null. Review at http://gwt-code-reviews.appspot.com/741801 Review by: rjrjr@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8541 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java index b4b78fc..14fed57 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java
@@ -23,12 +23,14 @@ import com.google.gwt.requestfactory.shared.Receiver; import com.google.gwt.requestfactory.shared.RequestEvent; import com.google.gwt.requestfactory.shared.UserInformationRecord; +import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord; import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.valuestore.shared.SyncResult; +import com.google.gwt.valuestore.shared.Value; import java.util.Set; @@ -44,7 +46,7 @@ /** * TODO(jgw): Put this some place more sensible. - * + * * @param amount the amount in dollars */ public static String formatCurrency(double amount) { @@ -89,24 +91,36 @@ } final HandlerManager eventBus = new HandlerManager(null); - final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class); + final ExpensesRequestFactory requestFactory = GWT.create( + ExpensesRequestFactory.class); requestFactory.init(eventBus); - final ExpensesMobileShell shell = new ExpensesMobileShell(eventBus, - requestFactory, employeeId); - RootPanel.get().add(shell); - - // Check for Authentication failures or mismatches - eventBus.addHandler(RequestEvent.TYPE, new AuthenticationFailureHandler()); + final long finalEmployeeId = employeeId; + requestFactory.employeeRequest().findEmployee(Value.of(employeeId)).fire( + new Receiver<EmployeeRecord>() { + @Override + public void onSuccess(EmployeeRecord employee, + Set<SyncResult> syncResults) { + final ExpensesMobileShell shell = new ExpensesMobileShell(eventBus, + requestFactory, employee); + RootPanel.get().add(shell); - // Add a login widget to the page - final LoginWidget login = shell.getLoginWidget(); - Receiver<UserInformationRecord> receiver = new Receiver<UserInformationRecord>() { - public void onSuccess(UserInformationRecord userInformationRecord, Set<SyncResult> syncResults) { - login.setUserInformation(userInformationRecord); - } - }; - requestFactory.userInformationRequest().getCurrentUserInformation( - Location.getHref()).fire(receiver); + // Check for Authentication failures or mismatches + eventBus.addHandler(RequestEvent.TYPE, + new AuthenticationFailureHandler()); + + // Add a login widget to the page + final LoginWidget login = shell.getLoginWidget(); + Receiver<UserInformationRecord> receiver + = new Receiver<UserInformationRecord>() { + public void onSuccess(UserInformationRecord userInformationRecord, + Set<SyncResult> syncResults) { + login.setUserInformation(userInformationRecord); + } + }; + requestFactory.userInformationRequest().getCurrentUserInformation( + Location.getHref()).fire(receiver); + } + } ); } }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java index b4d0ac6..d540149 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java
@@ -20,6 +20,7 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.requestfactory.client.LoginWidget; +import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord; import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord; import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory; import com.google.gwt.sample.expenses.gwt.request.ReportRecord; @@ -52,16 +53,16 @@ private MobileExpenseEntry expenseEntry; private MobileReportEntry reportEntry; - private final long employeeId; + private final EmployeeRecord employee; private final HandlerManager eventBus; private final ExpensesRequestFactory requestFactory; private ArrayList<MobilePage> pages = new ArrayList<MobilePage>(); public ExpensesMobileShell(HandlerManager eventBus, - ExpensesRequestFactory requestFactory, long employeeId) { + ExpensesRequestFactory requestFactory, EmployeeRecord employee) { this.eventBus = eventBus; this.requestFactory = requestFactory; - this.employeeId = employeeId; + this.employee = employee; initWidget(BINDER.createAndBindUi(this)); showReportList(); @@ -151,11 +152,11 @@ pushPage(reportEntry); } - private void showExpenseList(ReportRecord report) { + private void showExpenseList(final ReportRecord report) { if (expenseList == null) { expenseList = new MobileExpenseList(new MobileExpenseList.Listener() { - public void onCreateExpense(Long reportId) { - showNewExpenseEntry(reportId); + public void onCreateExpense(ReportRecord report) { + showNewExpenseEntry(report); } public void onEditReport(ReportRecord report) { @@ -172,7 +173,7 @@ pushPage(expenseList); } - private void showNewExpenseEntry(Long reportId) { + private void showNewExpenseEntry(ReportRecord report) { if (expenseEntry == null) { expenseEntry = new MobileExpenseEntry(new MobileExpenseEntry.Listener() { public void onExpenseUpdated() { @@ -181,11 +182,11 @@ }, requestFactory); } - expenseEntry.create(reportId); + expenseEntry.create(report); pushPage(expenseEntry); } - private void showNewReportEntry(Long reporterId) { + private void showNewReportEntry(EmployeeRecord reporter) { if (reportEntry == null) { reportEntry = new MobileReportEntry(new MobileReportEntry.Listener() { public void onReportUpdated() { @@ -194,7 +195,7 @@ }, requestFactory); } - reportEntry.create(reporterId); + reportEntry.create(reporter); pushPage(reportEntry); } @@ -223,14 +224,14 @@ private void showReportList() { if (reportList == null) { reportList = new MobileReportList(new MobileReportList.Listener() { - public void onCreateReport(Long reporterId) { - showNewReportEntry(reporterId); + public void onCreateReport(EmployeeRecord reporter) { + showNewReportEntry(reporter); } public void onReportSelected(ReportRecord report) { showExpenseList(report); } - }, requestFactory, employeeId); + }, requestFactory, employee); } pushPage(reportList);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java index 4c6f3b5..7cd556b 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseEntry.java
@@ -21,6 +21,7 @@ import com.google.gwt.requestfactory.shared.RequestObject; import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord; import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory; +import com.google.gwt.sample.expenses.gwt.request.ReportRecord; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; @@ -72,11 +73,11 @@ return this; } - public void create(Long reportId) { + public void create(ReportRecord report) { expense = (ExpenseRecord) requestFactory.create(ExpenseRecord.class); requestObject = requestFactory.expenseRequest().persist(expense); ExpenseRecord editableExpense = requestObject.edit(expense); - editableExpense.setReportId(reportId); + editableExpense.setReport(report); displayExpense(); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java index d372a55..fb9a08c 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
@@ -51,7 +51,7 @@ * TODO: doc. */ public interface Listener { - void onCreateExpense(Long reportId); + void onCreateExpense(ReportRecord report); void onEditReport(ReportRecord report); @@ -169,7 +169,7 @@ } public void onAdd() { - listener.onCreateExpense(report.getId()); + listener.onCreateExpense(report); } public void onCustom() {
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java index a08461d..5791343 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportEntry.java
@@ -19,6 +19,7 @@ import com.google.gwt.dom.client.Element; import com.google.gwt.requestfactory.shared.Receiver; import com.google.gwt.requestfactory.shared.RequestObject; +import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord; import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory; import com.google.gwt.sample.expenses.gwt.request.ReportRecord; import com.google.gwt.uibinder.client.UiBinder; @@ -81,11 +82,11 @@ return this; } - public void create(Long reporterId) { + public void create(EmployeeRecord reporter) { report = (ReportRecord) requestFactory.create(ReportRecord.class); requestObject = requestFactory.reportRequest().persist(report); ReportRecord editableReport = requestObject.edit(report); - report.setReporterKey(reporterId); + editableReport.setReporter(reporter); displayReport(); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java index be83685..5cda91f 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
@@ -17,6 +17,7 @@ import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.requestfactory.shared.Receiver; +import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord; import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory; import com.google.gwt.sample.expenses.gwt.request.ReportRecord; import com.google.gwt.user.cellview.client.CellList; @@ -43,7 +44,7 @@ * TODO: doc. */ public interface Listener { - void onCreateReport(Long reporterId); + void onCreateReport(EmployeeRecord reporter); void onReportSelected(ReportRecord report); } @@ -53,7 +54,7 @@ */ private Receiver<List<ReportRecord>> lastReceiver; - private final Long employeeId; + private final EmployeeRecord employee; private final Listener listener; private final CellList<ReportRecord> reportList; private final AsyncListViewAdapter<ReportRecord> reportAdapter; @@ -61,10 +62,10 @@ private final ExpensesRequestFactory requestFactory; public MobileReportList(final Listener listener, - final ExpensesRequestFactory requestFactory, long employeeId) { + final ExpensesRequestFactory requestFactory, EmployeeRecord employee) { this.listener = listener; this.requestFactory = requestFactory; - this.employeeId = new Long(employeeId); + this.employee = employee; reportAdapter = new AsyncListViewAdapter<ReportRecord>() { @Override protected void onRangeChanged(HasData<ReportRecord> view) { @@ -116,7 +117,7 @@ } public void onAdd() { - listener.onCreateReport(employeeId); + listener.onCreateReport(employee); } public void onCustom() { @@ -147,7 +148,7 @@ reportAdapter.updateViewData(0, size, newValues); } }; - requestFactory.reportRequest().findReportEntriesBySearch(employeeId, "", + requestFactory.reportRequest().findReportEntriesBySearch(employee.getId(), "", "", ReportRecord.created.getName() + " DESC", 0, 25).forProperties( getReportColumns()).fire(lastReceiver); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java index 21ac793..ac47217 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
@@ -32,8 +32,8 @@ Property<String> displayName = new Property<String>("displayName", "Display Name", String.class); Property<String> password = new Property<String>("password", "Password", String.class); - Property<Long> supervisorKey = new Property<Long>("supervisorKey", "Supervisor Key", - Long.class); + Property<EmployeeRecord> supervisor = new Property<EmployeeRecord>("supervisor", "Supervisor", + EmployeeRecord.class); Property<String> department = new Property<String>("department", "Department", String.class); @@ -43,7 +43,7 @@ String getPassword(); - Long getSupervisorKey(); + EmployeeRecord getSupervisor(); String getUserName(); @@ -55,7 +55,7 @@ void setPassword(String password); - void setSupervisorKey(Long supervisorKey); + void setSupervisor(EmployeeRecord supervisor); void setUserName(String userName); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java index 268c569..8b9a154 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
@@ -36,7 +36,7 @@ Property<Date> created = new Property<Date>("created", "Created", Date.class); Property<String> description = new Property<String>("description", "Description", String.class); Property<String> reasonDenied = new Property<String>("reasonDenied", "Reason Denied", String.class); - Property<Long> reportId = new Property<Long>("reportId", "Report Id", Long.class); + Property<ReportRecord> report = new Property<ReportRecord>("report", "Report", ReportRecord.class); Double getAmount(); @@ -50,7 +50,7 @@ String getReasonDenied(); - Long getReportId(); + ReportRecord getReport(); boolean isChanged(); @@ -66,5 +66,5 @@ void setReasonDenied(String reasonDenied); - void setReportId(Long reportId); + void setReport(ReportRecord report); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java index d98c4b9..7652744 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
@@ -30,16 +30,16 @@ @DataTransferObject(com.google.gwt.sample.expenses.server.domain.Report.class) public interface ReportRecord extends Record { - Property<Long> approvedSupervisorKey = new Property<Long>("approvedSupervisorKey", "Approved Supervisor Key", - Long.class); + Property<EmployeeRecord> approvedSupervisor = new Property<EmployeeRecord>("approvedSupervisor", "Approved Supervisor Key", + EmployeeRecord.class); Property<Date> created = new Property<Date>("created", "Created", Date.class); Property<String> department = new Property<String>("department", "Department", String.class); Property<String> notes = new Property<String>("notes", "Notes", String.class); Property<String> purpose = new Property<String>("purpose", "Purpose", String.class); - Property<Long> reporterKey = new Property<Long>("reporterKey", "Reporter Key", - Long.class); + Property<EmployeeRecord> reporter = new Property<EmployeeRecord>("reporter", "Reporter", + EmployeeRecord.class); - Long getApprovedSupervisorKey(); + EmployeeRecord getApprovedSupervisor(); Date getCreated(); @@ -49,11 +49,11 @@ String getPurpose(); - Long getReporterKey(); + EmployeeRecord getReporter(); boolean isChanged(); - void setApprovedSupervisorKey(Long approvedSupervisorKey); + void setApprovedSupervisor(EmployeeRecord approvedSupervisor); void setCreated(Date created); @@ -63,5 +63,5 @@ void setPurpose(String purpose); - void setReporterKey(Long reporterKey); + void setReporter(EmployeeRecord reporter); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java index 6aa0395..776f790 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/server/ReportGeneratorMain.java
@@ -55,8 +55,8 @@ repWriter = new PrintWriter(dir + "/Report.csv"); expWriter = new PrintWriter(dir + "/Expense.csv"); empWriter.println("userName,displayName,supervisorKey,VERSION,key,department,password"); - repWriter.println("created,notes,VERSION,approvedSupervisorKey,key,reporterKey,purposeLowerCase,purpose,department"); - expWriter.println("category,description,reasonDenied,amount,VERSION,reportId,key,created,approval"); + repWriter.println("created,notes,VERSION,approvedSupervisor,key,reporter,purposeLowerCase,purpose,department"); + expWriter.println("category,description,reasonDenied,amount,VERSION,report,key,created,approval"); } catch (IOException e) { throw new RuntimeException(e); } @@ -95,7 +95,7 @@ @Override public long storeExpense(ExpenseDTO expense) { long id = allids++; - // category,description,reasonDenied,amount,VERSION,reportId,key,created,approval" + // category,description,reasonDenied,amount,VERSION,report,key,created,approval" expWriter.println(expense.category + "," + expense.description + ",," + expense.amount + "," + VERSION + "," + expense.reportId + "," + id + "," + dateToString(expense.created) + ","); @@ -105,7 +105,7 @@ @Override public long storeReport(ReportDTO report) { long id = allids++; - // created,notes,VERSION,approvedSupervisorKey,key,reporterKey,purposeLowerCase,purpose,department + // created,notes,VERSION,approvedSupervisor,key,reporter,purposeLowerCase,purpose,department repWriter.println(dateToString(report.created) + ",\"" + report.notes + "\"," + VERSION + "," + report.approvedSupervisorKey + "," + id + "," + report.reporterKey + ",\"" + report.purpose.toLowerCase() + "\",\"" + report.purpose + "\"," + report.department);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java index 6cb007c..1b73e9f 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
@@ -120,6 +120,6 @@ } }; - requests.employeeRequest().findEmployee(Value.of(id)).fire(callback); + requests.employeeRequest().findEmployee(Value.of(id)).with("supervisor").fire(callback); } }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java index a71f8ed..6bc8acb 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
@@ -52,7 +52,7 @@ @UiField SpanElement password; @UiField - SpanElement supervisorKey; + SpanElement supervisor; @UiField HasClickHandlers edit; @UiField @@ -95,7 +95,7 @@ displayName.setInnerText(record.getDisplayName()); userName.setInnerText(record.getUserName()); password.setInnerText(record.getPassword()); - supervisorKey.setInnerText(String.valueOf(record.getSupervisorKey())); + supervisor.setInnerText(String.valueOf(record.getSupervisor() == null ? null : record.getSupervisor().getId())); idSpan.setInnerText(record.getId().toString()); versionSpan.setInnerText(record.getVersion().toString()); }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml index bc70a28..2f473d9 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml
@@ -35,7 +35,7 @@ <table class='{style.fields}'> <tr><td><div class='{style.label}'>Display Name:</div></td><td><span ui:field='displayName'></span></td></tr> <tr><td><div class='{style.label}'>User Name:</div></td><td><span ui:field='userName'></span></td></tr> - <tr><td><div class='{style.label}'>Supervisor Key:</div></td><td><span ui:field='supervisorKey'></span></td></tr> + <tr><td><div class='{style.label}'>Supervisor:</div></td><td><span ui:field='supervisor'></span></td></tr> <tr><td><div class='{style.label}'>Password:</div></td><td><span ui:field='password'></span></td></tr> </table>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java index f50fe57..e481300 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
@@ -15,7 +15,7 @@ */ package com.google.gwt.sample.expenses.gwt.ui.employee; -import com.google.gwt.app.client.LongRenderer; +import com.google.gwt.app.client.ProxyIdRenderer; import com.google.gwt.app.place.AbstractRecordListView; import com.google.gwt.app.place.PropertyColumn; import com.google.gwt.core.client.GWT; @@ -53,8 +53,8 @@ columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.userName)); columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.displayName)); columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.password)); - columns.add(new PropertyColumn<EmployeeRecord, java.lang.Long>( - EmployeeRecord.supervisorKey, LongRenderer.instance())); + columns.add(new PropertyColumn<EmployeeRecord, EmployeeRecord>( + EmployeeRecord.supervisor, ProxyIdRenderer.<EmployeeRecord>instance())); return columns; } }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java index a7c1163..d7c7770 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
@@ -119,6 +119,7 @@ display.showActivityWidget(view); } }; - requests.reportRequest().findReport(Value.of(id)).fire(callback); + requests.reportRequest().findReport(Value.of(id)).with( + "reporter", "approvedSupervisor").fire(callback); } }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java index 11190e9..7c2fed6 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
@@ -99,7 +99,7 @@ created.setInnerText(new DateTimeFormatRenderer().render(record.getCreated())); idSpan.setInnerText(record.getId().toString()); versionSpan.setInnerText(record.getVersion().toString()); - reporterKey.setInnerText(String.valueOf(record.getReporterKey())); - approvedSupervisorKey.setInnerText(String.valueOf(record.getApprovedSupervisorKey())); + reporterKey.setInnerText(String.valueOf(record.getReporter())); + approvedSupervisorKey.setInnerText(String.valueOf(record.getApprovedSupervisor())); } }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java index 559886c..0c4c0ac 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
@@ -15,12 +15,13 @@ */ package com.google.gwt.sample.expenses.gwt.ui.report; -import com.google.gwt.app.client.LongRenderer; +import com.google.gwt.app.client.ProxyIdRenderer; import com.google.gwt.app.place.AbstractRecordListView; import com.google.gwt.app.place.PropertyColumn; import com.google.gwt.core.client.GWT; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.DateTimeFormatRenderer; +import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord; import com.google.gwt.sample.expenses.gwt.request.ReportRecord; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -58,10 +59,10 @@ columns.add(new PropertyColumn<ReportRecord, Date>(ReportRecord.created, new DateTimeFormatRenderer(DateTimeFormat.getShortDateFormat()))); columns.add(PropertyColumn.<ReportRecord> getStringPropertyColumn(ReportRecord.purpose)); - columns.add(new PropertyColumn<ReportRecord, java.lang.Long>( - ReportRecord.reporterKey, LongRenderer.instance())); - columns.add(new PropertyColumn<ReportRecord, java.lang.Long>( - ReportRecord.approvedSupervisorKey, LongRenderer.instance())); + columns.add(new PropertyColumn<ReportRecord, EmployeeRecord>( + ReportRecord.reporter, ProxyIdRenderer.<EmployeeRecord>instance())); + columns.add(new PropertyColumn<ReportRecord, EmployeeRecord>( + ReportRecord.approvedSupervisor, ProxyIdRenderer.<EmployeeRecord>instance())); return columns; } }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java b/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java index fdf218c..ef2fde3 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/server/DataGenerator.java
@@ -206,7 +206,7 @@ private JSONObject getExpenseAsJson(Expense expense) throws JSONException { JSONObject jsonObject = new JSONObject(); jsonObject.put("id", "" + expense.getId()); - jsonObject.put("reportId", expense.getReportId()); + jsonObject.put("report", expense.getReportId()); jsonObject.put("description", expense.getDescription()); jsonObject.put("created", expense.getCreated().getTime()); jsonObject.put("amount", expense.getAmount()); @@ -239,12 +239,12 @@ jsonObject.put("id", "" + report.getId()); jsonObject.put("created", report.getCreated().getTime()); jsonObject.put("department", report.getDepartment()); - jsonObject.put("reporterKey", report.getReporterKey()); + jsonObject.put("reporter", report.getReporterKey()); jsonObject.put("purpose", report.getPurpose()); jsonObject.put("notes", report.getNotes()); Long approvedSupervisorKey = report.getApprovedSupervisorKey(); if (approvedSupervisorKey != null) { - jsonObject.put("approvedSupervisorKey", "" + report.getApprovedSupervisorKey()); + jsonObject.put("approvedSupervisor", "" + report.getApprovedSupervisorKey()); } return jsonObject; }
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 28b2cdf..c7802b0 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
@@ -24,6 +24,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Query; +import javax.persistence.Transient; import javax.persistence.Version; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -138,6 +139,9 @@ @Column(name = "version") private Integer version; + @Transient + private Employee supervisor; + public String getDepartment() { return department; } @@ -154,6 +158,10 @@ return this.password; } + public Employee getSupervisor() { + return supervisorKey != null ? findEmployee(supervisorKey) : null; + } + public Long getSupervisorKey() { return supervisorKey; } @@ -201,6 +209,10 @@ this.password = password; } + public void setSupervisor(Employee supervisor) { + supervisorKey = supervisor == null ? null : supervisor.getId(); + } + public void setSupervisorKey(Long supervisorKey) { this.supervisorKey = supervisorKey; }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java index d0034a0..4ca3206 100644 --- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java +++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java
@@ -26,6 +26,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Query; +import javax.persistence.Transient; import javax.persistence.Version; /** @@ -59,7 +60,7 @@ em.close(); } } - + public static Expense findExpense(Long id) { if (id == null) { return null; @@ -71,7 +72,7 @@ em.close(); } } - + @SuppressWarnings("unchecked") public static List<Expense> findExpensesByReport(Long reportId) { EntityManager em = entityManager(); @@ -98,17 +99,21 @@ private String category; private Date created; - + private String description; - + @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - + private String reasonDenied; - + + @Transient + private Report report; + // @JoinColumn + private Long reportId; @Version @@ -122,7 +127,7 @@ public String getApproval() { return this.approval; } - + public String getCategory() { return this.category; } @@ -134,15 +139,19 @@ public String getDescription() { return description; } - + public Long getId() { return this.id; } - + public String getReasonDenied() { return this.reasonDenied; } + public Report getReporter() { + return reportId != null ? Report.findReport(reportId) : null; + } + public Long getReportId() { return this.reportId; } @@ -169,11 +178,11 @@ em.close(); } } - + public void setAmount(Double amount) { this.amount = amount; } - + public void setApproval(String approval) { this.approval = approval; } @@ -198,8 +207,12 @@ this.reasonDenied = reasonDenied; } + public void setReport(Report report) { + reportId = report == null ? null : report.getId(); + } + public void setReportId(Long reportId) { - this.reportId = reportId; + this.reportId = reportId; } public void setVersion(Integer version) {
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 7d7378d..ac574e1 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
@@ -36,6 +36,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Query; +import javax.persistence.Transient; import javax.persistence.Version; /** @@ -66,8 +67,8 @@ String startsWith) { EntityManager em = entityManager(); try { - Query query = queryReportsBySearch(em, employeeId, department, - startsWith, null, true); + Query query = queryReportsBySearch(em, employeeId, department, startsWith, + null, true); if (query == null) { return REPORT_COUNT; } @@ -108,11 +109,13 @@ } @SuppressWarnings("unchecked") - public static List<Report> findReportEntries(int firstResult, int maxResults) { + 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(); + List<Report> reportList = em.createQuery( + "select o from Report o").setFirstResult(firstResult).setMaxResults( + maxResults).getResultList(); // force it to materialize reportList.size(); return reportList; @@ -127,8 +130,8 @@ int maxResults) { EntityManager em = entityManager(); try { - Query query = queryReportsBySearch(em, employeeId, department, - startsWith, orderBy, false); + Query query = queryReportsBySearch(em, employeeId, department, startsWith, + orderBy, false); // Try to get the memcache if (cache == null) { @@ -176,7 +179,7 @@ query.setHint(JPACursorHelper.CURSOR_HINT, trialCursor); while (firstResult > pos) { int min = Math.min(firstResult - pos, 1000); - + // If we need to skip more than 1000 records, ensure the // breaks occur at multiples of 1000 in order to increase the // chances of reusing cursors from the memcache @@ -184,7 +187,7 @@ int mod = (pos + min) % 1000; min -= mod; } - + query.setMaxResults(min); List<Report> results = query.getResultList(); int count = results.size(); @@ -192,7 +195,7 @@ break; } pos += count; - + // Save the cursor for later Cursor cursor = JPACursorHelper.getCursor(results); if (cache != null) { @@ -200,24 +203,25 @@ pos); cache.put(key, cursor.toWebSafeString()); } - + query.setHint(JPACursorHelper.CURSOR_HINT, cursor); } } - + query.setMaxResults(maxResults); List<Report> reportList = query.getResultList(); // force it to materialize reportList.size(); - + Cursor cursor = JPACursorHelper.getCursor(reportList); if (cache != null) { int pos = firstResult + reportList.size(); - String key = createKey(employeeId, department, startsWith, orderBy, pos); + String key = createKey(employeeId, department, startsWith, orderBy, + pos); cache.put(key, cursor.toWebSafeString()); } - + return reportList; } finally { em.close(); @@ -228,7 +232,8 @@ public static List<Report> findReportsByEmployee(Long employeeId) { EntityManager em = entityManager(); try { - Query query = em.createQuery("select o from Report o where o.reporterKey =:reporterKey"); + 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 @@ -241,22 +246,19 @@ private static String createKey(Long employeeId, String department, String startsWith, String orderBy, int firstResult) { - return "" + employeeId + "+" + encode(department) + "+" - + encode(startsWith) + "+" + encode(orderBy) + "+" + firstResult; + return "" + employeeId + "+" + encode(department) + "+" + encode(startsWith) + + "+" + encode(orderBy) + "+" + firstResult; } /** * Returns a String based on an input String that provides the following * guarantees. - * - * <ol> - * <li>The result contains no '+' characters - * <li>Distinct inputs always produce distinct results - * </ol> - * - * <p> - * Note that the transformation is not required to be reversible. - * + * + * <ol> <li>The result contains no '+' characters <li>Distinct inputs always + * produce distinct results </ol> + * + * <p> Note that the transformation is not required to be reversible. + * * @param s the input String * @return a String suitable for use as part of a a memcache key */ @@ -272,13 +274,13 @@ /** * Query for reports based on the search parameters. If startsWith is * specified, the results will not be ordered. - * - * @param em the {@link EntityManager} to use + * + * @param em the {@link EntityManager} to use * @param employeeId the employee id * @param department the department to search * @param startsWith the starting string - * @param orderBy the order of the results - * @param isCount true to query on the count only + * @param orderBy the order of the results + * @param isCount true to query on the count only * @return the query, or null to return full report count. */ private static Query queryReportsBySearch(EntityManager em, Long employeeId, @@ -335,7 +337,14 @@ return query; } + @Transient + private Employee approvedSupervisor; + + @Transient + private Employee reporter; + // @JoinColumn + private Long approvedSupervisorKey; private Date created; @@ -358,8 +367,7 @@ private String purposeLowerCase; /** - * Store reporter's key instead of reporter. See: - * http://code.google.com/appengine + * Store reporter's key instead of reporter. See: http://code.google.com/appengine * /docs/java/datastore/relationships.html#Unowned_Relationships */ // @JoinColumn @@ -369,6 +377,11 @@ @Column(name = "version") private Integer version; + public Employee getApprovedSupervisor() { + return approvedSupervisorKey != null ? Employee.findEmployee( + approvedSupervisorKey) : null; + } + public Long getApprovedSupervisorKey() { return approvedSupervisorKey; } @@ -393,6 +406,10 @@ return this.purpose; } + public Employee getReporter() { + return reporterKey != null ? Employee.findEmployee(reporterKey) : null; + } + public Long getReporterKey() { return this.reporterKey; } @@ -445,10 +462,18 @@ this.purposeLowerCase = purpose == null ? "" : purpose.toLowerCase(); } + public void setReporter(Employee reporter) { + reporterKey = reporter == null ? null : reporter.getId(); + } + public void setReporterKey(Long reporter) { this.reporterKey = reporter; } + public void setSupervisor(Employee supervisor) { + approvedSupervisorKey = supervisor == null ? null : supervisor.getId(); + } + public void setVersion(Integer version) { this.version = version; } @@ -466,4 +491,5 @@ sb.append("ApprovedSupervisor: ").append(getApprovedSupervisorKey()); return sb.toString(); } + }
diff --git a/user/src/com/google/gwt/app/client/NullParser.java b/user/src/com/google/gwt/app/client/NullParser.java new file mode 100644 index 0000000..e37ea84 --- /dev/null +++ b/user/src/com/google/gwt/app/client/NullParser.java
@@ -0,0 +1,51 @@ +/* + * 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.app.client; + +import com.google.gwt.text.shared.Parser; +import com.google.gwt.valuestore.shared.Record; + +import java.text.ParseException; + +/** + * <span style="color:red">Experimental API: This class is still under rapid + * development, and is very likely to be deleted. Use it at your own risk. + * </span> + * <p> + * A no-op renderer, always returns null + * @param <T> a Record type. + */ +public class NullParser<T extends Record> implements Parser<T> { + + private static NullParser INSTANCE; + + /** + * @return the instance of the null parser + */ + public static <T extends Record> Parser<T> instance() { + if (INSTANCE == null) { + INSTANCE = new NullParser<T>(); + } + return INSTANCE; + } + + protected NullParser() { + } + + public T parse(CharSequence object) throws ParseException { + return null; + } +}
diff --git a/user/src/com/google/gwt/app/client/ProxyBox.java b/user/src/com/google/gwt/app/client/ProxyBox.java new file mode 100644 index 0000000..30410a9 --- /dev/null +++ b/user/src/com/google/gwt/app/client/ProxyBox.java
@@ -0,0 +1,43 @@ +/* + * 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.app.client; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.user.client.ui.ValueBox; +import com.google.gwt.valuestore.shared.Record; + +/** + * <span style="color:red">Experimental API: This class is still under rapid + * development, and is very likely to be deleted. Use it at your own risk. + * </span> + * <p> + * A ValueBox that uses {@link com.google.gwt.app.client.ProxyParser} and + * {@link com.google.gwt.app.client.ProxyRenderer}. + * + * @param <T> a proxy record + */ +public class ProxyBox<T extends Record> extends ValueBox<T> { + + public ProxyBox() { + super(Document.get().createTextInputElement(), ProxyRenderer.<T>instance(), + ProxyParser.<T>instance()); + } + + public T getValue() { + // Until a sensible ProxyParser can be written + return null; + } +}
diff --git a/user/src/com/google/gwt/app/client/ProxyIdRenderer.java b/user/src/com/google/gwt/app/client/ProxyIdRenderer.java new file mode 100644 index 0000000..239ecf8 --- /dev/null +++ b/user/src/com/google/gwt/app/client/ProxyIdRenderer.java
@@ -0,0 +1,49 @@ +/* + * 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.app.client; + +import com.google.gwt.text.shared.AbstractRenderer; +import com.google.gwt.text.shared.Renderer; +import com.google.gwt.valuestore.shared.Record; + +/** + * <span style="color:red">Experimental API: This class is still under rapid + * development, and is very likely to be deleted. Use it at your own risk. + * </span> + * <p> + * Renderer of Record values + * @param <T> a record type + */ +public class ProxyIdRenderer<T extends Record> extends AbstractRenderer<T> { + private static ProxyIdRenderer INSTANCE; + + /** + * @return the instance + */ + public static <T extends Record> Renderer<T> instance() { + if (INSTANCE == null) { + INSTANCE = new ProxyIdRenderer<T>(); + } + return INSTANCE; + } + + protected ProxyIdRenderer() { + } + + public String render(T object) { + return toString(object == null ? null : object.getId()); + } +}
diff --git a/user/src/com/google/gwt/app/client/ProxyParser.java b/user/src/com/google/gwt/app/client/ProxyParser.java new file mode 100644 index 0000000..221a4f0 --- /dev/null +++ b/user/src/com/google/gwt/app/client/ProxyParser.java
@@ -0,0 +1,57 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.app.client; + +import com.google.gwt.text.shared.Parser; +import com.google.gwt.valuestore.shared.Record; + +import java.text.ParseException; + +/** + * <span style="color:red">Experimental API: This class is still under rapid + * development, and is very likely to be deleted. Use it at your own risk. + * </span> + * <p> + * A no-op renderer. + * @param <T> a Record type. + */ +public class ProxyParser<T extends Record> implements Parser<T> { + + private static ProxyParser INSTANCE; + + /** + * @return the instance of the no-op renderer + */ + public static <T extends Record> Parser<T> instance() { + if (INSTANCE == null) { + INSTANCE = new ProxyParser<T>(); + } + return INSTANCE; + } + + protected ProxyParser() { + } + + public T parse(CharSequence object) throws ParseException { + try { + // TODO: how can we map an id back into a record synchronously? Use + // ValueStore? + return null; + } catch (NumberFormatException e) { + throw new ParseException(e.getMessage(), 0); + } + } +}
diff --git a/user/src/com/google/gwt/app/client/ProxyRenderer.java b/user/src/com/google/gwt/app/client/ProxyRenderer.java new file mode 100644 index 0000000..5ea3d4f --- /dev/null +++ b/user/src/com/google/gwt/app/client/ProxyRenderer.java
@@ -0,0 +1,49 @@ +/* + * 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.app.client; + +import com.google.gwt.text.shared.AbstractRenderer; +import com.google.gwt.text.shared.Renderer; +import com.google.gwt.valuestore.shared.Record; + +/** + * <span style="color:red">Experimental API: This class is still under rapid + * development, and is very likely to be deleted. Use it at your own risk. + * </span> + * <p> + * Renderer of Record values + * @param <T> a record type + */ +public class ProxyRenderer<T extends Record> extends AbstractRenderer<T> { + private static ProxyRenderer INSTANCE; + + /** + * @return the instance + */ + public static <T extends Record> Renderer<T> instance() { + if (INSTANCE == null) { + INSTANCE = new ProxyRenderer<T>(); + } + return INSTANCE; + } + + protected ProxyRenderer() { + } + + public String render(T object) { + return toString(object == null ? null : object.getId()); + } +}
diff --git a/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java b/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java new file mode 100644 index 0000000..cafe722 --- /dev/null +++ b/user/src/com/google/gwt/app/client/ReadonlyProxyBox.java
@@ -0,0 +1,51 @@ +/* + * 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.app.client; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.user.client.ui.ValueBox; +import com.google.gwt.valuestore.shared.Record; + +/** + * <span style="color:red">Experimental API: This class is still under rapid + * development, and is very likely to be deleted. Use it at your own risk. + * </span> + * <p> + * A ValueBox that uses {@link NullParser} and + * {@link ProxyIdRenderer}. It is a display only placeholder class for now; + * + * @param <T> a proxy record + */ +public class ReadonlyProxyBox<T extends Record> extends ValueBox<T> { + + private T currentValue; + + public ReadonlyProxyBox() { + super(Document.get().createTextInputElement(), ProxyIdRenderer.<T>instance(), + NullParser.<T>instance()); + setReadOnly(true); + } + + public T getValue() { + // The display value cannot be modified; + return currentValue; + } + + public void setValue(T value) { + this.currentValue = value; + super.setValue(value); + } +}
diff --git a/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java b/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java index 9df2bbe..f8bc60f 100644 --- a/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java +++ b/user/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
@@ -36,6 +36,7 @@ import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.valuestore.shared.Property; +import com.google.gwt.valuestore.shared.Record; import java.io.PrintWriter; import java.util.Arrays; @@ -90,6 +91,7 @@ JClassType takesValueType; JClassType hasTextType; + JClassType jrecordType; JClassType stringType; private String capitalize(String name) { @@ -180,7 +182,8 @@ HasText.class.getName()); stringType = generatorContext.getTypeOracle().findType( String.class.getName()); - + recordType = generatorContext.getTypeOracle().findType( + Record.class.getName()); writeGetPropertiesMethod(sw, recordType); writeInit(sw, viewType, recordType); writeIsChangedMethod(sw, recordType, viewType); @@ -303,6 +306,9 @@ if (returnType.isAssignableTo(stringType)) { return ""; } + if (returnType.isAssignableTo(jrecordType)) { + return ".getId()+\"\""; + } return ".toString()"; }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java index 461303b..0c60cf5 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java
@@ -50,7 +50,9 @@ } public void handleResponseText(String text) { - JsArray<RecordJsoImpl> valueJsos = RecordJsoImpl.arrayFromJson(text); + RecordJsoImpl.JsonResults results = RecordJsoImpl.fromResults(text); + + JsArray<RecordJsoImpl> valueJsos = results.getListResult(); List<T> valueList = new ArrayList<T>(valueJsos.length()); for (int i = 0; i < valueJsos.length(); i++) { RecordJsoImpl jso = valueJsos.get(i); @@ -58,7 +60,9 @@ valueList.add(schema.create(jso)); } - requestFactory.getValueStore().setRecords(valueJsos); + requestFactory.getValueStore().setRecords(valueJsos, requestFactory); + processRelated(results.getRelated()); + receiver.onSuccess(valueList, Collections.<SyncResult> emptySet()); } }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java index 903458f..41c0864 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java
@@ -22,22 +22,19 @@ import java.util.Collections; /** - * <p> - * <span style="color:red">Experimental API: This class is still under rapid + * <p> <span style="color:red">Experimental API: This class is still under rapid * development, and is very likely to be deleted. Use it at your own risk. - * </span> - * </p> - * Abstract implementation of - * {@link com.google.gwt.requestfactory.shared.RequestObject + * </span> </p> Abstract implementation of {@link com.google.gwt.requestfactory.shared.RequestObject * RequestFactory.RequestObject} for requests that return single instances of * {@link Record}. - * + * * @param <T> the type of entities returned * @param <R> this request type */ public abstract class // -AbstractJsonObjectRequest<T extends Record, R extends AbstractJsonObjectRequest<T, R>> // + AbstractJsonObjectRequest<T extends Record, R extends AbstractJsonObjectRequest<T, R>> // extends AbstractRequest<T, R> implements RecordRequest<T> { + protected final RecordSchema<? extends T> schema; public AbstractJsonObjectRequest(RecordSchema<? extends T> schema, @@ -47,11 +44,15 @@ } public void handleResponseText(String text) { - RecordJsoImpl jso = RecordJsoImpl.fromJson(text); + RecordJsoImpl.JsonResults results = RecordJsoImpl.fromResults(text); + + RecordJsoImpl jso = results.getResult(); jso.setSchema(schema); - requestFactory.getValueStore().setRecord(jso); - receiver.onSuccess(schema.create(jso), - Collections.<SyncResult> emptySet()); + requestFactory.getValueStore().setRecord(jso, requestFactory); + processRelated(results.getRelated()); + + receiver.onSuccess(schema.create(jso), Collections.<SyncResult>emptySet()); } + }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java index f7c0631..9a9bfaf 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
@@ -15,6 +15,7 @@ */ package com.google.gwt.requestfactory.client.impl; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.requestfactory.shared.Receiver; import com.google.gwt.requestfactory.shared.RequestObject; import com.google.gwt.valuestore.shared.Property; @@ -26,8 +27,7 @@ import java.util.Set; /** - * <p> - * <span style="color:red">Experimental API: This class is still under rapid + * <p> <span style="color:red">Experimental API: This class is still under rapid * development, and is very likely to be deleted. Use it at your own risk. * </span> * </p> @@ -44,7 +44,7 @@ protected DeltaValueStoreJsonImpl deltaValueStore; protected Receiver<T> receiver; - private final Set<Property<?>> properties = new HashSet<Property<?>>(); + private final Set<String> propertyRefs = new HashSet<String>(); public AbstractRequest(RequestFactoryJsonImpl requestFactory) { this.requestFactory = requestFactory; @@ -76,21 +76,23 @@ requestFactory.fire(this); } + /** + * @deprecated use {@link #with(String...)} instead. + * @param properties + * @return + */ public R forProperties(Collection<Property<?>> properties) { - this.properties.addAll(properties); - return getThis(); - } - - public R forProperty(Property<?> property) { - this.properties.add(property); + for (Property p : properties) { + with(p.getName()); + } return getThis(); } /** * @return the properties */ - public Set<Property<?>> getProperties() { - return Collections.unmodifiableSet(properties); + public Set<String> getPropertyRefs() { + return Collections.unmodifiableSet(propertyRefs); } public boolean isChanged() { @@ -102,10 +104,29 @@ deltaValueStore = new DeltaValueStoreJsonImpl(valueStore, requestFactory); } + public void setSchemaAndRecord(String schemaToken, RecordJsoImpl jso) { + jso.setSchema(requestFactory.getSchema(schemaToken)); + requestFactory.getValueStore().setRecord(jso, requestFactory); + } + + public R with(String... propertyRef) { + for (String ref : propertyRef) { + propertyRefs.add(ref); + } + return getThis(); + } + /** * Subclasses must override to return {@code this}, to allow builder-style * methods to do the same. */ protected abstract R getThis(); + protected native void processRelated(JavaScriptObject related) /*-{ + for(var recordKey in related) { + var schemaAndId = recordKey.split(/-/, 2); + var jso = related[recordKey]; + this.@com.google.gwt.requestfactory.client.impl.AbstractRequest::setSchemaAndRecord(Ljava/lang/String;Lcom/google/gwt/requestfactory/client/impl/RecordJsoImpl;)(schemaAndId[0], jso); + } + }-*/; } \ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java index 8f6deb3..43b4121 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordImpl.java
@@ -50,7 +50,7 @@ public <V> V get(Property<V> property) { return jso.get(property); } - + public Long getId() { return jso.getId(); } @@ -95,4 +95,8 @@ this.deltaValueStore = deltaValueStore; } + protected ValueStoreJsonImpl getValueStore() { + return jso.getValueStore(); + } + }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java index f530947..feec11b 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
@@ -28,15 +28,34 @@ import java.util.Date; /** - * <p> - * <span style="color:red">Experimental API: This class is still under rapid + * <p> <span style="color:red">Experimental API: This class is still under rapid * development, and is very likely to be deleted. Use it at your own risk. - * </span> - * </p> - * JSO implementation of {@link Record}, used to back subclasses of + * </span> </p> JSO implementation of {@link Record}, used to back subclasses of * {@link RecordImpl}. */ public class RecordJsoImpl extends JavaScriptObject implements Record { + + /** + * JSO to hold result and related objects. + */ + public static class JsonResults extends JavaScriptObject { + + protected JsonResults() { + } + + public final native JsArray<RecordJsoImpl> getListResult() /*-{ + return this.result; + }-*/; + + public final native JavaScriptObject getRelated() /*-{ + return this.related; + }-*/; + + public final native RecordJsoImpl getResult() /*-{ + return this.result; + }-*/; + } + public static native JsArray<RecordJsoImpl> arrayFromJson(String json) /*-{ return eval(json); }-*/; @@ -64,7 +83,14 @@ return xyz; }-*/; + public static native JsonResults fromResults(String json) /*-{ + // TODO: clean this + eval("xyz=" + json); + return xyz; + }-*/; + /* Made protected, for testing */ + protected static native RecordJsoImpl create() /*-{ return {}; }-*/; @@ -88,7 +114,10 @@ } try { if (Boolean.class.equals(property.getType())) { - return (V) Boolean.valueOf(getBoolean(property.getName())); + return (V) Boolean.valueOf(getBoolean(property.getName())); + } + if (Character.class.equals(property.getType())) { + return (V) Character.valueOf(String.valueOf(get(property.getName())).charAt(0)); } if (Byte.class.equals(property.getType())) { return (V) Byte.valueOf((byte) getInt(property.getName())); @@ -130,8 +159,9 @@ } } } catch (final Exception ex) { - throw new IllegalStateException("Property " + property.getName() + " has invalid " + - " value " + get(property.getName()) + " for type " + property.getType()); + throw new IllegalStateException( + "Property " + property.getName() + " has invalid " + " value " + + get(property.getName()) + " for type " + property.getType()); } if (property instanceof EnumProperty) { @@ -144,13 +174,28 @@ } } } - - // Sun JDK compile fails without this cast - return (V) get(property.getName()); + + if (String.class == property.getType()) { + return (V) get(property.getName()); + } + // at this point, we checked all the known primitive/value types we support + // TODO: handle embedded types, List, Set, Map + + // else, it must be a record type + String relatedId = (String) get(property.getName()); + if (relatedId == null) { + return null; + } else { + // TODO: should cache this or modify JSO field in place + String schemaAndId[] = relatedId.split("-"); + return (V) getValueStore().getRecordBySchemaAndId( + getRequestFactory().getSchema(schemaAndId[0]), + Long.valueOf(schemaAndId[1])); + } } public final native <T> T get(String propertyName) /*-{ - return this[propertyName]; + return this[propertyName] || null; }-*/; public final Long getId() { @@ -160,10 +205,20 @@ public final <V> PropertyReference<V> getRef(Property<V> property) { return new PropertyReference<V>(this, property); } + + // TODO: HACK! Need to look up token to schema for relatins + public final native RequestFactoryJsonImpl getRequestFactory() /*-{ + return this['__rf']; + }-*/; public final native RecordSchema<?> getSchema() /*-{ return this['__key']; }-*/; + + // TODO: HACK! Make VS public and stash it in the record for relation lookups + public final native ValueStoreJsonImpl getValueStore() /*-{ + return this['__vs']; + }-*/; public final Integer getVersion() { return this.get(version); @@ -178,8 +233,8 @@ public final boolean isEmpty() { for (Property<?> property : getSchema().allProperties()) { - if ((property != Record.id) && (property != Record.version) - && (isDefined(property.getName()))) { + if ((property != Record.id) && (property != Record.version) && (isDefined( + property.getName()))) { return false; } } @@ -211,12 +266,19 @@ setString(property.getName(), (String) value); return; } - if (value instanceof Long || value instanceof BigDecimal || value instanceof BigInteger) { + if (value instanceof Character) { setString(property.getName(), String.valueOf(value)); return; } - if (value instanceof Integer || value instanceof Short || value instanceof Byte) { + if (value instanceof Long || value instanceof BigDecimal + || value instanceof BigInteger) { + setString(property.getName(), String.valueOf(value)); + return; + } + + if (value instanceof Integer || value instanceof Short + || value instanceof Byte) { setInt(property.getName(), ((Number) value).intValue()); return; } @@ -236,17 +298,39 @@ return; } - throw new UnsupportedOperationException("Can't yet set properties of type " - + value.getClass().getName()); + if (value instanceof Boolean) { + setBoolean(property.getName(), ((Boolean) value).booleanValue()); + } + + if (value instanceof Record) { + setString(property.getName(), + getRequestFactory().getSchema(value.getClass().getName()).getToken().getName() + + "-" + String.valueOf(((Record) value).getId())); + } + + throw new UnsupportedOperationException( + "Can't yet set properties of type " + value.getClass().getName()); } + + // TODO: HACK! Need to look up token to schema for relatins + public final native void setRequestFactory(RequestFactoryJsonImpl requestFactory) /*-{ + this['__rf'] = requestFactory; + }-*/; + public final native void setSchema(RecordSchema<?> schema) /*-{ this['__key'] = schema; }-*/; + // TODO: HACK! Make VS public and stash it in the record for relation lookups + + public final native void setValueStore(ValueStoreJsonImpl valueStoreJson) /*-{ + this['__vs']=valueStoreJson; + }-*/; + /** * Return JSON representation using org.json library. - * + * * @return returned string. */ public final native String toJson() /*-{ @@ -271,7 +355,7 @@ /** * Return JSON representation of just id and version fields, using org.json * library. - * + * * @return returned string. */ public final native String toJsonIdVersion() /*-{ @@ -287,7 +371,8 @@ return $wnd.JSON.stringify(object); }-*/; - private native boolean copyPropertyIfDifferent(String name, RecordJsoImpl from) /*-{ + private native boolean copyPropertyIfDifferent(String name, + RecordJsoImpl from) /*-{ if (this[name] == from[name]) { return false; } @@ -311,6 +396,10 @@ return this[name]; }-*/; + private native void setBoolean(String name, boolean value) /*-{ + this[name] = value; + }-*/; + private native void setDouble(String name, double value) /*-{ this[name] = value; }-*/;
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java index 7559688..3b1c109 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordToTypeMap.java
@@ -28,4 +28,5 @@ */ public interface RecordToTypeMap { RecordSchema<? extends Record> getType(Class<? extends Record> recordClass); + RecordSchema<? extends Record> getType(String token); }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java index 8f3a818..898eae5 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
@@ -120,6 +120,8 @@ e); } } + + public abstract RecordSchema getSchema(String token); /** * @param handlerManager
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java index d523a71..cff570e 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
@@ -17,6 +17,7 @@ import com.google.gwt.core.client.JsArray; import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.valuestore.shared.Record; import com.google.gwt.valuestore.shared.WriteOperation; import java.util.HashMap; @@ -29,7 +30,7 @@ * </span> * </p> */ -class ValueStoreJsonImpl { +public class ValueStoreJsonImpl { // package protected fields for use by DeltaValueStoreJsonImpl final HandlerManager eventBus; @@ -40,14 +41,26 @@ this.eventBus = eventBus; } - public void setRecord(RecordJsoImpl newRecord) { - setRecordInList(newRecord, 0, null); + public Record getRecordBySchemaAndId(RecordSchema<?> schema, + Long id) { + if (id == null) { + return null; + } + // TODO: pass isFuture to this method from decoding ID string + RecordKey key = new RecordKey(id, schema, false); + return schema.create(records.get(key)); } - public void setRecords(JsArray<RecordJsoImpl> newRecords) { + public void setRecord(RecordJsoImpl newRecord, + RequestFactoryJsonImpl requestFactory) { + setRecordInList(newRecord, 0, null, requestFactory); + } + + public void setRecords(JsArray<RecordJsoImpl> newRecords, + RequestFactoryJsonImpl requestFactory) { for (int i = 0, l = newRecords.length(); i < l; i++) { RecordJsoImpl newRecord = newRecords.get(i); - setRecordInList(newRecord, i, newRecords); + setRecordInList(newRecord, i, newRecords, requestFactory); } } @@ -55,11 +68,14 @@ * @param newRecord * @param i * @param array + * @param requestFactory */ private void setRecordInList(RecordJsoImpl newRecord, int i, - JsArray<RecordJsoImpl> array) { + JsArray<RecordJsoImpl> array, RequestFactoryJsonImpl requestFactory) { RecordKey recordKey = new RecordKey(newRecord, RequestFactoryJsonImpl.NOT_FUTURE); - + newRecord.setValueStore(this); + newRecord.setRequestFactory(requestFactory); + RecordJsoImpl oldRecord = records.get(recordKey); if (oldRecord == null) { records.put(recordKey, newRecord);
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java index b315688..0cf7f65 100644 --- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java +++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -65,23 +65,22 @@ import java.math.BigInteger; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; /** - * <p> - * <span style="color:red">Experimental API: This class is still under rapid + * <p> <span style="color:red">Experimental API: This class is still under rapid * development, and is very likely to be deleted. Use it at your own risk. - * </span> - * </p> - * Generates implementations of - * {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory} - * and its nested interfaces. + * </span> </p> Generates implementations of {@link com.google.gwt.requestfactory.shared.RequestFactory + * RequestFactory} and its nested interfaces. */ public class RequestFactoryGenerator extends Generator { - private final Set<JClassType> generatedRecordTypes = new HashSet<JClassType>(); + private final Set<JClassType> generatedRecordTypes + = new HashSet<JClassType>(); @Override public String generate(TreeLogger logger, GeneratorContext generatorContext, @@ -94,15 +93,16 @@ // Ensure that the requested type exists if (interfaceType == null) { - logger.log(TreeLogger.ERROR, "Could not find requested typeName: " - + interfaceName); + logger.log(TreeLogger.ERROR, + "Could not find requested typeName: " + interfaceName); throw new UnableToCompleteException(); } if (interfaceType.isInterface() == null) { // The incoming type wasn't a plain interface, we don't support // abstract base classes - logger.log(TreeLogger.ERROR, interfaceType.getQualifiedSourceName() - + " is not an interface.", null); + logger.log(TreeLogger.ERROR, + interfaceType.getQualifiedSourceName() + " is not an interface.", + null); throw new UnableToCompleteException(); } @@ -114,9 +114,8 @@ // If an implementation already exists, we don't need to do any work if (out != null) { - generateOnce( - typeOracle.findType(RequestFactory.class.getCanonicalName()), logger, - generatorContext, out, interfaceType, packageName, implName); + generateOnce(typeOracle.findType(RequestFactory.class.getCanonicalName()), + logger, generatorContext, out, interfaceType, packageName, implName); } return packageName + "." + implName; @@ -147,6 +146,9 @@ String recordImplTypeName = publicRecordType.getName() + "Impl"; PrintWriter pw = generatorContext.tryCreate(logger, packageName, recordImplTypeName); + + Set<JClassType> transitiveDeps = new LinkedHashSet<JClassType>(); + if (pw != null) { logger = logger.branch(TreeLogger.DEBUG, "Generating " + publicRecordType.getName()); @@ -157,9 +159,10 @@ String eventTypeName = publicRecordType.getName() + "Changed"; JClassType eventType = typeOracle.findType(packageName, eventTypeName); if (eventType == null) { - logger.log(TreeLogger.ERROR, String.format( - "Cannot find %s implementation %s.%s", - RecordChangedEvent.class.getName(), packageName, eventTypeName)); + logger.log(TreeLogger.ERROR, + String.format("Cannot find %s implementation %s.%s", + RecordChangedEvent.class.getName(), packageName, + eventTypeName)); throw new UnableToCompleteException(); } @@ -176,6 +179,8 @@ f.addImport(Collections.class.getName()); f.addImport(HashSet.class.getName()); f.addImport(Set.class.getName()); + f.addImport(Map.class.getName()); + f.addImport(HashMap.class.getName()); f.setSuperclass(RecordImpl.class.getSimpleName()); f.addImplementedInterface(publicRecordType.getName()); @@ -188,8 +193,10 @@ sw.println(); String simpleImplName = publicRecordType.getSimpleSourceName() + "Impl"; - printRequestImplClass(sw, publicRecordType, simpleImplName, true); - printRequestImplClass(sw, publicRecordType, simpleImplName, false); + printRequestImplClass(sw, publicRecordType, simpleImplName, true, + typeOracle); + printRequestImplClass(sw, publicRecordType, simpleImplName, false, + typeOracle); sw.println(); sw.println(String.format( @@ -203,6 +210,7 @@ sw.println("super(jso, isFuture);"); sw.outdent(); sw.println("}"); + JClassType recordBaseType = typeOracle.findType(Record.class.getName()); // getter methods for (JField field : publicRecordType.getFields()) { @@ -210,18 +218,26 @@ if (propertyType.getErasedType() == fieldType.getErasedType()) { JParameterizedType parameterized = fieldType.isParameterized(); if (parameterized == null) { - logger.log(TreeLogger.ERROR, fieldType - + " must have its param type set."); + logger.log(TreeLogger.ERROR, + fieldType + " must have its param type set."); throw new UnableToCompleteException(); } JClassType returnType = parameterized.getTypeArgs()[0]; sw.println(); sw.println(String.format("public %s get%s() {", - returnType.getQualifiedSourceName(), capitalize(field.getName()))); + returnType.getQualifiedSourceName(), + capitalize(field.getName()))); sw.indent(); sw.println(String.format("return get(%s);", field.getName())); sw.outdent(); sw.println("}"); + /* + * Because a Proxy A may relate to B which relates to C, we need to + * ensure transitively. + */ + if (isRecordType(typeOracle, returnType)) { + transitiveDeps.add(returnType); + } } } @@ -254,6 +270,11 @@ } generatedRecordTypes.add(publicRecordType); + // ensure generatation of transitive dependencies + for (JClassType type : transitiveDeps) { + ensureRecordType(logger, generatorContext, type.getPackage().getName(), + type); + } } private void generateOnce(JClassType requestFactoryType, TreeLogger logger, @@ -261,8 +282,9 @@ JClassType interfaceType, String packageName, String implName) throws UnableToCompleteException { - logger = logger.branch(TreeLogger.DEBUG, String.format( - "Generating implementation of %s", interfaceType.getName())); + logger = logger.branch(TreeLogger.DEBUG, + String.format("Generating implementation of %s", + interfaceType.getName())); ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory( packageName, implName); @@ -270,6 +292,8 @@ f.addImport(RequestFactoryJsonImpl.class.getName()); f.addImport(interfaceType.getQualifiedSourceName()); f.addImport(RecordToTypeMap.class.getName()); + f.addImport(Record.class.getName()); + f.addImport(RecordSchema.class.getName()); f.addImplementedInterface(interfaceType.getName()); f.setSuperclass(RequestFactoryJsonImpl.class.getSimpleName()); @@ -288,11 +312,9 @@ } JType returnType = method.getReturnType(); if (null == returnType) { - logger.log( - TreeLogger.ERROR, - String.format( - "Illegal return type for %s. Methods of %s must return interfaces, found void", - method.getName(), interfaceType.getName())); + logger.log(TreeLogger.ERROR, String.format( + "Illegal return type for %s. Methods of %s must return interfaces, found void", + method.getName(), interfaceType.getName())); throw new UnableToCompleteException(); } JClassType asInterface = returnType.isInterface(); @@ -317,8 +339,8 @@ } // write create(Class..) - JClassType recordToTypeInterface = generatorContext.getTypeOracle().findType( - RecordToTypeMap.class.getName()); + JClassType recordToTypeInterface = generatorContext.getTypeOracle().findType(RecordToTypeMap.class.getName()); + // TODO: note, this seems like a bug. What if you have 2 RequestFactories? String recordToTypeMapName = recordToTypeInterface.getName() + "Impl"; sw.println("public " + Record.class.getName() + " create(Class token) {"); sw.indent(); @@ -326,20 +348,26 @@ sw.outdent(); sw.println("}"); + sw.println( + "public RecordSchema<? extends Record> getSchema(String schemaToken) {"); + sw.indent(); + sw.println("return new " + recordToTypeMapName + "().getType(schemaToken);"); + sw.outdent(); + sw.println("}"); + // write a method for each request builder and generate it for (JMethod requestSelector : requestSelectors) { String returnTypeName = requestSelector.getReturnType().getQualifiedSourceName(); - String nestedImplName = capitalize(requestSelector.getName().replace('.', - '_')) - + "Impl"; + String nestedImplName = + capitalize(requestSelector.getName().replace('.', '_')) + "Impl"; String nestedImplPackage = generatorContext.getTypeOracle().findType( returnTypeName).getPackage().getName(); sw.println("public " + returnTypeName + " " + requestSelector.getName() + "() {"); sw.indent(); - sw.println("return new " + nestedImplPackage + "." + nestedImplName - + "(this);"); + sw.println( + "return new " + nestedImplPackage + "." + nestedImplName + "(this);"); sw.outdent(); sw.println("}"); sw.println(); @@ -369,8 +397,9 @@ private void generateRecordToTypeMap(TreeLogger logger, GeneratorContext generatorContext, PrintWriter out, JClassType interfaceType, String packageName, String implName) { - logger = logger.branch(TreeLogger.DEBUG, String.format( - "Generating implementation of %s", interfaceType.getName())); + logger = logger.branch(TreeLogger.DEBUG, + String.format("Generating implementation of %s", + interfaceType.getName())); ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory( packageName, implName); @@ -396,9 +425,30 @@ sw.println("}"); } - sw.println("throw new IllegalArgumentException(\"Unknown token \" + token + "); + sw.println( + "throw new IllegalArgumentException(\"Unknown token \" + token + "); sw.indent(); - sw.println("\", does not match any of the TOKEN vairables of a Record\");"); + sw.println("\", does not match any of the TOKEN variables of a Record\");"); + sw.outdent(); + sw.outdent(); + sw.println("}"); + + // TODO: unoptimal bloat, and there doesn't need to be two of these methods + sw.println("public RecordSchema<? extends Record> getType(String token) {"); + sw.indent(); + for (JClassType publicRecordType : generatedRecordTypes) { + String qualifiedSourceName = publicRecordType.getQualifiedSourceName(); + sw.println("if (token.equals(\"" + qualifiedSourceName + "\")) {"); + sw.indent(); + sw.println("return " + qualifiedSourceName + "Impl.SCHEMA;"); + sw.outdent(); + sw.println("}"); + } + + sw.println( + "throw new IllegalArgumentException(\"Unknown token \" + token + "); + sw.indent(); + sw.println("\", does not match any of the TOKEN variables of a Record\");"); sw.outdent(); sw.outdent(); sw.println("}"); @@ -413,8 +463,9 @@ JMethod selectorMethod, JClassType mainType, String packageName, String implName) throws UnableToCompleteException { JClassType selectorInterface = selectorMethod.getReturnType().isInterface(); - logger = logger.branch(TreeLogger.DEBUG, String.format( - "Generating implementation of %s", selectorInterface.getName())); + logger = logger.branch(TreeLogger.DEBUG, + String.format("Generating implementation of %s", + selectorInterface.getName())); ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory( packageName, implName); @@ -428,8 +479,8 @@ sw.println("private final " + mainType.getName() + "Impl factory;"); sw.println(); // constructor for the class. - sw.println("public " + implName + "(" + mainType.getName() - + "Impl factory) {"); + sw.println( + "public " + implName + "(" + mainType.getName() + "Impl factory) {"); sw.indent(); sw.println("this.factory = factory;"); sw.outdent(); @@ -485,21 +536,23 @@ } else if (isVoidRequest(typeOracle, requestType)) { requestClassName = AbstractVoidRequest.class.getName(); } else { - logger.log(TreeLogger.ERROR, "Return type " + requestType - + " is not yet supported"); + logger.log(TreeLogger.ERROR, + "Return type " + requestType + " is not yet supported"); throw new UnableToCompleteException(); } sw.println(getMethodDeclaration(method) + " {"); sw.indent(); - sw.println("return new " + requestClassName + "(factory" + enumArgument - + ") {"); + sw.println( + "return new " + requestClassName + "(factory" + enumArgument + ") {"); sw.indent(); String requestDataName = RequestData.class.getSimpleName(); sw.println("public " + requestDataName + " getRequestData() {"); sw.indent(); - sw.println("return new " + requestDataName + "(\"" + operationName - + "\", " + getParametersAsString(method, typeOracle) + ");"); + sw.println( + "return new " + requestDataName + "(\"" + operationName + "\", " + + getParametersAsString(method, typeOracle) + "," + + "getPropertyRefs());"); sw.outdent(); sw.println("}"); sw.outdent(); @@ -514,9 +567,8 @@ } /** - * This method is very similar to {@link - * com.google.gwt.core.ext.typeinfo.JMethod.getReadableDeclaration()}. The - * only change is that each parameter is final. + * This method is very similar to {@link com.google.gwt.core.ext.typeinfo.JMethod.getReadableDeclaration()}. + * The only change is that each parameter is final. */ private String getMethodDeclaration(JMethod method) { StringBuilder sb = new StringBuilder("public "); @@ -555,7 +607,8 @@ // TODO No. This defeats the entire purpose of PropertyReference. It's // supposed // to be dereferenced server side, not client side. - if ("com.google.gwt.valuestore.shared.PropertyReference".equals(parameter.getType().getQualifiedBinaryName())) { + if ("com.google.gwt.valuestore.shared.PropertyReference".equals( + parameter.getType().getQualifiedBinaryName())) { sb.append(".get()"); } JClassType classType = parameter.getType().isClassOrInterface(); @@ -577,7 +630,8 @@ return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(BigInteger.class.getName())); } - private boolean isBooleanRequest(TypeOracle typeOracle, JClassType requestType) { + private boolean isBooleanRequest(TypeOracle typeOracle, + JClassType requestType) { return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Boolean.class.getName())); } @@ -594,7 +648,8 @@ return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Date.class.getName())); } - private boolean isDoubleRequest(TypeOracle typeOracle, JClassType requestType) { + private boolean isDoubleRequest(TypeOracle typeOracle, + JClassType requestType) { return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Double.class.getName())); } @@ -602,11 +657,13 @@ return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Enum.class.getName())); } - private boolean isFloatRequest(TypeOracle typeOracle, JClassType requestType) { + private boolean isFloatRequest(TypeOracle typeOracle, + JClassType requestType) { return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Float.class.getName())); } - private boolean isIntegerRequest(TypeOracle typeOracle, JClassType requestType) { + private boolean isIntegerRequest(TypeOracle typeOracle, + JClassType requestType) { return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Integer.class.getName())); } @@ -619,11 +676,17 @@ return requestType.isAssignableTo(typeOracle.findType(RecordListRequest.class.getName())); } - private boolean isRecordRequest(TypeOracle typeOracle, JClassType requestType) { + private boolean isRecordRequest(TypeOracle typeOracle, + JClassType requestType) { return requestType.isAssignableTo(typeOracle.findType(RecordRequest.class.getName())); } - private boolean isShortRequest(TypeOracle typeOracle, JClassType requestType) { + private boolean isRecordType(TypeOracle typeOracle, JClassType requestType) { + return requestType.isAssignableTo(typeOracle.findType(Record.class.getName())); + } + + private boolean isShortRequest(TypeOracle typeOracle, + JClassType requestType) { return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Short.class.getName())); } @@ -633,11 +696,9 @@ /** * Prints a ListRequestImpl or ObjectRequestImpl class. - * - * @param list */ private void printRequestImplClass(SourceWriter sw, JClassType returnType, - String returnImplTypeName, boolean list) { + String returnImplTypeName, boolean list, TypeOracle typeOracle) { String name = list ? "ListRequestImpl" : "ObjectRequestImpl"; Class<?> superClass = list ? AbstractJsonListRequest.class @@ -648,6 +709,7 @@ + "> {"); sw.println(); sw.indent(); + sw.println(String.format("%s(%s factory) {", name, RequestFactoryJsonImpl.class.getSimpleName())); sw.indent(); @@ -661,6 +723,7 @@ sw.println("return this;"); sw.outdent(); sw.println("}"); + sw.outdent(); sw.println("}"); sw.println(); @@ -675,12 +738,13 @@ * @return * @throws UnableToCompleteException */ + private JClassType printSchema(TypeOracle typeOracle, JClassType publicRecordType, String recordImplTypeName, JClassType eventType, SourceWriter sw) { - sw.println(String.format( - "public static class MySchema extends RecordSchema<%s> {", - recordImplTypeName)); + sw.println( + String.format("public static class MySchema extends RecordSchema<%s> {", + recordImplTypeName)); sw.indent(); sw.println("private final Set<Property<?>> allProperties;"); @@ -737,8 +801,8 @@ sw.println(); sw.println("public Class getToken() {"); sw.indent(); - sw.println("return " + publicRecordType.getQualifiedSourceName() - + ".class;" + " // special field"); + sw.println("return " + publicRecordType.getQualifiedSourceName() + ".class;" + + " // special field"); sw.outdent(); sw.println("}");
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java index a063474..72b1ed2 100644 --- a/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java +++ b/user/src/com/google/gwt/requestfactory/server/DefaultSecurityProvider.java
@@ -43,6 +43,10 @@ } } + public String encodeClassType(Class<?> type) { + return type.getName(); + } + public String mapOperation(String operationName) throws SecurityException { return operationName; }
diff --git a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java index d017f36..ed09eca 100644 --- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java +++ b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
@@ -18,6 +18,7 @@ import com.google.gwt.requestfactory.shared.DataTransferObject; import com.google.gwt.requestfactory.shared.RequestData; import com.google.gwt.requestfactory.shared.RequestFactory; +import com.google.gwt.requestfactory.shared.Service; import com.google.gwt.valuestore.shared.Property; import com.google.gwt.valuestore.shared.Record; import com.google.gwt.valuestore.shared.WriteOperation; @@ -56,12 +57,15 @@ private static final Logger log = Logger.getLogger(JsonRequestProcessor.class.getName()); // TODO should we consume String, InputStream, or JSONObject? + /** * A class representing the pair of a domain entity and its corresponding * record class on the client side. */ public static class EntityRecordPair { + public final Class<?> entity; + public final Class<? extends Record> record; EntityRecordPair(Class<?> entity, Class<? extends Record> record) { @@ -80,13 +84,17 @@ return Collections.unmodifiableSet(blackList); } + private RequestProperty propertyRefs; + + private Map<String, JSONObject> relatedObjects + = new HashMap<String, JSONObject>(); + private OperationRegistry operationRegistry; public Collection<Property<?>> allProperties(Class<? extends Record> clazz) { Set<Property<?>> rtn = new HashSet<Property<?>>(); for (Field f : clazz.getFields()) { - if (Modifier.isStatic(f.getModifiers()) - && Property.class.isAssignableFrom(f.getType())) { + if (Modifier.isStatic(f.getModifiers()) && Property.class.isAssignableFrom(f.getType())) { try { rtn.add((Property<?>) f.get(null)); } catch (IllegalAccessException e) { @@ -99,7 +107,12 @@ public String decodeAndInvokeRequest(String encodedRequest) throws Exception { try { - return processJsonRequest(encodedRequest).toString(); + Logger.getLogger(this.getClass().getName()).finest("Incoming request " + + encodedRequest); + String response = processJsonRequest(encodedRequest).toString(); + Logger.getLogger(this.getClass().getName()).finest("Outgoing response " + + response); + return response; } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } catch (IllegalAccessException e) { @@ -119,7 +132,6 @@ /** * Encodes parameter value. - * */ public Object decodeParameterValue(Class<?> parameterType, String parameterValue) { @@ -174,21 +186,53 @@ } } } - throw new IllegalArgumentException("Can't decode enum " + parameterType - + " no matching ordinal " + ordinal); + throw new IllegalArgumentException( + "Can't decode enum " + parameterType + " no matching ordinal " + + ordinal); } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("Can't decode enum " + parameterType); + throw new IllegalArgumentException( + "Can't decode enum " + parameterType); } catch (InvocationTargetException e) { - throw new IllegalArgumentException("Can't decode enum " + parameterType); + throw new IllegalArgumentException( + "Can't decode enum " + parameterType); } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Can't decode enum " + parameterType); + throw new IllegalArgumentException( + "Can't decode enum " + parameterType); } } if (Date.class == parameterType) { return new Date(Long.parseLong(parameterValue)); } - throw new IllegalArgumentException("Unknown parameter type: " - + parameterType); + if (Record.class.isAssignableFrom(parameterType)) { + Service service = parameterType.getAnnotation(Service.class); + if (service != null) { + Class<?> sClass = service.value(); + String schemaAndId[] = parameterValue.toString().split("-", 2); + // ignore schema for now and use Property type + String findMeth = null; + try { + findMeth = getMethodNameFromPropertyName(sClass.getSimpleName(), + "find"); + Method meth = sClass.getMethod(findMeth, Long.class); + return meth.invoke(null, Long.valueOf(schemaAndId[1])); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new IllegalArgumentException( + sClass + " no method named " + findMeth); + } catch (InvocationTargetException e) { + e.printStackTrace(); + + throw new IllegalArgumentException( + sClass + " can't invoke method named " + findMeth); + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new IllegalArgumentException( + sClass + " can't access method named " + findMeth); + } + } + } + throw new IllegalArgumentException( + "Unknown parameter type: " + parameterType); } public Object encodePropertyValue(Object value) { @@ -220,18 +264,33 @@ * is sent into the response. */ public Object encodePropertyValueFromDataStore(Object entityElement, - String propertyName) throws SecurityException, NoSuchMethodException, - IllegalAccessException, InvocationTargetException { + Class<?> propertyType, String propertyName, + RequestProperty propertyContext) + throws SecurityException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException, JSONException { String methodName = getMethodNameFromPropertyName(propertyName, "get"); Method method = entityElement.getClass().getMethod(methodName); Object returnValue = method.invoke(entityElement); + if (returnValue != null && Record.class.isAssignableFrom(propertyType)) { + Method idMethod = entityElement.getClass().getMethod("getId"); + Long id = (Long) idMethod.invoke(entityElement); + + String keyRef = + operationRegistry.getSecurityProvider().encodeClassType(propertyType) + + "-" + id; + addRelatedObject(keyRef, returnValue, + (Class<? extends Record>) propertyType, + propertyContext.getProperty(propertyName)); + // replace value with id reference + return keyRef; + } return encodePropertyValue(returnValue); } /** * Generate an ID for a new record. The default behavior is to return null and * let the data store generate the ID automatically. - * + * * @param key the key of the record field * @return the ID of the new record, or null to auto generate */ @@ -260,43 +319,46 @@ return entity.getConstructor().newInstance(); } // TODO: check "version" validity. - return entity.getMethod("find" + entity.getSimpleName(), idType).invoke( - null, decodeParameterValue(idType, idValue.toString())); + return entity.getMethod("find" + entity.getSimpleName(), + idType).invoke(null, decodeParameterValue(idType, idValue.toString())); } /** * Converts the returnValue of a 'get' method to a JSONArray. - * - * @param resultList object returned by a 'get' method, must be of type - * List<?> + * + * @param resultList object returned by a 'get' method, must be of type + * List<?> * @param entityKeyClass the class type of the contained value * @return the JSONArray */ public JSONArray getJsonArray(List<?> resultList, - Class<? extends Record> entityKeyClass) throws IllegalArgumentException, - SecurityException, IllegalAccessException, JSONException, - NoSuchMethodException, InvocationTargetException { + Class<? extends Record> entityKeyClass) + throws IllegalArgumentException, SecurityException, + IllegalAccessException, JSONException, NoSuchMethodException, + InvocationTargetException { JSONArray jsonArray = new JSONArray(); if (resultList.size() == 0) { return jsonArray; } for (Object entityElement : resultList) { - jsonArray.put(getJsonObject(entityElement, entityKeyClass)); + jsonArray.put(getJsonObject(entityElement, entityKeyClass, propertyRefs)); } return jsonArray; } public JSONObject getJsonObject(Object entityElement, - Class<? extends Record> entityKeyClass) throws JSONException, - NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Class<? extends Record> entityKeyClass, RequestProperty propertyContext) + throws JSONException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { JSONObject jsonObject = new JSONObject(); for (Property<?> p : allProperties(entityKeyClass)) { - if (requestedProperty(p)) { + if (requestedProperty(p, propertyContext)) { String propertyName = p.getName(); - jsonObject.put(propertyName, encodePropertyValueFromDataStore( - entityElement, propertyName)); + jsonObject.put(propertyName, + encodePropertyValueFromDataStore(entityElement, p.getType(), + propertyName, propertyContext)); } } return jsonObject; @@ -305,11 +367,12 @@ /** * Returns methodName corresponding to the propertyName that can be invoked on * an entity. - * + * * Example: "userName" returns prefix + "UserName". "version" returns prefix + * "Version" */ - public String getMethodNameFromPropertyName(String propertyName, String prefix) { + public String getMethodNameFromPropertyName(String propertyName, + String prefix) { if (propertyName == null) { throw new NullPointerException("propertyName must not be null"); } @@ -362,13 +425,14 @@ * Returns the property fields (name => type) for a record. */ public Map<String, Class<?>> getPropertiesFromRecord( - Class<? extends Record> record) throws SecurityException, - IllegalAccessException, InvocationTargetException, NoSuchMethodException { + Class<? extends Record> record) + throws SecurityException, IllegalAccessException, + InvocationTargetException, NoSuchMethodException { Map<String, Class<?>> properties = new HashMap<String, Class<?>>(); for (Field f : record.getFields()) { if (Property.class.isAssignableFrom(f.getType())) { - Class<?> propertyType = (Class<?>) f.getType().getMethod("getType").invoke( - f.get(null)); + Class<?> propertyType = (Class<?>) f.getType().getMethod( + "getType").invoke(f.get(null)); properties.put(f.getName(), propertyType); } } @@ -379,32 +443,33 @@ * Returns the property value, in the specified type, from the request object. * The value is put in the DataStore. */ - public Object getPropertyValueFromRequest(JSONObject recordObject, - String key, Class<?> propertyType) throws JSONException { + public Object getPropertyValueFromRequest(JSONObject recordObject, String key, + Class<?> propertyType) throws JSONException { return decodeParameterValue(propertyType, recordObject.get(key).toString()); } @SuppressWarnings("unchecked") public Class<Record> getRecordFromClassToken(String recordToken) { try { - Class<?> clazz = Class.forName(recordToken, false, + Class<?> clazz = Class.forName(recordToken, false, getClass().getClassLoader()); if (Record.class.isAssignableFrom(clazz)) { return (Class<Record>) clazz; } - throw new SecurityException("Attempt to access non-record class " - + recordToken); + throw new SecurityException( + "Attempt to access non-record class " + recordToken); } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Non-existent record class " - + recordToken); + throw new IllegalArgumentException( + "Non-existent record class " + recordToken); } } public JSONObject getReturnRecord(WriteOperation writeOperation, Object entityInstance, JSONObject recordObject, - Set<ConstraintViolation<Object>> violations) throws SecurityException, - JSONException, IllegalAccessException, InvocationTargetException, - NoSuchMethodException { + Set<ConstraintViolation<Object>> violations, + Class<? extends Record> record) + throws SecurityException, JSONException, IllegalAccessException, + InvocationTargetException, NoSuchMethodException { // id/futureId, the identifying field is sent back from the incoming record. JSONObject returnObject = new JSONObject(); final boolean hasViolations = violations != null && !violations.isEmpty(); @@ -415,10 +480,12 @@ case CREATE: returnObject.put("futureId", recordObject.getString("id")); if (!hasViolations) { - returnObject.put("id", encodePropertyValueFromDataStore( - entityInstance, "id")); - returnObject.put("version", encodePropertyValueFromDataStore( - entityInstance, "version")); + returnObject.put("id", + encodePropertyValueFromDataStore(entityInstance, Long.class, + "id", propertyRefs)); + returnObject.put("version", + encodePropertyValueFromDataStore(entityInstance, Integer.class, + "version", propertyRefs)); } break; case DELETE: @@ -427,8 +494,9 @@ case UPDATE: returnObject.put("id", recordObject.getString("id")); if (!hasViolations) { - returnObject.put("version", encodePropertyValueFromDataStore( - entityInstance, "version")); + returnObject.put("version", + encodePropertyValueFromDataStore(entityInstance, Integer.class, + "version", propertyRefs)); } break; } @@ -464,7 +532,7 @@ Set<ConstraintViolation<Object>> violations) throws JSONException { JSONObject violationsAsJson = new JSONObject(); for (ConstraintViolation<Object> violation : violations) { - violationsAsJson.put(violation.getPropertyPath().toString(), + violationsAsJson.put(violation.getPropertyPath().toString(), violation.getMessage()); } return violationsAsJson; @@ -481,7 +549,13 @@ RequestDefinition operation; JSONObject topLevelJsonObject = new JSONObject(jsonRequestString); - String operationName = topLevelJsonObject.getString(RequestData.OPERATION_TOKEN); + String operationName = topLevelJsonObject.getString( + RequestData.OPERATION_TOKEN); + + String propertyRefsString = + topLevelJsonObject.has(RequestData.PROPERTY_REF_TOKEN) ? + topLevelJsonObject.getString(RequestData.PROPERTY_REF_TOKEN) : ""; + propertyRefs = RequestProperty.parse(propertyRefsString); if (operationName.equals(RequestFactory.SYNC)) { return sync(topLevelJsonObject.getString(RequestData.CONTENT_TOKEN)); } else { @@ -490,28 +564,35 @@ Method domainMethod = domainClass.getMethod( operation.getDomainMethodName(), operation.getParameterTypes()); if (!Modifier.isStatic(domainMethod.getModifiers())) { - throw new IllegalArgumentException("the " + domainMethod.getName() - + " is not static"); + throw new IllegalArgumentException( + "the " + domainMethod.getName() + " is not static"); } Object args[] = getObjectsFromParameterMap( - getParameterMap(topLevelJsonObject), domainMethod.getParameterTypes()); + getParameterMap(topLevelJsonObject), + domainMethod.getParameterTypes()); Object result = invokeStaticDomainMethod(domainMethod, args); if ((result instanceof List<?>) != operation.isReturnTypeList()) { - throw new IllegalArgumentException(String.format( - "Type mismatch, expected %s%s, but %s returns %s", - operation.isReturnTypeList() ? "list of " : "", - operation.getReturnType(), domainMethod, - domainMethod.getReturnType())); + throw new IllegalArgumentException( + String.format("Type mismatch, expected %s%s, but %s returns %s", + operation.isReturnTypeList() ? "list of " : "", + operation.getReturnType(), domainMethod, + domainMethod.getReturnType())); } if (result instanceof List<?>) { - return toJsonArray(operation, result); - } else if (result instanceof Number - && !(result instanceof BigDecimal || result instanceof BigInteger)) { + JSONObject envelop = new JSONObject(); + envelop.put("result", toJsonArray(operation, result)); + envelop.put("related", encodeRelatedObjectsToJson()); + return envelop; + } else if (result instanceof Number && !(result instanceof BigDecimal + || result instanceof BigInteger)) { return result; } else { + JSONObject envelop = new JSONObject(); JSONObject jsonObject = toJsonObject(operation, result); - return jsonObject; + envelop.put("result", jsonObject); + envelop.put("related", encodeRelatedObjectsToJson()); + return envelop; } } } @@ -519,12 +600,18 @@ /** * returns true if the property has been requested. TODO: use the properties * that should be coming with the request. - * - * @param p the field of entity ref + * + * @param p the field of entity ref + * @param propertyContext the root of the current dotted property reference * @return has the property value been requested */ - public boolean requestedProperty(Property<?> p) { - return !BLACK_LIST.contains(p.getName()); + public boolean requestedProperty(Property<?> p, + RequestProperty propertyContext) { + if (Record.class.isAssignableFrom(p.getType())) { + return propertyContext.hasProperty(p.getName()); + } else { + return !BLACK_LIST.contains(p.getName()); + } } public void setOperationRegistry(OperationRegistry operationRegistry) { @@ -546,8 +633,9 @@ int length = reportArray.length(); if (length == 0) { - throw new IllegalArgumentException("No json array for " - + writeOperation.name() + " should have been sent"); + throw new IllegalArgumentException( + "No json array for " + writeOperation.name() + + " should have been sent"); } for (int i = 0; i < length; i++) { JSONObject recordWithSchema = reportArray.getJSONObject(i); @@ -585,18 +673,11 @@ /** * Persist a recordObject of token "recordToken" and return useful information - * as a JSONObject to return back. - * <p> - * Example: recordToken = "Employee", entity = Employee.class, record = - * EmployeeRecord.class - *<p> - * Steps: - * <ol> - * <li>assert that each property is present in "EmployeeRecord" - * <li>invoke "findEmployee (id)" OR new Employee() - * <li>set various fields on the attached entity and persist OR remove() - * <li>return data - * </ol> + * as a JSONObject to return back. <p> Example: recordToken = "Employee", + * entity = Employee.class, record = EmployeeRecord.class <p> Steps: <ol> + * <li>assert that each property is present in "EmployeeRecord" <li>invoke + * "findEmployee (id)" OR new Employee() <li>set various fields on the + * attached entity and persist OR remove() <li>return data </ol> */ public JSONObject updateRecordInDataStore(String recordToken, JSONObject recordObject, WriteOperation writeOperation) { @@ -605,7 +686,8 @@ Class<? extends Record> record = getRecordFromClassToken(recordToken); Class<?> entity = getEntityFromRecordAnnotation(record); - Map<String, Class<?>> propertiesInRecord = getPropertiesFromRecord(record); + Map<String, Class<?>> propertiesInRecord = getPropertiesFromRecord( + record); validateKeys(recordObject, propertiesInRecord.keySet()); updatePropertyTypes(propertiesInRecord, entity); @@ -664,7 +746,7 @@ // return data back. return getReturnRecord(writeOperation, entityInstance, recordObject, - violations); + violations, record); } catch (Exception ex) { log.severe(String.format("Caught exception [%s] %s", ex.getClass().getName(), ex.getLocalizedMessage())); @@ -678,12 +760,31 @@ while (keys.hasNext()) { String key = (String) keys.next(); if (!declaredProperties.contains(key)) { - throw new IllegalArgumentException("key " + key - + " is not permitted to be set"); + throw new IllegalArgumentException( + "key " + key + " is not permitted to be set"); } } } + private void addRelatedObject(String keyRef, Object returnValue, + Class<? extends Record> propertyType, RequestProperty propertyContext) + throws JSONException, IllegalAccessException, NoSuchMethodException, + InvocationTargetException { + Class<? extends Record> clazz = + (Class<? extends Record>) returnValue.getClass(); + + relatedObjects.put(keyRef, getJsonObject(returnValue, propertyType, + propertyContext)); + } + + private JSONObject encodeRelatedObjectsToJson() throws JSONException { + JSONObject array = new JSONObject(); + for (Map.Entry<String, JSONObject> entry : relatedObjects.entrySet()) { + array.put(entry.getKey(), entry.getValue()); + } + return array; + } + @SuppressWarnings("unchecked") private Object toJsonArray(RequestDefinition operation, Object result) throws IllegalAccessException, JSONException, NoSuchMethodException, @@ -698,7 +799,7 @@ throws JSONException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { JSONObject jsonObject = getJsonObject(result, - (Class<? extends Record>) operation.getReturnType()); + (Class<? extends Record>) operation.getReturnType(), propertyRefs); return jsonObject; } }
diff --git a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java index 5cb63a6..06c7675 100644 --- a/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java +++ b/user/src/com/google/gwt/requestfactory/server/OperationRegistry.java
@@ -26,4 +26,6 @@ public interface OperationRegistry { RequestDefinition getOperation(String operationName); + + RequestSecurityProvider getSecurityProvider(); }
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java index a0deea1..14efd2b 100644 --- a/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java +++ b/user/src/com/google/gwt/requestfactory/server/ReflectionBasedOperationRegistry.java
@@ -151,6 +151,7 @@ } /** + * Turns an operation in the form of package.requestClass::method into a * RequestDefinition via reflection. */ @@ -182,6 +183,10 @@ } } + public RequestSecurityProvider getSecurityProvider() { + return securityProvider; + } + private Method findMethod(Class<?> clazz, String methodName) { for (Method method : clazz.getDeclaredMethods()) { if ((method.getModifiers() & Modifier.PUBLIC) != 0) {
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java index a52862d..72dc579 100644 --- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java +++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -20,6 +20,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -88,6 +89,8 @@ } // TODO: clean exception handling code below. } catch (Exception e) { + Logger.getLogger(getClass().getName()).severe(e.getMessage()); + e.printStackTrace(); throw new RuntimeException(e); } }
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProperty.java b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java new file mode 100644 index 0000000..7287a0b --- /dev/null +++ b/user/src/com/google/gwt/requestfactory/server/RequestProperty.java
@@ -0,0 +1,137 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.server; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * <p> <span style="color:red">Experimental API: This class is still under rapid development, and is + * very likely to be deleted. Use it at your own risk. </span> </p> Represents one piece in a + * property reference sequence. + */ +public class RequestProperty implements Iterable<RequestProperty> { + + /** + * Merge two property chains. + */ + public static RequestProperty coalesce(RequestProperty... properties) { + assert properties.length > 0; + RequestProperty root = new RequestProperty(""); + for (RequestProperty prop : properties) { + if ("".equals(prop.getPropertyName())) { + for (RequestProperty p : prop) { + root.mergeProperty(p); + } + } else { + root.mergeProperty(prop); + } + } + return root; + } + + public static RequestProperty parse(String selectors) { + String parts[] = selectors.split("\\s*,\\s*"); + RequestProperty props[] = new RequestProperty[parts.length]; + for (int i = 0; i < parts.length; i++) { + RequestProperty newProp = new RequestProperty(""); + newProp.parseInternal(parts[i]); + props[i] = newProp; + } + return props.length == 1 ? props[0] : coalesce(props); + } + + private String propertyName; + private Map<String, RequestProperty> subProperties; + + private RequestProperty(String propertyName) { + this.propertyName = propertyName; + } + + public RequestProperty add(RequestProperty propertyRef) { + if (subProperties == null) { + subProperties = new HashMap<String, RequestProperty>(); + } + subProperties.put(propertyRef.getPropertyName(), propertyRef); + return this; + } + + public RequestProperty getProperty(String propName) { + return subProperties == null ? null : subProperties.get(propName); + } + + public String getPropertyName() { + return propertyName; + } + + public boolean hasProperty(String name) { + return subProperties == null ? false : subProperties.containsKey(name); + } + + public Iterator<RequestProperty> iterator() { + return subProperties == null ? + (Iterator<RequestProperty>) Collections.EMPTY_MAP.values().iterator() : + subProperties.values().iterator(); + } + + public RequestProperty mergeProperty(RequestProperty property) { + RequestProperty foundProp = getProperty(property.getPropertyName()); + if (foundProp == null && !"".equals(property.getPropertyName())) { + add(property); + } else { + for (RequestProperty p : property) { + if (foundProp == null) { + add(p); + } else { + foundProp.mergeProperty(p); + } + } + } + return foundProp; + } + + private RequestProperty getOrCreate(String part) { + RequestProperty prop = getProperty(part); + if (prop == null) { + prop = new RequestProperty(part); + add(prop); + } + return prop; + } + + private RequestProperty parseInternal(String sequence) { + int dotIndex = sequence.indexOf('.'); + String part = dotIndex > -1 ? sequence.substring(0, dotIndex) : sequence; + RequestProperty prop = getOrCreate(part); + add(prop); + + if (dotIndex > -1) { + if (dotIndex < sequence.length() - 1) { + String next = sequence.substring(dotIndex + 1); + if ("".equals(next)) { + throw new IllegalArgumentException("Empty property name '..' not allowed in " + sequence); + } + if (next.length() > 0) { + return prop.parseInternal(next); + } + } + throw new IllegalArgumentException("Trailing '.' in with() call " + sequence); + } + return prop; + } +}
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java index 232eb85..b16dd02 100644 --- a/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java +++ b/user/src/com/google/gwt/requestfactory/server/RequestSecurityProvider.java
@@ -32,6 +32,11 @@ void checkClass(Class<?> clazz) throws SecurityException; /** + * Encode a Class type into a String token. + */ + String encodeClassType(Class<?> type); + + /** * Optionally decodes a previously encoded operation. Throws exception if * argument is not a legal operation. */
diff --git a/user/src/com/google/gwt/requestfactory/server/UserInformation.java b/user/src/com/google/gwt/requestfactory/server/UserInformation.java index 15e871d..a90243f 100644 --- a/user/src/com/google/gwt/requestfactory/server/UserInformation.java +++ b/user/src/com/google/gwt/requestfactory/server/UserInformation.java
@@ -74,7 +74,7 @@ public static UserInformation getCurrentUserInformation(String redirectUrl) { UserInformation userInfo = null; - if (!userInformationImplClass.isEmpty()) { + if (!"".equals(userInformationImplClass)) { try { userInfo = (UserInformation) Class.forName( userInformationImplClass).getConstructor(
diff --git a/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java b/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java index 7d741de..ee417a5 100644 --- a/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java +++ b/user/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
@@ -22,20 +22,20 @@ import java.util.List; /** - * <p> - * <span style="color:red">Experimental API: This class is still under rapid + * <p> <span style="color:red">Experimental API: This class is still under rapid * development, and is very likely to be deleted. Use it at your own risk. - * </span> - * </p> - * Implemented by RequestObjects for service methods that return lists of - * records. - * + * </span> </p> Implemented by RequestObjects for service methods that return + * lists of records. + * * @param <R> The type held by the returned list */ -public interface RecordListRequest<R extends Record> extends - RequestObject<List<R>> { +public interface RecordListRequest<R extends Record> + extends RequestObject<List<R>> { + RecordListRequest<R> with(String... propertyRefs); + + /** + * @deprecated use {@link #with(String...)} instead. + */ RecordListRequest<R> forProperties(Collection<Property<?>> properties); - - RecordListRequest<R> forProperty(Property<?> property); }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java b/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java index a359fb5..db93689 100644 --- a/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java +++ b/user/src/com/google/gwt/requestfactory/shared/RecordRequest.java
@@ -34,7 +34,11 @@ public interface RecordRequest<R extends Record> extends RequestObject<R> { + RecordRequest<R> with(String... propertyRefs); + + /** + * @deprecated use {@link #with(String...)} instead. + */ RecordRequest<R> forProperties(Collection<Property<?>> properties); - RecordRequest<R> forProperty(Property<?> property); }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestData.java b/user/src/com/google/gwt/requestfactory/shared/RequestData.java index 59e77ec..e6085c7 100644 --- a/user/src/com/google/gwt/requestfactory/shared/RequestData.java +++ b/user/src/com/google/gwt/requestfactory/shared/RequestData.java
@@ -16,7 +16,9 @@ package com.google.gwt.requestfactory.shared; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.Set; /** * <p> @@ -34,14 +36,19 @@ public static final String CONTENT_TOKEN = "contentData"; public static final String OPERATION_TOKEN = "operation"; public static final String PARAM_TOKEN = "param"; + public static final String PROPERTY_REF_TOKEN = "propertyRefs"; // TODO: non-final is a hack for now. private String operation; private final Object[] parameters; - public RequestData(String operation, Object[] parameters) { + private Set<String> propertyRefs; + + public RequestData(String operation, Object[] parameters, + Set<String> propertyRefs) { this.operation = operation; this.parameters = parameters; + this.propertyRefs = propertyRefs; } /** @@ -64,6 +71,18 @@ if (contentData != null) { requestMap.put(CONTENT_TOKEN, contentData); } + + if (propertyRefs != null && !propertyRefs.isEmpty()) { + StringBuffer props = new StringBuffer(); + Iterator<String> propIt = propertyRefs.iterator(); + while (propIt.hasNext()) { + props.append(propIt.next()); + if (propIt.hasNext()) { + props.append(","); + } + } + requestMap.put(PROPERTY_REF_TOKEN, props.toString()); + } return requestMap; } }
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java index c032975..dd80628 100644 --- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java +++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -27,8 +27,7 @@ * Marker interface for the RequestFactory code generator. */ public interface RequestFactory { - String JSON_CONTENT_TYPE_UTF8 = - "application/json; charset=utf-8"; + static final String JSON_CONTENT_TYPE_UTF8 = "application/json; charset=utf-8"; String SYNC = "SYNC"; @@ -36,7 +35,7 @@ String URL = "gwtRequest"; Record create(Class token); - + void init(HandlerManager eventBus); // The following methods match the format for the generated sub-interfaces
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java b/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java index d7f828f..faa4821 100644 --- a/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java +++ b/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
@@ -51,6 +51,14 @@ } throw new IllegalArgumentException("Unknown token " + recordClass); } + + public RecordSchema<? extends Record> getType( + String recordClass) { + if (recordClass.equals(SimpleFooRecord.class.getName())) { + return SimpleFooRecordImpl.SCHEMA; + } + throw new IllegalArgumentException("Unknown token " + recordClass); + } }; ValueStoreJsonImpl valueStore = null; RequestFactoryJsonImpl requestFactory = null; @@ -71,6 +79,11 @@ return create(token, typeMap); } + + public RecordSchema getSchema(String token) { + return typeMap.getType(token); + } + @Override public void init(HandlerManager eventBus) { // ignore. @@ -91,7 +104,7 @@ jso.set(SimpleFooRecord.intId, 4); jso.set(SimpleFooRecord.created, new Date()); jso.setSchema(SimpleFooRecordImpl.SCHEMA); - valueStore.setRecord(jso); + valueStore.setRecord(jso, requestFactory); } public void testCreate() { @@ -188,7 +201,7 @@ RecordImpl mockRecord = new RecordImpl(RecordJsoImpl.create(futureId, 1, SimpleFooRecordImpl.SCHEMA), RequestFactoryJsonImpl.NOT_FUTURE); - valueStore.setRecord(mockRecord.asJso()); // marked as non-future.. + valueStore.setRecord(mockRecord.asJso(), requestFactory); // marked as non-future.. DeltaValueStoreJsonImpl deltaValueStore = new DeltaValueStoreJsonImpl( valueStore, requestFactory);
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java b/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java index 1017275..6c77dc1 100644 --- a/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java +++ b/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooRecordImpl.java
@@ -18,6 +18,7 @@ import com.google.gwt.valuestore.shared.Property; import com.google.gwt.valuestore.shared.Record; import com.google.gwt.valuestore.shared.RecordChangedEvent; +import com.google.gwt.valuestore.shared.SimpleBarRecord; import com.google.gwt.valuestore.shared.SimpleFooRecord; import com.google.gwt.valuestore.shared.WriteOperation; @@ -80,10 +81,14 @@ super(jso, isFuture); } + public SimpleBarRecord getBarField() { + return (SimpleBarRecord) getValueStore().getRecordBySchemaAndId(SCHEMA, (Long) (Object) get(barField)); + } + public Boolean getBoolField() { return get(boolField); } - + public java.util.Date getCreated() { return get(created); }
diff --git a/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java b/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java index 82b527f..682f0e7 100644 --- a/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java +++ b/user/test/com/google/gwt/requestfactory/server/JsonRequestProcessorTest.java
@@ -95,12 +95,13 @@ com.google.gwt.valuestore.server.SimpleFoo.reset(); try { // fetch object - JSONObject foo = (JSONObject) requestProcessor.processJsonRequest("{ \"" + JSONObject results = (JSONObject) requestProcessor.processJsonRequest("{ \"" + RequestData.OPERATION_TOKEN + "\": \"" + com.google.gwt.valuestore.shared.SimpleFooRequest.class.getName() + ReflectionBasedOperationRegistry.SCOPE_SEPARATOR + "findSimpleFooById\", " + "\"" + RequestData.PARAM_TOKEN + "0\": \"999\" }"); + JSONObject foo = results.getJSONObject("result"); assertEquals(foo.getInt("id"), 999); assertEquals(foo.getInt("intId"), 42); assertEquals(foo.getString("userName"), "GWT");
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java b/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java new file mode 100644 index 0000000..e08ca68 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/server/RequestPropertyTest.java
@@ -0,0 +1,41 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.server; + +import junit.framework.TestCase; + +/** + * Tests for {@link com.google.gwt.requestfactory.server.RequestProperty} . + */ +public class RequestPropertyTest extends TestCase { + + public void testParseMultipleSelector() { + RequestProperty prop = RequestProperty.parse("supervisor.name, supervisor.age"); + RequestProperty sup = prop.getProperty("supervisor"); + assertNotNull(sup); + RequestProperty name = sup.getProperty("name"); + assertNotNull(name); + RequestProperty age = sup.getProperty("age"); + assertNotNull(name); + } + public void testParseSingleSelector() { + RequestProperty prop = RequestProperty.parse("supervisor.name"); + RequestProperty sup = prop.getProperty("supervisor"); + assertNotNull(sup); + RequestProperty name = sup.getProperty("name"); + assertNotNull(name); + } +} \ No newline at end of file
diff --git a/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java b/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java index b6b2001..132646d 100644 --- a/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java +++ b/user/test/com/google/gwt/valuestore/client/RequestFactoryTest.java
@@ -48,6 +48,27 @@ assertEquals(8L, (long) response.getLongField()); assertEquals(com.google.gwt.valuestore.shared.SimpleEnum.FOO, response.getEnumField()); + assertEquals(null, response.getBarField()); + finishTest(); + } + }); + } + + public void testFetchEntityWithRelation() { + SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); + HandlerManager hm = new HandlerManager(null); + req.init(hm); + delayTestFinish(5000); + req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire( + new Receiver<SimpleFooRecord>() { + public void onSuccess(SimpleFooRecord response, + Set<SyncResult> syncResult) { + assertEquals(42, (int) response.getIntId()); + assertEquals("GWT", response.getUserName()); + assertEquals(8L, (long) response.getLongField()); + assertEquals(com.google.gwt.valuestore.shared.SimpleEnum.FOO, + response.getEnumField()); + assertNotNull(response.getBarField()); finishTest(); } });
diff --git a/user/test/com/google/gwt/valuestore/server/SimpleBar.java b/user/test/com/google/gwt/valuestore/server/SimpleBar.java new file mode 100644 index 0000000..8d2e908 --- /dev/null +++ b/user/test/com/google/gwt/valuestore/server/SimpleBar.java
@@ -0,0 +1,93 @@ +/* + * 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.valuestore.server; + +import com.google.gwt.requestfactory.shared.Id; + +import java.util.Collections; +import java.util.List; + +/** + * Domain object for SimpleFooRequest. + */ +public class SimpleBar { + + static SimpleBar singleton = new SimpleBar(); + + public static Long countSimpleBar() { + return 1L; + } + + public static List<SimpleBar> findAll() { + return Collections.singletonList(singleton); + } + + public static SimpleBar findSimpleBar(Long id) { + return findSimpleBarById(id); + } + + public static SimpleBar findSimpleBarById(Long id) { + singleton.setId(id); + return singleton; + } + + public static SimpleBar getSingleton() { + return singleton; + } + + public static void reset() { + singleton = new SimpleBar(); + } + + Integer version = 1; + + @Id + private Long id = -1L; + + private String userName; + + public SimpleBar() { + version = 1; + userName = "FOO"; + } + + public Long getId() { + return id; + } + + public String getUserName() { + return userName; + } + + public Integer getVersion() { + return version; + } + + public void persist() { + } + + public void setId(Long id) { + this.id = id; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public void setVersion(Integer version) { + this.version = version; + } +}
diff --git a/user/test/com/google/gwt/valuestore/server/SimpleFoo.java b/user/test/com/google/gwt/valuestore/server/SimpleFoo.java index 6de9282..6c7ac35 100644 --- a/user/test/com/google/gwt/valuestore/server/SimpleFoo.java +++ b/user/test/com/google/gwt/valuestore/server/SimpleFoo.java
@@ -36,6 +36,7 @@ public static List<SimpleFoo> findAll() { return Collections.singletonList(singleton); } + public static SimpleFoo findSimpleFoo(Long id) { return findSimpleFooById(id); } @@ -51,11 +52,13 @@ public static void reset() { singleton = new SimpleFoo(); - }; + } + @SuppressWarnings("unused") private static Integer privateMethod() { return 0; } + Integer version = 1; private Boolean boolField; @@ -70,6 +73,8 @@ private String userName; + private SimpleBar barField; + public SimpleFoo() { intId = 42; version = 1; @@ -77,9 +82,14 @@ longField = 8L; enumField = SimpleEnum.FOO; created = new Date(); + barField = SimpleBar.getSingleton(); boolField = true; } + public SimpleBar getBarField() { + return barField; + } + public Boolean getBoolField() { return boolField; } @@ -115,6 +125,10 @@ public void persist() { } + public void setBarField(SimpleBar barField) { + this.barField = barField; + } + public void setBoolField(Boolean bool) { boolField = bool; }
diff --git a/user/test/com/google/gwt/valuestore/shared/SimpleBarRecord.java b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecord.java new file mode 100644 index 0000000..86e85b7 --- /dev/null +++ b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecord.java
@@ -0,0 +1,33 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.valuestore.shared; + +import com.google.gwt.requestfactory.shared.DataTransferObject; +import com.google.gwt.valuestore.server.SimpleBar; + +/** + * A simple entity used for testing. Has an int field and date field. Add other data types as their + * support gets built in. + */ +@DataTransferObject(SimpleBar.class) +public interface SimpleBarRecord extends Record { + + Property<String> userName = new Property<String>("userName", "User Name", + String.class); + + String getUserName(); + +} \ No newline at end of file
diff --git a/user/test/com/google/gwt/valuestore/shared/SimpleBarRecordChanged.java b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecordChanged.java new file mode 100644 index 0000000..55b68c0 --- /dev/null +++ b/user/test/com/google/gwt/valuestore/shared/SimpleBarRecordChanged.java
@@ -0,0 +1,50 @@ +/* + * 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.valuestore.shared; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Test implementation of {@link com.google.gwt.valuestore.shared.RecordChangedEvent} for + * {@link com.google.gwt.valuestore.shared.SimpleFooRecord}. + */ +public class SimpleBarRecordChanged extends + RecordChangedEvent<SimpleBarRecord, SimpleBarRecordChanged.Handler> { + +/** + * Test Handler for SimpleFooChanged event. + */ + public interface Handler extends EventHandler { + void onSimpleBarRecordChanged(SimpleBarRecordChanged record); + } + + public static final Type<Handler> TYPE = new Type<Handler>(); + + public SimpleBarRecordChanged(SimpleBarRecord record, + WriteOperation writeOperation) { + super(record, writeOperation); + } + + @Override + public Type<Handler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(Handler handler) { + handler.onSimpleBarRecordChanged(this); + } +}
diff --git a/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java b/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java index 02b09ff..e0f8c44 100644 --- a/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java +++ b/user/test/com/google/gwt/valuestore/shared/SimpleFooRecord.java
@@ -39,18 +39,23 @@ new EnumProperty<com.google.gwt.valuestore.shared.SimpleEnum>("enumField", com.google.gwt.valuestore.shared.SimpleEnum.class, SimpleEnum.values()); + Property<SimpleBarRecord> barField = new Property<SimpleBarRecord>("barField", + SimpleBarRecord.class); + + SimpleBarRecord getBarField(); + Boolean getBoolField(); Date getCreated(); + com.google.gwt.valuestore.shared.SimpleEnum getEnumField(); + Integer getIntId(); + Long getLongField(); + String getPassword(); String getUserName(); - Long getLongField(); - - com.google.gwt.valuestore.shared.SimpleEnum getEnumField(); - -} \ No newline at end of file +}