commit b040d1a28de7e65b7f16c3d529aa205d750d6d3c
Author: iwakeh <iwakeh(a)torproject.org>
Date: Mon Dec 11 13:58:03 2017 +0000
Introduce a date class encapsulating all calculations related to dates.
Also use LocalDate for date parsing; turn tests into parametrized tests
and add tests for checking validity of parameters.
---
.../metrics/exonerator/ExoneraTorDate.java | 47 +++++++++
.../metrics/exonerator/ExoneraTorServlet.java | 110 +++++++--------------
.../metrics/exonerator/ExoneraTorDateTest.java | 80 +++++++++++++++
.../metrics/exonerator/ExoneraTorServletTest.java | 29 ------
4 files changed, 163 insertions(+), 103 deletions(-)
diff --git a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDate.java b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDate.java
new file mode 100644
index 0000000..723ffdd
--- /dev/null
+++ b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDate.java
@@ -0,0 +1,47 @@
+/* Copyright 2017 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.exonerator;
+
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeParseException;
+
+class ExoneraTorDate {
+
+ public static final ExoneraTorDate INVALID = new ExoneraTorDate("");
+ private static final int DATELENGTH = "yyyy-mm-dd".length();
+
+ final boolean empty;
+ final boolean valid;
+ final String asString;
+ final LocalDate date;
+ final String asRequested;
+ final boolean tooRecent;
+
+ ExoneraTorDate(String parameter) {
+ this.asRequested = parameter;
+ this.empty = null == parameter || parameter.trim().isEmpty();
+ this.date = empty ? null : parseDatestamp(parameter);
+ this.valid = null != date;
+ this.asString = valid ? date.format(ISO_LOCAL_DATE) : "";
+ this.tooRecent = valid
+ && date.isAfter(LocalDate.now(ZoneOffset.UTC).minusDays(2));
+ }
+
+ private static LocalDate parseDatestamp(String datestamp) {
+ String trimmedDatestamp = datestamp.replaceAll("\\s", "");
+ if (trimmedDatestamp.length() >= DATELENGTH) {
+ try {
+ return LocalDate
+ .parse(trimmedDatestamp.substring(0, DATELENGTH), ISO_LOCAL_DATE);
+ } catch (DateTimeParseException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java
index 1ee857b..8464f50 100644
--- a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorServlet.java
@@ -3,8 +3,6 @@
package org.torproject.metrics.exonerator;
-import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
-
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
@@ -15,10 +13,6 @@ import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.time.LocalDate;
-import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -26,7 +20,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.SortedMap;
-import java.util.TimeZone;
import java.util.TreeMap;
import java.util.regex.Pattern;
@@ -77,11 +70,8 @@ public class ExoneraTorServlet extends HttpServlet {
final boolean relayIpHasError = relayIp == null;
/* Parse timestamp parameter. */
- String timestampParameter = request.getParameter("timestamp");
- String timestampStr = parseTimestampParameter(timestampParameter);
- final boolean timestampHasError = timestampStr == null;
- final boolean timestampTooRecent = !timestampHasError
- && checkTimestampTooRecent(timestampStr);
+ ExoneraTorDate requestedDate
+ = new ExoneraTorDate(request.getParameter("timestamp"));
/* Parse lang parameter. */
String langParameter = request.getParameter("lang");
@@ -94,20 +84,21 @@ public class ExoneraTorServlet extends HttpServlet {
/* Step 2: Query the backend server. */
boolean successfullyConnectedToBackend = false;
- String firstDate = null;
- String lastDate = null;
+ ExoneraTorDate firstDate = ExoneraTorDate.INVALID;
+ ExoneraTorDate lastDate = ExoneraTorDate.INVALID;
boolean noRelevantConsensuses = true;
List<String[]> statusEntries = new ArrayList<>();
List<String> addressesInSameNetwork = null;
/* Only query, if we received valid user input. */
- if (null != relayIp && !relayIp.isEmpty() && null != timestampStr
- && !timestampStr.isEmpty() && !timestampTooRecent) {
- QueryResponse queryResponse = this.queryBackend(relayIp, timestampStr);
+ if (null != relayIp && !relayIp.isEmpty()
+ && requestedDate.valid && !requestedDate.tooRecent) {
+ QueryResponse queryResponse
+ = this.queryBackend(relayIp, requestedDate.asString);
if (null != queryResponse) {
successfullyConnectedToBackend = true;
- firstDate = queryResponse.firstDateInDatabase;
- lastDate = queryResponse.lastDateInDatabase;
+ firstDate = new ExoneraTorDate(queryResponse.firstDateInDatabase);
+ lastDate = new ExoneraTorDate(queryResponse.lastDateInDatabase);
if (null != queryResponse.relevantStatuses
&& queryResponse.relevantStatuses) {
noRelevantConsensuses = false;
@@ -148,19 +139,19 @@ public class ExoneraTorServlet extends HttpServlet {
this.writeHeader(out, rb, langStr);
/* Write form. */
- boolean timestampOutOfRange = null != timestampStr
- && (null != firstDate && timestampStr.compareTo(firstDate) < 0
- || (null != lastDate && timestampStr.compareTo(lastDate) > 0));
+ boolean timestampOutOfRange = requestedDate.valid
+ && (firstDate.valid && requestedDate.date.isBefore(firstDate.date)
+ || (lastDate.valid && requestedDate.date.isAfter(lastDate.date)));
this.writeForm(out, rb, relayIp, relayIpHasError
- || ("".equals(relayIp) && !"".equals(timestampStr)), timestampStr,
- !relayIpHasError
- && !("".equals(relayIp) && !"".equals(timestampStr))
- && (timestampHasError || timestampOutOfRange
- || (!"".equals(relayIp) && "".equals(timestampStr))), langStr);
+ || ("".equals(relayIp) && !requestedDate.empty),
+ requestedDate.asString, !relayIpHasError
+ && !("".equals(relayIp) && !requestedDate.valid)
+ && (!requestedDate.valid || timestampOutOfRange
+ || (!"".equals(relayIp) && requestedDate.empty)), langStr);
/* If both parameters are empty, don't print any summary and exit.
* This is the start page. */
- if ("".equals(relayIp) && "".equals(timestampStr)) {
+ if ("".equals(relayIp) && requestedDate.empty) {
this.writeFooter(out, rb, null, null);
/* If only one parameter is empty and the other is not, print summary
@@ -168,7 +159,7 @@ public class ExoneraTorServlet extends HttpServlet {
} else if ("".equals(relayIp)) {
this.writeSummaryNoIp(out, rb);
this.writeFooter(out, rb, null, null);
- } else if ("".equals(timestampStr)) {
+ } else if (requestedDate.empty) {
this.writeSummaryNoTimestamp(out, rb);
this.writeFooter(out, rb, null, null);
@@ -177,14 +168,14 @@ public class ExoneraTorServlet extends HttpServlet {
} else if (relayIpHasError) {
this.writeSummaryInvalidIp(out, rb, ipParameter);
this.writeFooter(out, rb, null, null);
- } else if (timestampHasError) {
- this.writeSummaryInvalidTimestamp(out, rb, timestampParameter);
+ } else if (!requestedDate.valid) {
+ this.writeSummaryInvalidTimestamp(out, rb, requestedDate.asRequested);
this.writeFooter(out, rb, null, null);
/* If the timestamp is too recent, print summary with error message and
* exit. */
- } else if (timestampTooRecent) {
- this.writeSummaryTimestampTooRecent(out, rb, timestampStr);
+ } else if (requestedDate.tooRecent) {
+ this.writeSummaryTimestampTooRecent(out, rb, requestedDate.asString);
this.writeFooter(out, rb, null, null);
/* If we were unable to connect to the database,
@@ -195,35 +186,36 @@ public class ExoneraTorServlet extends HttpServlet {
/* Similarly, if we found the database to be empty,
* write an error message, too. */
- } else if (null == firstDate || null == lastDate) {
+ } else if (firstDate.empty || lastDate.empty) {
this.writeSummaryNoData(out, rb);
this.writeFooter(out, rb, null, null);
/* If the requested date is out of range, tell the user. */
} else if (timestampOutOfRange) {
- this.writeSummaryTimestampOutsideRange(out, rb, timestampStr,
- firstDate, lastDate);
- this.writeFooter(out, rb, relayIp, timestampStr);
+ this.writeSummaryTimestampOutsideRange(out, rb, requestedDate.asString,
+ firstDate.asString, lastDate.asString);
+ this.writeFooter(out, rb, relayIp, requestedDate.asString);
} else if (noRelevantConsensuses) {
this.writeSummaryNoDataForThisInterval(out, rb);
- this.writeFooter(out, rb, relayIp, timestampStr);
+ this.writeFooter(out, rb, relayIp, requestedDate.asString);
/* Print out result. */
} else {
if (!statusEntries.isEmpty()) {
- this.writeSummaryPositive(out, rb, relayIp, timestampStr);
- this.writeTechnicalDetails(out, rb, relayIp, timestampStr,
+ this.writeSummaryPositive(out, rb, relayIp, requestedDate.asString);
+ this.writeTechnicalDetails(out, rb, relayIp, requestedDate.asString,
statusEntries);
} else if (addressesInSameNetwork != null
&& !addressesInSameNetwork.isEmpty()) {
this.writeSummaryAddressesInSameNetwork(out, rb, relayIp,
- timestampStr, langStr, addressesInSameNetwork);
+ requestedDate.asString, langStr, addressesInSameNetwork);
} else {
- this.writeSummaryNegative(out, rb, relayIp, timestampStr);
+ this.writeSummaryNegative(out, rb, relayIp, requestedDate.asString);
}
- this.writePermanentLink(out, rb, relayIp, timestampStr, langStr);
- this.writeFooter(out, rb, relayIp, timestampStr);
+ this.writePermanentLink(out, rb, relayIp, requestedDate.asString,
+ langStr);
+ this.writeFooter(out, rb, relayIp, requestedDate.asString);
}
/* Forward to the JSP that adds header and footer. */
@@ -298,35 +290,6 @@ public class ExoneraTorServlet extends HttpServlet {
return relayIp;
}
- /** Parse a timestamp parameter and return either a non-<code>null</code>
- * value in case the parameter was valid or empty, or <code>null</code> if it
- * was non-empty and invalid. */
- static String parseTimestampParameter(String passedTimestampParameter) {
- String timestampStr = "";
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- dateFormat.setLenient(false);
- if (passedTimestampParameter != null
- && passedTimestampParameter.length() > 0) {
- String timestampParameter = passedTimestampParameter.trim();
- try {
- long timestamp = dateFormat.parse(timestampParameter).getTime();
- timestampStr = dateFormat.format(timestamp);
- } catch (ParseException e) {
- timestampStr = null;
- }
- }
- return timestampStr;
- }
-
- /** Return whether the timestamp parameter is too recent, which is the case if
- * it matches the day before the current system date (in UTC) or is even
- * younger. */
- static boolean checkTimestampTooRecent(String timestampParameter) {
- return LocalDate.parse(timestampParameter, ISO_LOCAL_DATE)
- .isAfter(LocalDate.now(ZoneOffset.UTC).minusDays(2));
- }
-
/* Helper method for fetching a query response via URL. */
private QueryResponse queryBackend(String relayIp, String timestampStr) {
@@ -698,4 +661,3 @@ public class ExoneraTorServlet extends HttpServlet {
out.close();
}
}
-
diff --git a/src/test/java/org/torproject/metrics/exonerator/ExoneraTorDateTest.java b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorDateTest.java
new file mode 100644
index 0000000..79690e7
--- /dev/null
+++ b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorDateTest.java
@@ -0,0 +1,80 @@
+/* Copyright 2017 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.metrics.exonerator;
+
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.Collection;
+
+(a)RunWith(Parameterized.class)
+public class ExoneraTorDateTest {
+
+ /** All test data. */
+ @Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { // input, output
+ {"2000- 10-10", LocalDate.parse("2000-10-10", ISO_LOCAL_DATE),
+ false, true},
+ {"2010-12-16 +0001", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2010-12-16 CEST", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2010-12-16abcd", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2010-12-16", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2000-10-10 12:10:00", LocalDate.parse("2000-10-10", ISO_LOCAL_DATE),
+ false, true},
+ {"2000-10-10 1210-04-05",
+ LocalDate.parse("2000-10-10", ISO_LOCAL_DATE), false, true},
+ {"20.10.16", null, false, false},
+ {null, null, true, false},
+ {"", null, true, false},
+ {"2010-12 16", null, false, false},
+ {"2010-\t12-\t16", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2010- 12- \t16", LocalDate.parse("2010-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2003-12-\t16", LocalDate.parse("2003-12-16", ISO_LOCAL_DATE),
+ false, true},
+ {"2004-10-14\t", LocalDate.parse("2004-10-14", ISO_LOCAL_DATE),
+ false, true},
+ {"\n2005-10-12\t\t", LocalDate.parse("2005-10-12", ISO_LOCAL_DATE),
+ false, true},
+ {" 2001-10-13 ", LocalDate.parse("2001-10-13", ISO_LOCAL_DATE),
+ false, true}
+ });
+ }
+
+ @Parameter(0)
+ public String dateParameter;
+
+ @Parameter(1)
+ public LocalDate expectedDate;
+
+ @Parameter(2)
+ public boolean empty;
+
+ @Parameter(3)
+ public boolean valid;
+
+ @Test
+ public void testTimestampParsing() {
+ ExoneraTorDate date = new ExoneraTorDate(dateParameter);
+ assertEquals("Input data: " + dateParameter, expectedDate, date.date);
+ assertEquals("Input data: " + dateParameter, empty, date.empty);
+ assertEquals("Input data: " + dateParameter, valid, date.valid);
+ }
+
+}
+
diff --git a/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java
index 16592c3..fc7e8e3 100644
--- a/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java
+++ b/src/test/java/org/torproject/metrics/exonerator/ExoneraTorServletTest.java
@@ -32,34 +32,5 @@ public class ExoneraTorServletTest {
assertEquals(data[1], ExoneraTorServlet.parseIpParameter(data[0]));
}
}
-
- private static final String[][] timestampTestData
- = { // input, output
- {"2000- 10-10", "2000-10-10"},
- {"2010-12-16 +0001", "2010-12-16"},
- {"2010-12-16 CEST", "2010-12-16"},
- {"2010-12-16abcd", "2010-12-16"},
- {"2010-12-16", "2010-12-16"},
- {"2000-10-10 12:10:00", "2000-10-10"},
- {"2000-10-10 1210-04-05", "2000-10-10"},
- {"20.10.16", null},
- {null, ""},
- {"", ""},
- {"2010-12 16", null},
- {"2010-\t12-\t16", "2010-12-16"},
- {"2010- 12- \t16", "2010-12-16"},
- {"2003-12-\t16", "2003-12-16"},
- {"2004-10-10\t", "2004-10-10"},
- {"\n2005-10-10\t\t", "2005-10-10"},
- {" 2001-10-10 ", "2001-10-10"}
- };
-
- @Test
- public void testTimestampParsing() {
- for (String[] data : timestampTestData) {
- assertEquals(data[1], ExoneraTorServlet.parseTimestampParameter(data[0]));
- }
- }
-
}