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.

Reply via email to