Config class no longer so monolithic.
Review at http://gwt-code-reviews.appspot.com/310802

Review by: amitmanjhi@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7882 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index b78b6fc..5d5a3de 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -16,6 +16,7 @@
 package com.google.gwt.requestfactory.server;
 
 import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.RequestFactory.Config;
 import com.google.gwt.requestfactory.shared.RequestFactory.RequestDefinition;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
 import com.google.gwt.valuestore.shared.Property;
@@ -43,15 +44,16 @@
 import javax.servlet.http.HttpServletResponse;
 
 /**
- * Handles GWT RequestFactory requests. Configured via servlet context param
- * servlet.serverOperation, which must be set to the fully qualified name of an
- * enum implementing {@link RequestDefinition}.
+ * Handles GWT RequestFactory JSON requests. Configured via servlet context
+ * param <code>servlet.serverOperation</code>, which must be set to the name of a default
+ * instantiable class implementing
+ * com.google.gwt.requestfactory.shared.RequestFactory.Config.
  * <p>
  * e.g.
  * 
  * <pre>  &lt;context-param>
     &lt;param-name>servlet.serverOperation&lt;/param-name>
-    &lt;param-value>com.google.gwt.sample.expenses.shared.ExpenseRequestFactory$ServerSideOperation&lt;/param-value>
+    &lt;param-value>com.myco.myapp.MyAppServerSideOperations&lt;/param-value>
   &lt;/context-param>
 
  * </pre>
@@ -69,6 +71,8 @@
     }
   }
 
+  private Config config;
+
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
       throws IOException {
@@ -83,8 +87,7 @@
         sync(topLevelJsonObject.getString(RequestDataManager.CONTENT_TOKEN),
             writer);
       } else {
-
-        operation = getOperationFromName(operationName, getConfigClass());
+        operation = getOperation(operationName);
         Class<?> domainClass = Class.forName(operation.getDomainClassName());
         Method domainMethod = domainClass.getMethod(
             operation.getDomainMethodName(), operation.getParameterTypes());
@@ -122,8 +125,9 @@
   }
 
   /**
-   * Allows subclass to provide hack implementation. TODO real reflection based
-   * implsementation.
+   * Allows subclass to provide hack implementation.
+   * <p>
+   * TODO real reflection based implementation.
    */
   @SuppressWarnings("unused")
   protected void sync(String content, PrintWriter writer) {
@@ -131,26 +135,50 @@
   }
 
   @SuppressWarnings("unchecked")
-  private Class<? extends RequestDefinition> getConfigClass() {
-    try {
-      final String serverOperation = getServletContext().getInitParameter(
-          SERVER_OPERATION_CONTEXT_PARAM);
-      if (null == serverOperation) {
-        throw new IllegalStateException(String.format(
-            "Context parameter \"%s\" must name an enum implementing %s",
-            SERVER_OPERATION_CONTEXT_PARAM, RequestDefinition.class.getName()));
+  private void ensureConfig() {
+    if (config == null) {
+      synchronized (this) {
+        if (config != null) {
+          return;
+        }
+        try {
+          final String serverOperation = getServletContext().getInitParameter(
+              SERVER_OPERATION_CONTEXT_PARAM);
+          if (null == serverOperation) {
+            failConfig();
+          }
+          Class<?> clazz = Class.forName(serverOperation);
+          if (Config.class.isAssignableFrom(clazz)) {
+            config = ((Class<? extends Config>) clazz).newInstance();
+          }
+
+        } catch (ClassNotFoundException e) {
+          failConfig(e);
+        } catch (InstantiationException e) {
+          failConfig(e);
+        } catch (IllegalAccessException e) {
+          failConfig(e);
+        } catch (SecurityException e) {
+          failConfig(e);
+        } catch (ClassCastException e) {
+          failConfig(e);
+        }
       }
-      Class<?> clazz = Class.forName(serverOperation);
-      if (!RequestDefinition.class.isAssignableFrom(clazz)) {
-        throw new RuntimeException(serverOperation + " must implement "
-            + RequestDefinition.class.getName());
-      }
-      return (Class<? extends RequestDefinition>) clazz;
-    } catch (ClassNotFoundException e) {
-      throw new RuntimeException(e);
     }
   }
 
+  private void failConfig() {
+    failConfig(null);
+  }
+
+  private void failConfig(Throwable e) {
+    final String message = String.format("Context parameter \"%s\" must name "
+        + "a default instantiable configuration class implementing %s",
+        SERVER_OPERATION_CONTEXT_PARAM, RequestFactory.Config.class.getName());
+
+    throw new IllegalStateException(message, e);
+  }
+
   private String getContent(HttpServletRequest request) throws IOException {
     int contentLength = request.getContentLength();
     byte contentBytes[] = new byte[contentLength];
@@ -211,16 +239,15 @@
     return methodName.toString();
   }
 
-  private RequestDefinition getOperationFromName(String operationName,
-      Class<? extends RequestDefinition> enumClass) throws SecurityException,
-      IllegalAccessException, InvocationTargetException, NoSuchMethodException {
-    for (RequestDefinition operation : ((RequestDefinition[]) enumClass.getMethod(
-        "values").invoke(null))) {
-      if (operation.name().equals(operationName)) {
-        return operation;
-      }
+  private RequestDefinition getOperation(String operationName) {
+    RequestDefinition operation;
+    ensureConfig();
+    operation = config.requestDefinitions().get(operationName);
+    if (null == operation) {
+      throw new IllegalArgumentException("Unknown operation "
+          + operationName);
     }
-    throw new IllegalArgumentException("Unknown operation " + operationName);
+    return operation;
   }
 
   /**
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 1cdb8bd..d5bdf28 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -16,50 +16,32 @@
 package com.google.gwt.requestfactory.shared;
 
 import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.requestfactory.server.RequestFactoryServlet;
 import com.google.gwt.valuestore.shared.DeltaValueStore;
 import com.google.gwt.valuestore.shared.ValueStore;
 import com.google.gwt.valuestore.shared.ValuesKey;
 
+import java.util.Map;
+
 /**
  * Marker interface for the RequestFactory code generator.
  */
 public interface RequestFactory {
 
-  String URL = "/expenses/data";
-
-  /*
-   * eventually, this will become an enum of update operations.
-   */
-  String UPDATE_STRING = "SYNC";
-
   /**
-   * Implemented by the request objects created by this factory.
+   * Implemented by the configuration class used by
+   * {@link RequestFactoryServlet}.
    */
-  interface RequestObject {
-    void fire();
-
-    String getRequestData();
-
-    void handleResponseText(String responseText);
+  interface Config {
+    Map<String, RequestDefinition> requestDefinitions();
   }
 
-  ValueStore getValueStore();
-
-  void init(HandlerManager handlerManager);
-
-  SyncRequest syncRequest(DeltaValueStore deltaValueStore);
-
   /**
-   * Implemented by the enum that defines the mapping between request objects
-   * and service methods.
+   * Implemented by enums that defines the mapping between request objects and
+   * service methods.
    */
   interface RequestDefinition {
     /**
-     * Returns the name.
-     */
-    String name();
-
-    /**
      * Returns the name of the (domain) class that contains the method to be
      * invoked on the server.
      */
@@ -79,5 +61,34 @@
      * Returns the return type of the method to be invoked on the server.
      */
     Class<? extends ValuesKey<?>> getReturnType();
+
+    /**
+     * Returns the name.
+     */
+    String name();
   }
+
+  /**
+   * Implemented by the request objects created by this factory.
+   */
+  interface RequestObject {
+    void fire();
+
+    String getRequestData();
+
+    void handleResponseText(String responseText);
+  }
+
+  String URL = "/expenses/data";
+
+  /*
+   * eventually, this will become an enum of update operations.
+   */
+  String UPDATE_STRING = "SYNC";
+
+  ValueStore getValueStore();
+
+  void init(HandlerManager handlerManager);
+
+  SyncRequest syncRequest(DeltaValueStore deltaValueStore);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
index f84426c..b8b3b7e 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
@@ -16,12 +16,33 @@
 package com.google.gwt.sample.expenses.gwt.request;
 
 import com.google.gwt.requestfactory.shared.EntityListRequest;
+import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.ServerOperation;
+import com.google.gwt.valuestore.shared.ValuesKey;
 
 /**
  * Request selector.
  */
 public interface EmployeeRequest {
+  public enum ServerOperations implements RequestFactory.RequestDefinition {
+    FIND_ALL_EMPLOYEES {
+      public String getDomainMethodName() {
+        return "findAllEmployees";
+      }
+
+      public Class<? extends ValuesKey<?>> getReturnType() {
+        return com.google.gwt.sample.expenses.gwt.request.EmployeeKey.class;
+      }
+    };
+
+    public String getDomainClassName() {
+      return "com.google.gwt.sample.expenses.server.domain.Employee";
+    }
+
+    public Class<?>[] getParameterTypes() {
+      return null;
+    }
+  }
 
   /**
    * @return a request object
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
index 7f10ff6..03f4718 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
@@ -15,67 +15,38 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
+import com.google.gwt.requestfactory.shared.RequestFactory.Config;
 import com.google.gwt.requestfactory.shared.RequestFactory.RequestDefinition;
-import com.google.gwt.valuestore.shared.ValuesKey;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
- * Represents the server side operation to be carried out. This enum will be
- * generated by the JPA-aware tool.
+ * Configuration class for
+ * {@link com.google.gwt.requestfactory.server.RequestFactoryServlet
+ * RequestFactoryServlet}
  */
-public enum ExpensesServerSideOperations implements RequestDefinition {
-  FIND_ALL_EMPLOYEES("com.google.gwt.sample.expenses.server.domain.Employee",
-      "findAllEmployees", null,
-      com.google.gwt.sample.expenses.gwt.request.EmployeeKey.class), //
-  FIND_ALL_REPORTS("com.google.gwt.sample.expenses.server.domain.Report",
-      "findAllReports", null,
-      com.google.gwt.sample.expenses.gwt.request.ReportKey.class), //
-  FIND_REPORTS_BY_EMPLOYEE(
-      "com.google.gwt.sample.expenses.server.domain.Report",
-      "findReportsByEmployee", new Class[] {java.lang.Long.class},
-      com.google.gwt.sample.expenses.gwt.request.ReportKey.class); //
+public class ExpensesServerSideOperations implements Config {
 
-  /**
-   * the server side domain class.
-   */
-  private final String domainClassName;
-
-  /**
-   * the methodName of the domain class that is to be invoked.
-   */
-  private final String domainMethodName;
-
-  /**
-   * the parameterTypes of the parameters the domain method requires.
-   */
-  private final Class<?>[] parameterTypes;
-
-  /**
-   * for "READ" methods, the methods return a List. This class denotes the types
-   * of the elements of the list.
-   */
-  private final Class<? extends ValuesKey<?>> returnType;
-
-  private ExpensesServerSideOperations(String domainClassName, String domainMethodName,
-      Class<?> parameterTypes[], Class<? extends ValuesKey<?>> entryReturnType) {
-    this.domainClassName = domainClassName;
-    this.domainMethodName = domainMethodName;
-    this.parameterTypes = parameterTypes;
-    this.returnType = entryReturnType;
+  private static void putAll(RequestDefinition[] values,
+      Map<String, RequestDefinition> newMap) {
+    for (RequestDefinition def : values) {
+      newMap.put(def.name(), def);
+    }
   }
 
-  public String getDomainClassName() {
-    return domainClassName;
+  private final Map<String, RequestDefinition> map;
+
+  public ExpensesServerSideOperations() {
+    Map<String, RequestDefinition> newMap = new HashMap<String, RequestDefinition>();
+    putAll(EmployeeRequest.ServerOperations.values(), newMap);
+    putAll(ReportRequest.ServerOperations.values(), newMap);
+    map = Collections.unmodifiableMap(newMap);
   }
 
-  public String getDomainMethodName() {
-    return domainMethodName;
+  public Map<String, RequestDefinition> requestDefinitions() {
+    return map;
   }
 
-  public Class<?>[] getParameterTypes() {
-    return parameterTypes;
-  }
-
-  public Class<? extends ValuesKey<?>> getReturnType() {
-    return returnType;
-  }
-}
\ No newline at end of file
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
index 4be2dab..75857ec 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
@@ -17,13 +17,48 @@
 
 import com.google.gwt.requestfactory.shared.EntityListRequest;
 import com.google.gwt.requestfactory.shared.LongString;
+import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.ServerOperation;
 import com.google.gwt.valuestore.shared.ValueRef;
+import com.google.gwt.valuestore.shared.ValuesKey;
 
 /**
  * Request selector.
  */
 public interface ReportRequest {
+  public enum ServerOperations implements RequestFactory.RequestDefinition {
+    FIND_REPORTS_BY_EMPLOYEE {
+      public String getDomainMethodName() {
+        return "findReportsByEmployee";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[] { java.lang.Long.class };
+      }
+
+      public Class<? extends ValuesKey<?>> getReturnType() {
+        return com.google.gwt.sample.expenses.gwt.request.ReportKey.class;
+      }
+    },
+
+    FIND_ALL_REPORTS {
+      public String getDomainMethodName() {
+        return "findAllReports";
+      }
+
+      public Class<? extends ValuesKey<?>> getReturnType() {
+        return com.google.gwt.sample.expenses.gwt.request.ReportKey.class;
+      }
+    };
+
+    public String getDomainClassName() {
+      return "com.google.gwt.sample.expenses.server.domain.Report";
+    }
+
+    public Class<?>[] getParameterTypes() {
+      return null;
+    }
+  }
 
   /**
    * @return a request object
diff --git a/bikeshed/war/WEB-INF/web.xml b/bikeshed/war/WEB-INF/web.xml
index bb3088e..17718a8 100644
--- a/bikeshed/war/WEB-INF/web.xml
+++ b/bikeshed/war/WEB-INF/web.xml
@@ -8,7 +8,7 @@
   <!--  Configures expensesData servlet -->
   <context-param>
     <param-name>servlet.serverOperation</param-name>
-    <param-value>com.google.gwt.sample.expenses.gwt.request.ExpensesRequests</param-value>
+    <param-value>com.google.gwt.sample.expenses.gwt.request.ExpensesServerSideOperations</param-value>
   </context-param>
 
   <!-- Servlets -->