[tor-commits] [exonerator/master] Use Java 8 date-time functionality.

karsten at torproject.org karsten at torproject.org
Mon Jan 7 08:06:33 UTC 2019


commit 714862c3ecde23b44076c7dca42f8366714f8c11
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Sun Dec 16 11:39:42 2018 +0100

    Use Java 8 date-time functionality.
    
    Implements #28859.
---
 .../exonerator/ExoneraTorDatabaseImporter.java     | 38 +++++----
 .../metrics/exonerator/QueryServlet.java           | 94 +++++++++++-----------
 2 files changed, 63 insertions(+), 69 deletions(-)

diff --git a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDatabaseImporter.java b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDatabaseImporter.java
index 3ed7c19..57eb5df 100644
--- a/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDatabaseImporter.java
+++ b/src/main/java/org/torproject/metrics/exonerator/ExoneraTorDatabaseImporter.java
@@ -29,13 +29,14 @@ import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.Calendar;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedMap;
-import java.util.TimeZone;
 import java.util.TreeMap;
 
 /* Import Tor descriptors into the ExoneraTor database. */
@@ -131,10 +132,11 @@ public class ExoneraTorDatabaseImporter {
     try {
       if (lockFile.exists()) {
         BufferedReader br = new BufferedReader(new FileReader(lockFile));
-        long runStarted = Long.parseLong(br.readLine());
+        Instant runStarted = Instant.ofEpochMilli(Long.parseLong(
+            br.readLine()));
         br.close();
-        if (System.currentTimeMillis() - runStarted
-            < 6L * 60L * 60L * 1000L) {
+        if (runStarted.plus(Duration.ofHours(6L))
+            .compareTo(Instant.now()) >= 0) {
           logger.warn("File 'exonerator-lock' is less than 6 "
               + "hours old.  Exiting.");
           System.exit(1);
@@ -223,7 +225,8 @@ public class ExoneraTorDatabaseImporter {
 
   /* Parse a consensus. */
   private static void parseConsensus(RelayNetworkStatusConsensus consensus) {
-    long validAfterMillis = consensus.getValidAfterMillis();
+    LocalDateTime validAfter = LocalDateTime.ofInstant(Instant.ofEpochMilli(
+        consensus.getValidAfterMillis()), ZoneOffset.UTC);
     for (NetworkStatusEntry entry : consensus.getStatusEntries().values()) {
       if (entry.getFlags().contains("Running")) {
         String fingerprintBase64 = null;
@@ -248,26 +251,21 @@ public class ExoneraTorDatabaseImporter {
           orAddresses.add(orAddressAndPort.substring(0,
               orAddressAndPort.lastIndexOf(":")));
         }
-        importStatusentry(validAfterMillis, fingerprintBase64, nickname,
+        importStatusentry(validAfter, fingerprintBase64, nickname,
             exit, orAddresses);
       }
     }
   }
 
-  /* UTC calendar for importing timestamps into the database. */
-  private static Calendar calendarUTC = Calendar.getInstance(
-      TimeZone.getTimeZone("UTC"));
-
   /* Import a status entry with one or more OR addresses into the
    * database. */
-  private static void importStatusentry(long validAfterMillis,
+  private static void importStatusentry(LocalDateTime validAfter,
       String fingerprintBase64, String nickname, Boolean exit,
       Set<String> orAddresses) {
     try {
       for (String orAddress : orAddresses) {
         insertStatusentryStatement.clearParameters();
-        insertStatusentryStatement.setTimestamp(1,
-            new Timestamp(validAfterMillis), calendarUTC);
+        insertStatusentryStatement.setObject(1, validAfter);
         insertStatusentryStatement.setString(2, fingerprintBase64);
         if (!orAddress.contains(":")) {
           insertStatusentryStatement.setString(3, orAddress);
@@ -353,23 +351,23 @@ public class ExoneraTorDatabaseImporter {
             exitAddressParts[2]);
         String exitAddress24 = Hex.encodeHexString(
             exitAddress24Bytes);
-        long scannedMillis = e.getValue();
+        LocalDateTime scanned = LocalDateTime.ofInstant(
+            Instant.ofEpochMilli(e.getValue()), ZoneOffset.UTC);
         importExitlistentry(fingerprintBase64, exitAddress24, exitAddress,
-            scannedMillis);
+            scanned);
       }
     }
   }
 
   /* Import an exit list entry into the database. */
   private static void importExitlistentry(String fingerprintBase64,
-      String exitAddress24, String exitAddress, long scannedMillis) {
+      String exitAddress24, String exitAddress, LocalDateTime scanned) {
     try {
       insertExitlistentryStatement.clearParameters();
       insertExitlistentryStatement.setString(1, fingerprintBase64);
       insertExitlistentryStatement.setString(2, exitAddress);
       insertExitlistentryStatement.setString(3, exitAddress24);
-      insertExitlistentryStatement.setTimestamp(4,
-          new Timestamp(scannedMillis), calendarUTC);
+      insertExitlistentryStatement.setObject(4, scanned);
       insertExitlistentryStatement.execute();
     } catch (SQLException e) {
       logger.error("Could not import exit list entry.  Exiting.", e);
diff --git a/src/main/java/org/torproject/metrics/exonerator/QueryServlet.java b/src/main/java/org/torproject/metrics/exonerator/QueryServlet.java
index a807ad0..760e385 100644
--- a/src/main/java/org/torproject/metrics/exonerator/QueryServlet.java
+++ b/src/main/java/org/torproject/metrics/exonerator/QueryServlet.java
@@ -14,12 +14,15 @@ import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -47,7 +50,9 @@ public class QueryServlet extends HttpServlet {
 
   private DataSource ds;
 
-  private static final long MILLISECONDS_IN_A_DAY = 24L * 60L * 60L * 1000L;
+  private static final DateTimeFormatter validAfterTimeFormatter
+      = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+      .withZone(ZoneOffset.UTC);
 
   @Override
   public void init() {
@@ -88,7 +93,7 @@ public class QueryServlet extends HttpServlet {
             "Missing timestamp parameter.");
         return;
       }
-      Long timestamp = this.parseTimestampParameter(timestampParameter);
+      LocalDate timestamp = this.parseTimestampParameter(timestampParameter);
       if (null == timestamp) {
         response.sendError(HttpServletResponse.SC_BAD_REQUEST,
             "Invalid timestamp parameter.");
@@ -216,9 +221,9 @@ public class QueryServlet extends HttpServlet {
     return address48;
   }
 
-  private Long parseTimestampParameter(
+  private LocalDate parseTimestampParameter(
       String passedTimestampParameter) {
-    Long timestamp = null;
+    LocalDate timestamp = null;
     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
     dateFormat.setLenient(false);
@@ -226,8 +231,8 @@ public class QueryServlet extends HttpServlet {
         && passedTimestampParameter.length() > 0) {
       String timestampParameter = passedTimestampParameter.trim();
       try {
-        timestamp = dateFormat.parse(timestampParameter).getTime();
-      } catch (ParseException e) {
+        timestamp = LocalDate.parse(timestampParameter);
+      } catch (DateTimeException e) {
         timestamp = null;
       }
     }
@@ -239,13 +244,13 @@ public class QueryServlet extends HttpServlet {
    * it matches the day before the current system date (in UTC) or is even
    * younger. */
   private boolean checkTimestampTooRecent(String timestampParameter) {
-    return timestampParameter.compareTo(ZonedDateTime.now(ZoneOffset.UTC)
-        .toLocalDate().minusDays(1).toString()) >= 0;
+    return timestampParameter.compareTo(LocalDate.now(ZoneOffset.UTC)
+        .minusDays(1).toString()) >= 0;
   }
 
   /* Helper methods for querying the database. */
 
-  private QueryResponse queryDatabase(String relayIp, long timestamp) {
+  private QueryResponse queryDatabase(String relayIp, LocalDate timestamp) {
 
     /* Convert address to hex. */
     String addressHex = !relayIp.contains(":")
@@ -255,23 +260,16 @@ public class QueryServlet extends HttpServlet {
     }
     String address24Hex = addressHex.substring(0, 6);
 
-    /* Prepare formatting response items. */
-    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-    SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
-        "yyyy-MM-dd HH:mm:ss");
-    validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-
     /* Store all dates contained in the query response in order to populate the
      * {first|last}_date_in_database and relevant_statuses fields. */
-    SortedSet<Long> allDates = new TreeSet<>();
+    SortedSet<LocalDate> allDates = new TreeSet<>();
 
     /* Store all possible matches for the results table by base64-encoded
      * fingerprint and valid-after time. This map is first populated by going
      * through the result set and adding or updating map entries, so that
      * there's one entry per fingerprint and valid-after time with one or more
      * addresses. In a second step, exit addresses are added to map entries. */
-    SortedMap<String, SortedMap<Long, QueryResponse.Match>>
+    SortedMap<String, SortedMap<LocalDateTime, QueryResponse.Match>>
         matchesByFingerprintBase64AndValidAfter = new TreeMap<>();
 
     /* Store all possible matches by address. This map has two purposes: First,
@@ -289,53 +287,50 @@ public class QueryServlet extends HttpServlet {
      * result set and later added to the two maps above containing matches. The
      * reason for separating these steps is that the result set may contain
      * status entries and exit list entries in any specific order. */
-    SortedMap<String, SortedMap<Long, String>>
+    SortedMap<String, SortedMap<LocalDateTime, String>>
         exitAddressesByFingeprintBase64AndScanned = new TreeMap<>();
 
     /* Make the database query to populate the sets and maps above. */
-    final long requestedConnection = System.currentTimeMillis();
-    Calendar utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+    final Instant requestedConnection = Instant.now();
     try (Connection conn = this.ds.getConnection()) {
       try (CallableStatement cs = conn.prepareCall(
           "{call search_by_date_address24(?, ?)}")) {
-        cs.setDate(1, new java.sql.Date(timestamp), utcCalendar);
+        cs.setObject(1, timestamp);
         cs.setString(2, address24Hex);
         try (ResultSet rs = cs.executeQuery()) {
           while (rs.next()) {
-            java.sql.Date date = rs.getDate(1, utcCalendar);
+            LocalDate date = rs.getObject(1, LocalDate.class);
             String fingerprintBase64 = rs.getString(2);
-            java.sql.Timestamp scanned = rs.getTimestamp(3, utcCalendar);
+            LocalDateTime scanned = rs.getObject(3, LocalDateTime.class);
             String exitAddress = rs.getString(4);
-            java.sql.Timestamp validAfter = rs.getTimestamp(5, utcCalendar);
+            LocalDateTime validAfter = rs.getObject(5, LocalDateTime.class);
             String nickname = rs.getString(6);
             Boolean exit = rs.getBoolean(7);
             String orAddress = rs.getString(8);
             if (null != date) {
-              allDates.add(date.getTime());
+              allDates.add(date);
             } else if (null != scanned) {
-              long scannedMillis = scanned.getTime();
               exitAddressesByFingeprintBase64AndScanned.putIfAbsent(
                   fingerprintBase64, new TreeMap<>());
               exitAddressesByFingeprintBase64AndScanned.get(fingerprintBase64)
-                  .put(scannedMillis, exitAddress);
+                  .put(scanned, exitAddress);
             } else if (null != validAfter) {
-              long validAfterMillis = validAfter.getTime();
               matchesByFingerprintBase64AndValidAfter.putIfAbsent(
                   fingerprintBase64, new TreeMap<>());
               if (!matchesByFingerprintBase64AndValidAfter
-                  .get(fingerprintBase64).containsKey(validAfterMillis)) {
-                String validAfterString = validAfterTimeFormat.format(
-                    validAfterMillis);
+                  .get(fingerprintBase64).containsKey(validAfter)) {
+                String validAfterString = validAfter.format(
+                    validAfterTimeFormatter);
                 String fingerprint = Hex.encodeHexString(Base64.decodeBase64(
                     fingerprintBase64 + "=")).toUpperCase();
                 matchesByFingerprintBase64AndValidAfter.get(fingerprintBase64)
-                    .put(validAfterMillis, new QueryResponse.Match(
+                    .put(validAfter, new QueryResponse.Match(
                         validAfterString, new TreeSet<>(), fingerprint,
                         nickname, exit));
               }
               QueryResponse.Match match
                   = matchesByFingerprintBase64AndValidAfter
-                  .get(fingerprintBase64).get(validAfterMillis);
+                  .get(fingerprintBase64).get(validAfter);
               if (orAddress.contains(":")) {
                 match.addresses.add("[" + orAddress + "]");
               } else {
@@ -349,8 +344,8 @@ public class QueryServlet extends HttpServlet {
           this.logger.warn("Result set error.  Returning 'null'.", e);
           return null;
         }
-        this.logger.info("Returned a database connection to the pool after {}"
-            + " millis.", System.currentTimeMillis() - requestedConnection);
+        this.logger.info("Returned a database connection to the pool after {}.",
+            Duration.between(requestedConnection, Instant.now()));
       } catch (SQLException e) {
         this.logger.warn("Callable statement error.  Returning 'null'.", e);
         return null;
@@ -361,7 +356,7 @@ public class QueryServlet extends HttpServlet {
     }
 
     /* Go through exit addresses and update possible matches. */
-    for (Map.Entry<String, SortedMap<Long, String>> e
+    for (Map.Entry<String, SortedMap<LocalDateTime, String>> e
         : exitAddressesByFingeprintBase64AndScanned.entrySet()) {
       String fingerprintBase64 = e.getKey();
       if (!matchesByFingerprintBase64AndValidAfter.containsKey(
@@ -373,13 +368,12 @@ public class QueryServlet extends HttpServlet {
          * nearby matches. We'll just skip it. */
         continue;
       }
-      for (Map.Entry<Long, String> e1 : e.getValue().entrySet()) {
-        long scannedMillis = e1.getKey();
+      for (Map.Entry<LocalDateTime, String> e1 : e.getValue().entrySet()) {
+        LocalDateTime scanned = e1.getKey();
         String exitAddress = e1.getValue();
         for (QueryResponse.Match match
             : matchesByFingerprintBase64AndValidAfter.get(fingerprintBase64)
-            .subMap(scannedMillis, scannedMillis + MILLISECONDS_IN_A_DAY)
-            .values()) {
+            .subMap(scanned, scanned.plusDays(1L)).values()) {
           match.addresses.add(exitAddress);
           matchesByAddress.putIfAbsent(exitAddress, new HashSet<>());
           matchesByAddress.get(exitAddress).add(match);
@@ -390,13 +384,15 @@ public class QueryServlet extends HttpServlet {
     /* Write all results to a new QueryResponse object. */
     final QueryResponse response = new QueryResponse();
     response.queryAddress = relayIp;
-    response.queryDate = dateFormat.format(timestamp);
+    response.queryDate = timestamp.format(DateTimeFormatter.ISO_DATE);
     if (!allDates.isEmpty()) {
-      response.firstDateInDatabase = dateFormat.format(allDates.first());
-      response.lastDateInDatabase = dateFormat.format(allDates.last());
+      response.firstDateInDatabase = allDates.first()
+          .format(DateTimeFormatter.ISO_DATE);
+      response.lastDateInDatabase = allDates.last()
+          .format(DateTimeFormatter.ISO_DATE);
       response.relevantStatuses = allDates.contains(timestamp)
-          || allDates.contains(timestamp - MILLISECONDS_IN_A_DAY)
-          || allDates.contains(timestamp + MILLISECONDS_IN_A_DAY);
+          || allDates.contains(timestamp.minusDays(1L))
+          || allDates.contains(timestamp.plusDays(1L));
     }
     if (matchesByAddress.containsKey(relayIp)) {
       List<QueryResponse.Match> matchesList



More information about the tor-commits mailing list