Revision: 7747
Author: amitman...@google.com
Date: Thu Mar 18 15:14:08 2010
Log: Use reflection on the server side to have a shared code for handling
all GET
requests. The output of these requests is assumed to be a List of Entity.
This list is then converted automatically to a JSON string and sent over the
wire.
Patch by: amitmanjhi
Review by: rjrjr
Review at http://gwt-code-reviews.appspot.com/240801
http://code.google.com/p/google-web-toolkit/source/detail?r=7747
Added:
/trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/UrlParameterManager.java
Modified:
/trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/MethodName.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/ReportRequestImpl.java
/trunk/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
=======================================
--- /dev/null
+++
/trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/UrlParameterManager.java
Thu Mar 18 15:14:08 2010
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gen;
+
+import java.util.Map;
+
+/**
+ * An utitlity class to manage the encoding and decoding of parameters.
+ *
+ * TODO: add appropriate unit tests.
+ */
+public class UrlParameterManager {
+
+ private static final String TOKEN = "param";
+
+ public static Object[] getObjectsFromFragment(
+ Map<String, String[]> parameterMap, Class<?> parameterClasses[]) {
+ assert parameterClasses != null;
+ Object args[] = new Object[parameterClasses.length];
+ for (int i = 0; i < parameterClasses.length; i++) {
+ args[i] = encodeParameterValue(parameterClasses[i].getName(),
+ parameterMap.get("param" + i));
+ }
+ return args;
+ }
+
+ /**
+ * Returns the string that encodes the values. The string has a leading
&.
+ *
+ * @param values
+ * @return
+ */
+ public static String getUrlFragment(Object values[]) {
+ assert values != null;
+ StringBuffer fragment = new StringBuffer();
+ for (int i = 0; i < values.length; i++) {
+ Object value = values[i];
+ fragment.append("&");
+ fragment.append(TOKEN);
+ fragment.append(i);
+ fragment.append("=");
+ fragment.append(value.toString());
+ }
+ return fragment.toString();
+ }
+
+ /**
+ * Encodes parameter value.
+ *
+ */
+ private static Object encodeParameterValue(String parameterType,
+ String parameterValues[]) {
+ assert parameterValues != null;
+ assert parameterValues.length == 1;
+ String parameterValue = parameterValues[0];
+ if ("java.lang.String".equals(parameterType)) {
+ return parameterValue;
+ }
+ if ("java.lang.Integer".equals(parameterType)
+ || "int".equals(parameterType)) {
+ return new Integer(parameterValue);
+ }
+ if ("java.lang.Long".equals(parameterType) |
| "long".equals(parameterType)) {
+ return new Long(parameterValue);
+ }
+ throw new IllegalArgumentException("Unknown parameter type: "
+ + parameterType);
+ }
+}
=======================================
--- /trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/MethodName.java
Wed Mar 17 12:54:18 2010
+++ /trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/MethodName.java
Thu Mar 18 15:14:08 2010
@@ -17,7 +17,37 @@
/**
* Represents the MethodName.
+ *
+ * TODO: Remove this class in preference to RequestFactory interfaces or
+ * generate automatically from RequestFactory interfaces.
*/
public enum MethodName {
- FIND_ALL_EMPLOYEES, FIND_ALL_REPORTS, FIND_EMPLOYEE,
FIND_REPORTS_BY_EMPLOYEE, SYNC,
-}
+ FIND_ALL_EMPLOYEES("Employee", "findAllEmployees"), FIND_ALL_REPORTS(
+ "Report", "findAllReports"),
FIND_EMPLOYEE("Employee", "findEmployee"), FIND_REPORTS_BY_EMPLOYEE(
+ "Report", "findReportsByEmployee"), SYNC("", "");
+
+ /* the className that contains the method */
+ private final String className;
+
+ /* the methodName */
+ private final String methodName;
+
+ private MethodName(String className, String methodName) {
+ this.className = className;
+ this.methodName = methodName;
+ }
+
+ /**
+ * @return the className
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * @return the methodName
+ */
+ public String getMethodName() {
+ return methodName;
+ }
+}
=======================================
---
/trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/ReportRequestImpl.java
Wed Mar 17 12:54:18 2010
+++
/trunk/bikeshed/src/com/google/gwt/sample/expenses/gen/ReportRequestImpl.java
Thu Mar 18 15:14:08 2010
@@ -57,13 +57,14 @@
public void fire() {
- // TODO: need some way to track that this request has been issued
so that
- // we don't issue another request that arrives while we are
waiting for
- // the response.
- RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
+ // TODO: need some way to track that this request has been issued
so
+ // that we don't issue another request that arrives while we are
waiting
+ // for the response.
+ RequestBuilder builder = new RequestBuilder(
+ RequestBuilder.GET,
"/expenses/data?methodName="
- + MethodName.FIND_REPORTS_BY_EMPLOYEE.name() + "&id="
- + id.getEntity().getId());
+ + MethodName.FIND_REPORTS_BY_EMPLOYEE.name()
+ + UrlParameterManager.getUrlFragment(new Object[]
{id.getEntity().getId()}));
builder.setCallback(new RequestCallback() {
public void onError(Request request, Throwable exception) {
=======================================
---
/trunk/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
Wed Mar 17 12:54:18 2010
+++
/trunk/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
Thu Mar 18 15:14:08 2010
@@ -16,7 +16,7 @@
package com.google.gwt.sample.expenses.server;
import com.google.gwt.sample.expenses.gen.MethodName;
-import com.google.gwt.sample.expenses.server.domain.Employee;
+import com.google.gwt.sample.expenses.gen.UrlParameterManager;
import com.google.gwt.sample.expenses.server.domain.Report;
import com.google.gwt.sample.expenses.server.domain.Storage;
@@ -27,6 +27,14 @@
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -36,33 +44,68 @@
*
*/
public class ExpensesDataServlet extends HttpServlet {
+
+ // TODO: Remove this hack
+ private static final Set<String> PROPERTY_SET = new HashSet<String>();
+ static {
+ for (String str : new String[] {
+ "ID", "VERSION", "DISPLAY_NAME", "USER_NAME", "PURPOSE", "CREATED"})
{
+ PROPERTY_SET.add(str);
+ }
+ }
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse
response)
throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
-
- MethodName methodName =
getMethodName(request.getParameter("methodName"));
PrintWriter writer = response.getWriter();
- switch (methodName) {
- case FIND_ALL_EMPLOYEES:
- findAllEmployees(writer);
- break;
- case FIND_ALL_REPORTS:
- findAllReports(writer);
- break;
- case FIND_EMPLOYEE:
- // TODO
- break;
- case FIND_REPORTS_BY_EMPLOYEE:
- findReportsByEmployee(request, writer);
- break;
- default:
- System.err.println("Unknown method " + methodName);
- break;
- }
- writer.flush();
+ MethodName operation =
getMethodName(request.getParameter("methodName"));
+ try {
+ Class<?> classOperation =
Class.forName("com.google.gwt.sample.expenses.server.domain."
+ + operation.getClassName());
+ Method methodOperation = null;
+ // TODO: check if method names must be unique in a class.
+ for (Method method : classOperation.getDeclaredMethods()) {
+ if (method.getName().equals(operation.getMethodName())) {
+ methodOperation = method;
+ break;
+ }
+ }
+ if (methodOperation == null) {
+ throw new IllegalArgumentException("unable to find "
+ + operation.getMethodName() + " in " + classOperation);
+ }
+ if (!Modifier.isStatic(methodOperation.getModifiers())) {
+ throw new IllegalArgumentException("the " +
methodOperation.getName()
+ + " is not static");
+ }
+ Map<String, String[]> parameterMap = request.getParameterMap();
+ Object args[] =
UrlParameterManager.getObjectsFromFragment(parameterMap,
+ methodOperation.getParameterTypes());
+ Object resultList = methodOperation.invoke(null, args);
+ if (!(resultList instanceof List)) {
+ throw new IllegalArgumentException("return value not a list "
+ + resultList);
+ }
+ JSONArray jsonArray = getJsonArray((List<?>) resultList);
+ writer.print(jsonArray.toString());
+ writer.flush();
+ // TODO: clean exception handling code below.
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("unable to load the class: "
+ + operation);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException(e);
+ } catch (SecurityException e) {
+ throw new IllegalArgumentException(e);
+ } catch (JSONException e) {
+ throw new IllegalArgumentException(e);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(e);
+ }
}
@Override
@@ -83,49 +126,43 @@
writer.flush();
}
- private void findAllEmployees(PrintWriter writer) {
+ /**
+ * Converts the returnValue of a 'get' method to a JSONArray.
+ *
+ * @param resultObject object returned by a 'get' method, must be of type
+ * List<? extends Entity>
+ * @return the JSONArray
+ * @throws ClassNotFoundException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws NoSuchMethodException
+ * @throws JSONException
+ * @throws SecurityException
+ */
+ private JSONArray getJsonArray(List<?> resultList)
+ throws ClassNotFoundException, SecurityException, JSONException,
+ NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
JSONArray jsonArray = new JSONArray();
- for (Employee e : Employee.findAllEmployees()) {
- try {
- // TODO should only be getting requested properties
- // TODO clearly there should be centralized code for these
conversions
- JSONObject jsonObject = new JSONObject();
- jsonObject.put(
- com.google.gwt.sample.expenses.shared.EmployeeRef.ID.getName(),
- Long.toString(e.getId()));
- jsonObject.put(
-
com.google.gwt.sample.expenses.shared.EmployeeRef.VERSION.getName(),
- e.getVersion().intValue());
- jsonObject.put(
-
com.google.gwt.sample.expenses.shared.EmployeeRef.USER_NAME.getName(),
- e.getUserName());
- jsonObject.put(
-
com.google.gwt.sample.expenses.shared.EmployeeRef.DISPLAY_NAME.getName(),
- e.getDisplayName());
- jsonArray.put(jsonObject);
- } catch (JSONException ex) {
- System.err.println("Unable to create a JSON object " + ex);
- }
- }
- writer.print(jsonArray.toString());
- }
-
- private void findAllReports(PrintWriter writer) {
- JSONArray jsonArray = new JSONArray();
- for (Report r : Report.findAllReports()) {
- reportToJson(jsonArray, r);
- }
- writer.print(jsonArray.toString());
- }
-
- private void findReportsByEmployee(HttpServletRequest request,
- PrintWriter writer) {
- JSONArray jsonArray = new JSONArray();
- Long id = Long.valueOf(request.getParameter("id"));
- for (Report r : Report.findReportsByEmployee(id)) {
- reportToJson(jsonArray, r);
- }
- writer.print(jsonArray.toString());
+ if (resultList.size() == 0) {
+ return jsonArray;
+ }
+ Object firstElement = resultList.get(0);
+ Class<?> entityClass = firstElement.getClass();
+ Class<?> entityRefClass =
Class.forName("com.google.gwt.sample.expenses.shared."
+ + entityClass.getSimpleName() + "Ref");
+ for (Object entityElement : resultList) {
+ JSONObject jsonObject = new JSONObject();
+ for (Field field : entityRefClass.getFields()) {
+ // TODO: perhaps get the property names from javax.persistence.Id
fields
+ if (isProperty(field) && requestedProperty(field)) {
+ String propertyName = getPropertyName(field);
+ jsonObject.put(propertyName, getPropertyValue(entityElement,
+ propertyName));
+ }
+ }
+ jsonArray.put(jsonObject);
+ }
+ return jsonArray;
}
/**
@@ -142,30 +179,85 @@
}
/**
- * @param jsonArray
- * @param r
+ * Returns methodName corresponding to the propertyName that can be
invoked on
+ * an {...@link Entity} object.
+ *
+ * Example: "USER_NAME" returns "getUserName". "VERSION"
returns "getVersion"
*/
- private void reportToJson(JSONArray jsonArray, Report r) {
- try {
- // TODO should only be getting requested properties
- // TODO clearly there should be centralized code for these
conversions
- JSONObject jsonObject = new JSONObject();
- jsonObject.put(
- com.google.gwt.sample.expenses.shared.EmployeeRef.ID.getName(),
- Long.toString(r.getId()));
- jsonObject.put(
-
com.google.gwt.sample.expenses.shared.ReportRef.VERSION.getName(),
- r.getVersion().intValue());
- jsonObject.put(
-
com.google.gwt.sample.expenses.shared.ReportRef.CREATED.getName(),
- Double.valueOf(r.getCreated().getTime()));
- jsonObject.put(
-
com.google.gwt.sample.expenses.shared.ReportRef.PURPOSE.getName(),
- r.getPurpose());
- jsonArray.put(jsonObject);
- } catch (JSONException ex) {
- System.err.println("Unable to create a JSON object " + ex);
- }
+ private String getMethodNameFromPropertyName(String propertyName) {
+ assert propertyName != null;
+ assert propertyName.equals(propertyName.toUpperCase());
+ StringBuffer methodName = new StringBuffer("get");
+ int index = 0;
+ int length = propertyName.length();
+ while (index < length) {
+ int underscore = propertyName.indexOf('_', index);
+ if (underscore == -1) {
+ underscore = length;
+ }
+ methodName.append(propertyName.charAt(index));
+ methodName.append(propertyName.substring(index + 1,
underscore).toLowerCase());
+ index = underscore + 1; // skip the '_' character.
+ }
+ return methodName.toString();
+ }
+
+ /**
+ * returns the property name.
+ */
+ private String getPropertyName(Field field) {
+ return field.getName();
+ }
+
+ /**
+ * @param entityElement
+ * @param property
+ * @return
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ */
+ private Object getPropertyValue(Object entityElement, String
propertyName)
+ throws SecurityException, NoSuchMethodException,
IllegalAccessException,
+ InvocationTargetException {
+ String methodName = getMethodNameFromPropertyName(propertyName);
+ Method method = entityElement.getClass().getMethod(methodName);
+ Object returnValue = method.invoke(entityElement);
+ /*
+ * TODO: make these conventions more prominent. 1. encoding long as
String
+ * 2. encoding Date as Double
+ */
+ if (returnValue instanceof java.lang.Long) {
+ return returnValue.toString();
+ }
+ if (returnValue instanceof java.util.Date) {
+ return new Double(((java.util.Date) returnValue).getTime());
+ }
+ return returnValue;
+ }
+
+ /**
+ * @param field
+ * @return
+ */
+ private boolean isProperty(Field field) {
+ int modifiers = field.getModifiers();
+ if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)
+ || !Modifier.isPublic(modifiers)) {
+ return false;
+ }
+ return "Property".equals(field.getType().getSimpleName());
+ }
+
+ /**
+ * returns true if the property has been requested. TODO: fix this hack.
+ *
+ * @param field the field of entity ref
+ * @return has the property value been requested
+ */
+ private boolean requestedProperty(Field field) {
+ return PROPERTY_SET.contains(field.getName());
}
/**
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors
To unsubscribe from this group, send email to
google-web-toolkit-contributors+unsubscribegooglegroups.com or reply to this email with
the words "REMOVE ME" as the subject.