[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] [metrics-web/master 1/2] Move graph parameter checking to its own class.



Author: Karsten Loesing <karsten.loesing@xxxxxxx>
Date: Sun, 17 Oct 2010 14:49:21 +0200
Subject: Move graph parameter checking to its own class.
Commit: e9c9803ac0f1737da392a3b8c3092aed0bcadf38

---
 .../torproject/ernie/web/GraphImageServlet.java    |  242 ++------------------
 .../ernie/web/GraphParameterChecker.java           |  243 ++++++++++++++++++++
 2 files changed, 257 insertions(+), 228 deletions(-)
 create mode 100644 src/org/torproject/ernie/web/GraphParameterChecker.java

diff --git a/src/org/torproject/ernie/web/GraphImageServlet.java b/src/org/torproject/ernie/web/GraphImageServlet.java
index c45f57c..add55c7 100644
--- a/src/org/torproject/ernie/web/GraphImageServlet.java
+++ b/src/org/torproject/ernie/web/GraphImageServlet.java
@@ -15,45 +15,7 @@ public class GraphImageServlet extends HttpServlet {
 
   private GraphController graphController;
 
-  private SimpleDateFormat dateFormat;
-
-  /* Available graphs with corresponding parameter lists. */
-  private Map<String, String> availableGraphs;
-
-  /* Known parameters and parameter values. */
-  private Map<String, String> knownParameterValues;
-
   public void init() {
-    this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-    this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-
-    this.availableGraphs = new HashMap<String, String>();
-    this.availableGraphs.put("networksize", "start,end,filename");
-    this.availableGraphs.put("relayflags", "start,end,flag,filename");
-    this.availableGraphs.put("relayflags-hour",
-        "start,end,flag,filename");
-    this.availableGraphs.put("versions", "start,end,filename");
-    this.availableGraphs.put("platforms", "start,end,filename");
-    this.availableGraphs.put("bandwidth", "start,end,filename");
-    this.availableGraphs.put("dirbytes", "start,end,filename");
-    this.availableGraphs.put("new-users", "start,end,country,filename");
-    this.availableGraphs.put("direct-users",
-        "start,end,country,filename");
-    this.availableGraphs.put("bridge-users",
-         "start,end,country,filename");
-    this.availableGraphs.put("gettor", "start,end,bundle,filename");
-    this.availableGraphs.put("torperf",
-         "start,end,source,filesize,filename");
-
-    this.knownParameterValues = new HashMap<String, String>();
-    this.knownParameterValues.put("flag",
-        "Running,Exit,Guard,Fast,Stable");
-    this.knownParameterValues.put("country", "all,au,bh,br,ca,cn,cu,de,"
-        + "et,fr,gb,ir,it,jp,kr,mm,pl,ru,sa,se,sy,tn,tm,us,uz,vn,ye");
-    this.knownParameterValues.put("bundle", "all,en,zh_CN,fa");
-    this.knownParameterValues.put("source", "siv,moria,torperf");
-    this.knownParameterValues.put("filesize", "50kb,1mb,5mb");
-
     ServletConfig servletConfig = getServletConfig();
     String rserveHost = servletConfig.getInitParameter("rserveHost");
     String rservePort = servletConfig.getInitParameter("rservePort");
@@ -83,208 +45,32 @@ public class GraphImageServlet extends HttpServlet {
       requestedGraph = requestedGraph.substring(requestedGraph.
           lastIndexOf("/") + 1);
     }
-    if (!this.availableGraphs.containsKey(requestedGraph)) {
+
+    /* Check parameters. */
+    Map<String, String[]> checkedParameters = GraphParameterChecker.
+        getInstance().checkParameters(requestedGraph,
+        request.getParameterMap());
+    if (checkedParameters == null) {
       response.sendError(HttpServletResponse.SC_BAD_REQUEST);
       return;
     }
 
-    /* Find out which other parameters are supported by this graph type
-     * and parse them if they are given. */
-    Set<String> supportedGraphParameters = new HashSet<String>(Arrays.
-        asList(this.availableGraphs.get(requestedGraph).split(",")));
-    Map<String, String[]> recognizedGraphParameters =
-        new HashMap<String, String[]>();
-
-    /* Parse start and end dates if supported by the graph type. If
-     * neither start nor end date are given, set the default date range to
-     * the past 90 days. */
-    if (supportedGraphParameters.contains("start") ||
-        supportedGraphParameters.contains("end")) {
-      String startParameter = request.getParameter("start");
-      String endParameter = request.getParameter("end");
-      if (startParameter == null && endParameter == null) {
-        /* If no start and end parameters are given, set default date
-         * range to the past 90 days. */
-        long now = System.currentTimeMillis();
-        startParameter = dateFormat.format(now
-            - 90L * 24L * 60L * 60L * 1000L);
-        endParameter = dateFormat.format(now);
-      } else if (startParameter != null && endParameter != null) {
-        long startTimestamp = -1L, endTimestamp = -1L;
-        try {
-          startTimestamp = dateFormat.parse(startParameter).getTime();
-          endTimestamp = dateFormat.parse(endParameter).getTime();
-        } catch (ParseException e)  {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-        /* The parameters are dates. Good. Does end not precede start? */
-        if (startTimestamp > endTimestamp) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-        /* And while we're at it, make sure both parameters lie in this
-         * century. */
-        if (!startParameter.startsWith("20") ||
-            !endParameter.startsWith("20")) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-        /* Looks like sane parameters. Re-format them to get a canonical
-         * version, not something like 2010-1-1, 2010-01-1, etc. */
-        startParameter = dateFormat.format(startTimestamp);
-        endParameter = dateFormat.format(endTimestamp);
-      } else {
-        /* Either none or both of start and end need to be set. */
-        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-        return;
-      }
-      recognizedGraphParameters.put("start",
-          new String[] { startParameter });
-      recognizedGraphParameters.put("end", new String[] { endParameter });
-    }
-
-    /* Parse relay flags if supported by the graph type. If no relay flags
-     * are passed or none of them have been recognized, use the set of all
-     * known flags as default. */
-    if (supportedGraphParameters.contains("flag")) {
-      String[] flagParameters = request.getParameterValues("flag");
-      List<String> knownFlags = Arrays.asList(
-          this.knownParameterValues.get("flag").split(","));
-      if (flagParameters != null) {
-        for (String flag : flagParameters) {
-          if (flag == null || flag.length() == 0 ||
-              !knownFlags.contains(flag)) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-            return;
-          }
-        }
-      } else {
-        flagParameters = this.knownParameterValues.get("flag").split(",");
-      }
-      recognizedGraphParameters.put("flag", flagParameters);
-    }
-
-    /* Parse country codes if supported by the graph type. If no countries
-     * are passed, use country code "all" (all countries) as default. */
-    if (supportedGraphParameters.contains("country")) {
-      String[] countryParameters = request.getParameterValues("country");
-      List<String> knownCountries = Arrays.asList(
-          this.knownParameterValues.get("country").split(","));
-      if (countryParameters != null) {
-        for (String country : countryParameters) {
-          if (country == null || country.length() == 0 ||
-              !knownCountries.contains(country)) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-            return;
-          }
-        }
-      } else {
-        countryParameters = new String[] { "all" };
-      }
-      recognizedGraphParameters.put("country", countryParameters);
-    }
-
-    /* Parse GetTor bundle if supported by the graph type. Only a single
-     * bundle can be passed. If no bundle is passed, use "all" as
-     * default. */
-    if (supportedGraphParameters.contains("bundle")) {
-      String[] bundleParameter = request.getParameterValues("bundle");
-      List<String> knownBundles = Arrays.asList(
-          this.knownParameterValues.get("bundle").split(","));
-      if (bundleParameter != null) {
-        if (bundleParameter.length != 1) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-        if (bundleParameter[0].length() == 0 ||
-            !knownBundles.contains(bundleParameter[0])) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-      } else {
-        bundleParameter = new String[] { "all" };
-      }
-      recognizedGraphParameters.put("bundle", bundleParameter);
-    }
-
-    /* Parse torperf data source if supported by the graph type. Only a
-     * single source can be passed. If no source is passed, use "torperf"
-     * as default. */
-    if (supportedGraphParameters.contains("source")) {
-      String[] sourceParameter = request.getParameterValues("source");
-      List<String> knownSources = Arrays.asList(
-          this.knownParameterValues.get("source").split(","));
-      if (sourceParameter != null) {
-        if (sourceParameter.length != 1) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-        if (sourceParameter[0].length() == 0 ||
-            !knownSources.contains(sourceParameter[0])) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-      } else {
-        sourceParameter = new String[] { "torperf" };
-      }
-      recognizedGraphParameters.put("source", sourceParameter);
-    }
-
-    /* Parse torperf file size if supported by the graph type. Only a
-     * single file size can be passed. If no file size is passed, use
-     * "50kb" as default. */
-    if (supportedGraphParameters.contains("filesize")) {
-      String[] filesizeParameter = request.getParameterValues("filesize");
-      List<String> knownFilesizes = Arrays.asList(
-          this.knownParameterValues.get("filesize").split(","));
-      if (filesizeParameter != null) {
-        if (filesizeParameter.length != 1) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-        if (filesizeParameter[0].length() == 0 ||
-            !knownFilesizes.contains(filesizeParameter[0])) {
-          response.sendError(HttpServletResponse.SC_BAD_REQUEST);
-          return;
-        }
-      } else {
-        filesizeParameter = new String[] { "50kb" };
-      }
-      recognizedGraphParameters.put("filesize", filesizeParameter);
-    }
-
     /* Prepare filename and R query string. */
     StringBuilder rQueryBuilder = new StringBuilder("plot_"
         + requestedGraph.replaceAll("-", "_") + "("),
         imageFilenameBuilder = new StringBuilder(requestedGraph);
-    List<String> requiredGraphParameters = Arrays.asList(
-        this.availableGraphs.get(requestedGraph).split(","));
-    for (String graphParameter : requiredGraphParameters) {
-      if (graphParameter.equals("filename")) {
-        break;
-      }
-      if (!recognizedGraphParameters.containsKey(graphParameter)) {
-        /* We should have parsed this parameter above! */
-        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
-            "Missing parameter: " + graphParameter);
-        return;
-      }
-      String[] parameterValues = recognizedGraphParameters.get(
-          graphParameter);
-      if (parameterValues.length == 0) {
-        /* We should not have added a zero-length array here! */
-        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
-            "Missing parameter: " + graphParameter);
-        return;
-      }
+    for (Map.Entry<String, String[]> parameter :
+        checkedParameters.entrySet()) {
+      String parameterName = parameter.getKey();
+      String[] parameterValues = parameter.getValue();
       for (String param : parameterValues) {
         imageFilenameBuilder.append("-" + param);
       }
       if (parameterValues.length < 2) {
-        rQueryBuilder.append("'" + parameterValues[0] + "', ");
+        rQueryBuilder.append(parameterName + " = '" + parameterValues[0]
+            + "', ");
       } else {
-        rQueryBuilder.append("c(");
+        rQueryBuilder.append(parameterName + " = c(");
         for (int i = 0; i < parameterValues.length - 1; i++) {
           rQueryBuilder.append("'" + parameterValues[i] + "', ");
         }
@@ -294,7 +80,7 @@ public class GraphImageServlet extends HttpServlet {
     }
     imageFilenameBuilder.append(".png");
     String imageFilename = imageFilenameBuilder.toString();
-    rQueryBuilder.append("'%s')");
+    rQueryBuilder.append("path = '%s')");
     String rQuery = rQueryBuilder.toString();
 
     /* Request graph from graph controller, which either returns it from
diff --git a/src/org/torproject/ernie/web/GraphParameterChecker.java b/src/org/torproject/ernie/web/GraphParameterChecker.java
new file mode 100644
index 0000000..2bbbaf9
--- /dev/null
+++ b/src/org/torproject/ernie/web/GraphParameterChecker.java
@@ -0,0 +1,243 @@
+package org.torproject.ernie.web;
+
+import java.text.*;
+import java.util.*;
+
+/**
+ * Checks request parameters passed to graph-generating servlets.
+ */
+public class GraphParameterChecker {
+
+  /**
+   * Singleton instance of this class.
+   */
+  private static GraphParameterChecker instance =
+      new GraphParameterChecker();
+
+  /**
+   * Returns the singleton instance of this class.
+   */
+  public static GraphParameterChecker getInstance() {
+    return instance;
+  }
+
+  /* Date format for parsing start and end dates. */
+  private SimpleDateFormat dateFormat;
+
+  /* Available graphs with corresponding parameter lists. */
+  private Map<String, String> availableGraphs;
+
+  /* Known parameters and parameter values. */
+  private Map<String, String> knownParameterValues;
+
+  /**
+   * Initializes map with valid parameters for each of the graphs.
+   */
+  public GraphParameterChecker() {
+    this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+    this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+    this.availableGraphs = new HashMap<String, String>();
+    this.availableGraphs.put("networksize", "start,end,filename");
+    this.availableGraphs.put("relayflags", "start,end,flag,filename");
+    this.availableGraphs.put("relayflags-hour",
+        "start,end,flag,filename");
+    this.availableGraphs.put("versions", "start,end,filename");
+    this.availableGraphs.put("platforms", "start,end,filename");
+    this.availableGraphs.put("bandwidth", "start,end,filename");
+    this.availableGraphs.put("dirbytes", "start,end,filename");
+    this.availableGraphs.put("new-users", "start,end,country,filename");
+    this.availableGraphs.put("direct-users",
+        "start,end,country,filename");
+    this.availableGraphs.put("bridge-users",
+         "start,end,country,filename");
+    this.availableGraphs.put("gettor", "start,end,bundle,filename");
+    this.availableGraphs.put("torperf",
+         "start,end,source,filesize,filename");
+
+    this.knownParameterValues = new HashMap<String, String>();
+    this.knownParameterValues.put("flag",
+        "Running,Exit,Guard,Fast,Stable");
+    this.knownParameterValues.put("country", "all,au,bh,br,ca,cn,cu,de,"
+        + "et,fr,gb,ir,it,jp,kr,mm,pl,ru,sa,se,sy,tn,tm,us,uz,vn,ye");
+    this.knownParameterValues.put("bundle", "all,en,zh_CN,fa");
+    this.knownParameterValues.put("source", "siv,moria,torperf");
+    this.knownParameterValues.put("filesize", "50kb,1mb,5mb");
+  }
+
+  /**
+   * Checks request parameters for the given graph type and returns a map
+   * of recognized parameters, or null if the graph type doesn't exist or
+   * the parameters are invalid.
+   */
+  public Map<String, String[]> checkParameters(String graphType,
+      Map requestParameters) {
+
+    /* Check if the graph type exists. */
+    if (graphType == null ||
+        !this.availableGraphs.containsKey(graphType)) {
+      return null;
+    }
+
+    /* Find out which other parameters are supported by this graph type
+     * and parse them if they are given. */
+    Set<String> supportedGraphParameters = new HashSet<String>(Arrays.
+        asList(this.availableGraphs.get(graphType).split(",")));
+    Map<String, String[]> recognizedGraphParameters =
+        new HashMap<String, String[]>();
+
+    /* Parse start and end dates if supported by the graph type. If
+     * neither start nor end date are given, set the default date range to
+     * the past 90 days. */
+    if (supportedGraphParameters.contains("start") ||
+        supportedGraphParameters.contains("end")) {
+      String[] startParameter = (String[]) requestParameters.get("start");
+      String[] endParameter = (String[]) requestParameters.get("end");
+      if (startParameter == null && endParameter == null) {
+        /* If no start and end parameters are given, set default date
+         * range to the past 90 days. */
+        long now = System.currentTimeMillis();
+        startParameter = new String[] {
+            dateFormat.format(now - 90L * 24L * 60L * 60L * 1000L) };
+        endParameter = new String[] { dateFormat.format(now) };
+      } else if (startParameter != null && startParameter.length == 1 &&
+          endParameter != null && endParameter.length == 1) {
+        long startTimestamp = -1L, endTimestamp = -1L;
+        try {
+          startTimestamp = dateFormat.parse(startParameter[0]).getTime();
+          endTimestamp = dateFormat.parse(endParameter[0]).getTime();
+        } catch (ParseException e)  {
+          return null;
+        }
+        /* The parameters are dates. Good. Does end not precede start? */
+        if (startTimestamp > endTimestamp) {
+          return null;
+        }
+        /* And while we're at it, make sure both parameters lie in this
+         * century. */
+        if (!startParameter[0].startsWith("20") ||
+            !endParameter[0].startsWith("20")) {
+          return null;
+        }
+        /* Looks like sane parameters. Re-format them to get a canonical
+         * version, not something like 2010-1-1, 2010-01-1, etc. */
+        startParameter = new String[] {
+            dateFormat.format(startTimestamp) };
+        endParameter = new String[] { dateFormat.format(endTimestamp) };
+      } else {
+        /* Either none or both of start and end need to be set. */
+        return null;
+      }
+      recognizedGraphParameters.put("start", startParameter);
+      recognizedGraphParameters.put("end", endParameter);
+    }
+
+    /* Parse relay flags if supported by the graph type. If no relay flags
+     * are passed or none of them have been recognized, use the set of all
+     * known flags as default. */
+    if (supportedGraphParameters.contains("flag")) {
+      String[] flagParameters = (String[]) requestParameters.get("flag");
+      List<String> knownFlags = Arrays.asList(
+          this.knownParameterValues.get("flag").split(","));
+      if (flagParameters != null) {
+        for (String flag : flagParameters) {
+          if (flag == null || flag.length() == 0 ||
+              !knownFlags.contains(flag)) {
+            return null;
+          }
+        }
+      } else {
+        flagParameters = this.knownParameterValues.get("flag").split(",");
+      }
+      recognizedGraphParameters.put("flag", flagParameters);
+    }
+
+    /* Parse country codes if supported by the graph type. If no countries
+     * are passed, use country code "all" (all countries) as default. */
+    if (supportedGraphParameters.contains("country")) {
+      String[] countryParameters = (String[]) requestParameters.get(
+          "country");
+      List<String> knownCountries = Arrays.asList(
+          this.knownParameterValues.get("country").split(","));
+      if (countryParameters != null) {
+        for (String country : countryParameters) {
+          if (country == null || country.length() == 0 ||
+              !knownCountries.contains(country)) {
+            return null;
+          }
+        }
+      } else {
+        countryParameters = new String[] { "all" };
+      }
+      recognizedGraphParameters.put("country", countryParameters);
+    }
+
+    /* Parse GetTor bundle if supported by the graph type. Only a single
+     * bundle can be passed. If no bundle is passed, use "all" as
+     * default. */
+    if (supportedGraphParameters.contains("bundle")) {
+      String[] bundleParameter = (String[]) requestParameters.get(
+          "bundle");
+      List<String> knownBundles = Arrays.asList(
+          this.knownParameterValues.get("bundle").split(","));
+      if (bundleParameter != null) {
+        if (bundleParameter.length != 1 ||
+            bundleParameter[0].length() == 0 ||
+            !knownBundles.contains(bundleParameter[0])) {
+          return null;
+        }
+      } else {
+        bundleParameter = new String[] { "all" };
+      }
+      recognizedGraphParameters.put("bundle", bundleParameter);
+    }
+
+    /* Parse torperf data source if supported by the graph type. Only a
+     * single source can be passed. If no source is passed, use "torperf"
+     * as default. */
+    if (supportedGraphParameters.contains("source")) {
+      String[] sourceParameter = (String[]) requestParameters.get(
+          "source");
+      List<String> knownSources = Arrays.asList(
+          this.knownParameterValues.get("source").split(","));
+      if (sourceParameter != null) {
+        if (sourceParameter.length != 1) {
+          return null;
+        }
+        if (sourceParameter[0].length() == 0 ||
+            !knownSources.contains(sourceParameter[0])) {
+          return null;
+        }
+      } else {
+        sourceParameter = new String[] { "torperf" };
+      }
+      recognizedGraphParameters.put("source", sourceParameter);
+    }
+
+    /* Parse torperf file size if supported by the graph type. Only a
+     * single file size can be passed. If no file size is passed, use
+     * "50kb" as default. */
+    if (supportedGraphParameters.contains("filesize")) {
+      String[] filesizeParameter = (String[]) requestParameters.get(
+          "filesize");
+      List<String> knownFilesizes = Arrays.asList(
+          this.knownParameterValues.get("filesize").split(","));
+      if (filesizeParameter != null) {
+        if (filesizeParameter.length != 1) {
+          return null;
+        }
+        if (filesizeParameter[0].length() == 0 ||
+            !knownFilesizes.contains(filesizeParameter[0])) {
+          return null;
+        }
+      } else {
+        filesizeParameter = new String[] { "50kb" };
+      }
+      recognizedGraphParameters.put("filesize", filesizeParameter);
+    }
+
+    /* We now have a map with all required graph parameters. Return it. */
+    return recognizedGraphParameters;
+  }
+}
+
-- 
1.7.1