summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluccioman <luccioman@users.noreply.github.com>2018-06-29 15:49:55 +0200
committerluccioman <luccioman@users.noreply.github.com>2018-06-29 15:49:55 +0200
commitf895745e1cd0dea933e73cd779f749a2224d583d (patch)
treece61e57920fb408b0ab12727ee9eadd3c94bd613
parent5c6c61809a0d71a852668de75a87880079499d9a (diff)
Removed more unsafe concurrent accesses to SimpleDateFormat instances.
SimpleDateFormat must not be used by concurrent threads without synchronization for parsing or formating dates as it is not thread-safe (internally holds a calendar instance that is not synchronized). Prefer now DateTimeFormatter when possible as it is thread-safe without concurrent access performance bottleneck (does not internally use synchronization locks).
-rw-r--r--htroot/CrawlResults.java14
-rw-r--r--htroot/IndexCreateQueues_p.java18
-rw-r--r--source/net/yacy/cora/document/feed/RSSMessage.java11
-rw-r--r--source/net/yacy/cora/protocol/HeaderFramework.java66
-rw-r--r--test/java/net/yacy/cora/protocol/HeaderFrameworkTest.java51
5 files changed, 144 insertions, 16 deletions
diff --git a/htroot/CrawlResults.java b/htroot/CrawlResults.java
index ef9c2312b..3429ac31b 100644
--- a/htroot/CrawlResults.java
+++ b/htroot/CrawlResults.java
@@ -24,7 +24,8 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
@@ -34,6 +35,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.encoding.UTF8;
import net.yacy.cora.document.id.Punycode.PunycodeException;
@@ -350,11 +352,17 @@ public class CrawlResults {
return prop;
}
- private static SimpleDateFormat dayFormatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US);
+ private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter
+ .ofPattern("uuuu/MM/dd", Locale.US).withZone(ZoneId.systemDefault());
+
+ /**
+ * @param date a date to render as a String
+ * @return the date formatted using the DAY_FORMATTER pattern.
+ */
private static String daydate(final Date date) {
if (date == null) {
return "";
}
- return dayFormatter.format(date);
+ return GenericFormatter.formatSafely(date.toInstant(), DAY_FORMATTER);
}
}
diff --git a/htroot/IndexCreateQueues_p.java b/htroot/IndexCreateQueues_p.java
index 393889daf..96c135067 100644
--- a/htroot/IndexCreateQueues_p.java
+++ b/htroot/IndexCreateQueues_p.java
@@ -1,6 +1,7 @@
import java.net.MalformedURLException;
-import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
@@ -12,6 +13,7 @@ import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
+import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.protocol.Domains;
@@ -28,10 +30,18 @@ import net.yacy.server.serverSwitch;
public class IndexCreateQueues_p {
- private static SimpleDateFormat dayFormatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US);
+ private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter
+ .ofPattern("uuuu/MM/dd", Locale.US).withZone(ZoneId.systemDefault());
+
+ /**
+ * @param date a date to render as a String
+ * @return the date formatted using the DAY_FORMATTER pattern.
+ */
private static String daydate(final Date date) {
- if (date == null) return "";
- return dayFormatter.format(date);
+ if (date == null) {
+ return "";
+ }
+ return GenericFormatter.formatSafely(date.toInstant(), DAY_FORMATTER);
}
private static final int INVALID = 0;
diff --git a/source/net/yacy/cora/document/feed/RSSMessage.java b/source/net/yacy/cora/document/feed/RSSMessage.java
index a12768a24..8906d103b 100644
--- a/source/net/yacy/cora/document/feed/RSSMessage.java
+++ b/source/net/yacy/cora/document/feed/RSSMessage.java
@@ -25,6 +25,7 @@
package net.yacy.cora.document.feed;
import java.text.ParseException;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -156,7 +157,7 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
if (title.length() > 0) this.map.put(Token.title.name(), title);
if (description.length() > 0) this.map.put(Token.description.name(), description);
if (link.length() > 0) this.map.put(Token.link.name(), link);
- this.map.put(Token.pubDate.name(), HeaderFramework.FORMAT_RFC1123.format(new Date()));
+ this.map.put(Token.pubDate.name(), HeaderFramework.formatNowRFC1123());
this.map.put(Token.guid.name(), artificialGuidPrefix + Integer.toHexString((title + description + link).hashCode()));
}
@@ -165,7 +166,7 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
if (title.length() > 0) this.map.put(Token.title.name(), title);
if (description.length() > 0) this.map.put(Token.description.name(), description);
this.map.put(Token.link.name(), link.toNormalform(true));
- this.map.put(Token.pubDate.name(), HeaderFramework.FORMAT_RFC1123.format(new Date()));
+ this.map.put(Token.pubDate.name(), HeaderFramework.formatNowRFC1123());
if (guid.length() > 0) {
this.map.put(Token.guid.name(), guid);
}
@@ -261,8 +262,8 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
if (!dateString.isEmpty()) { // skip parse exception on empty string
Date date;
try {
- date = HeaderFramework.FORMAT_RFC1123.parse(dateString);
- } catch (final ParseException e) {
+ date = Date.from(ZonedDateTime.parse(dateString, HeaderFramework.RFC1123_FORMATTER).toInstant());
+ } catch (final RuntimeException e) {
try {
date = GenericFormatter.SHORT_SECOND_FORMATTER.parse(dateString, 0).getTime();
} catch (final ParseException e1) {
@@ -401,7 +402,7 @@ public class RSSMessage implements Hit, Comparable<RSSMessage>, Comparator<RSSMe
@Override
public void setPubDate(final Date pubdate) {
- setValue(Token.pubDate, HeaderFramework.FORMAT_RFC1123.format(pubdate));
+ setValue(Token.pubDate, HeaderFramework.formatNowRFC1123());
}
@Override
diff --git a/source/net/yacy/cora/protocol/HeaderFramework.java b/source/net/yacy/cora/protocol/HeaderFramework.java
index 3595cc933..0eeb3ddf2 100644
--- a/source/net/yacy/cora/protocol/HeaderFramework.java
+++ b/source/net/yacy/cora/protocol/HeaderFramework.java
@@ -26,6 +26,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
@@ -230,10 +234,33 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
/** Date formatter/parser for standard compliant HTTP header dates (RFC 1123) */
private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss Z"; // with numeric time zone indicator as defined in RFC5322
private static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
- public static final SimpleDateFormat FORMAT_RFC1123 = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
- public static final SimpleDateFormat FORMAT_RFC1036 = new SimpleDateFormat(PATTERN_RFC1036, Locale.US);
+ private static final SimpleDateFormat FORMAT_RFC1123 = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
private static final TimeZone TZ_GMT = TimeZone.getTimeZone("GMT");
private static final Calendar CAL_GMT = Calendar.getInstance(TZ_GMT, Locale.US);
+
+ /**
+ * A thread-safe date formatter using the
+ * {@link HeaderFramework#PATTERN_RFC1123} pattern with the US locale on the UTC
+ * time zone.
+ */
+ public static final DateTimeFormatter RFC1123_FORMATTER = DateTimeFormatter
+ .ofPattern(PATTERN_RFC1123.replace("yyyy", "uuuu")).withLocale(Locale.US).withZone(ZoneOffset.UTC);
+
+ /**
+ * @return a new SimpleDateFormat instance using the
+ * {@link HeaderFramework#PATTERN_RFC1123} pattern with the US locale.
+ */
+ public static SimpleDateFormat newRfc1123Format() {
+ return new SimpleDateFormat(HeaderFramework.PATTERN_RFC1123, Locale.US);
+ }
+
+ /**
+ * @return a new SimpleDateFormat instance using the
+ * {@link HeaderFramework#PATTERN_RFC1036} pattern with the US locale.
+ */
+ public static SimpleDateFormat newRfc1036Format() {
+ return new SimpleDateFormat(HeaderFramework.PATTERN_RFC1036, Locale.US);
+ }
/**
* RFC 2616 requires that HTTP clients are able to parse all 3 different
@@ -241,9 +268,9 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
*/
private static final SimpleDateFormat[] FORMATS_HTTP = new SimpleDateFormat[] {
// RFC 1123/822 (Standard) "Mon, 12 Nov 2007 10:11:12 GMT"
- FORMAT_RFC1123,
+ newRfc1123Format(),
// RFC 1036/850 (old) "Monday, 12-Nov-07 10:11:12 GMT"
- FORMAT_RFC1036,
+ newRfc1036Format(),
// ANSI C asctime() "Mon Nov 12 10:11:12 2007"
GenericFormatter.newAnsicFormat(),
};
@@ -265,6 +292,34 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
return s;
}
}
+
+ /**
+ * @param epochMilli
+ * a time value as the number of milliseconds from Epoch
+ * (1970-01-01T00:00:00Z)
+ * @return the time formatted using the {@link HeaderFramework#PATTERN_RFC1123}
+ * pattern.
+ */
+ public static final String formatRFC1123(final long epochMilli) {
+ try {
+ /* Prefer first using the thread-safe DateTimeFormatter shared instance */
+ return RFC1123_FORMATTER.format(Instant.ofEpochMilli(epochMilli));
+ } catch (final DateTimeException e) {
+ /*
+ * This should not happen, but rather than failing we prefer here to use
+ * formatting function using the synchronized SimpleDateFormat
+ */
+ return formatRFC1123(new Date(epochMilli));
+ }
+ }
+
+ /**
+ * @return the current time formatted using the
+ * {@link HeaderFramework#PATTERN_RFC1123} pattern.
+ */
+ public static final String formatNowRFC1123() {
+ return formatRFC1123(System.currentTimeMillis());
+ }
/** Initialization of static formats */
static {
@@ -277,6 +332,9 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
format.setTimeZone(TZ_GMT);
format.set2DigitYearStart(CAL_GMT.getTime());
}
+
+ FORMAT_RFC1123.setTimeZone(TZ_GMT);
+ FORMAT_RFC1123.set2DigitYearStart(CAL_GMT.getTime());
}
/**
diff --git a/test/java/net/yacy/cora/protocol/HeaderFrameworkTest.java b/test/java/net/yacy/cora/protocol/HeaderFrameworkTest.java
index 71d013698..971dc57ad 100644
--- a/test/java/net/yacy/cora/protocol/HeaderFrameworkTest.java
+++ b/test/java/net/yacy/cora/protocol/HeaderFrameworkTest.java
@@ -1,6 +1,29 @@
+// HeaderFrameworkTest.java
+// Copyright 2006-2018 by theli, f1ori, Michael Peter Christen; mc@yacy.net, reger; reger18@arcor.de, luccioman; https://github.com/luccioman
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
package net.yacy.cora.protocol;
+import java.time.Instant;
import java.util.Date;
+
import junit.framework.TestCase;
import org.junit.Test;
@@ -18,6 +41,22 @@ public class HeaderFrameworkTest extends TestCase {
// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());
+
+ parsedDate = HeaderFramework.parseHTTPDate("Monday, 12-Nov-07 10:11:12 GMT");
+
+ // returned date must not be null
+ assertNotNull(parsedDate);
+
+ // Print Result
+ System.out.println("testParseHTTPDate: " + parsedDate.toString());
+
+ parsedDate = HeaderFramework.parseHTTPDate("Mon Nov 12 10:11:12 2007");
+
+ // returned date must not be null
+ assertNotNull(parsedDate);
+
+ // Print Result
+ System.out.println("testParseHTTPDate: " + parsedDate.toString());
}
/**
@@ -31,4 +70,16 @@ public class HeaderFrameworkTest extends TestCase {
assertEquals("utf-8", HeaderFramework.getCharacterEncoding("Text/HTML;Charset=\"utf-8\""));
assertEquals("utf-8", HeaderFramework.getCharacterEncoding("text/html; charset=\"utf-8\""));
}
+
+ /**
+ * Unit test for date formatting with RFC 1123 format
+ */
+ @Test
+ public void testFormatRfc1123() {
+ assertEquals("", HeaderFramework.formatRFC1123(null));
+
+ final Instant time = Instant.parse("2018-06-29T13:04:55.00Z");
+ assertEquals("Fri, 29 Jun 2018 13:04:55 +0000", HeaderFramework.formatRFC1123(time.toEpochMilli()));
+ assertEquals("Fri, 29 Jun 2018 13:04:55 +0000", HeaderFramework.formatRFC1123(Date.from(time)));
+ }
}