Commit b040d1a2 authored by iwakeh's avatar iwakeh Committed by Karsten Loesing
Browse files

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.
parent bc33d034
/* 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;
}
}
......@@ -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();
}
}
/* 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;
@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);
}
}
......@@ -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]));
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment