Skip to content
Permalink
Browse files

Fixed unsafe conccurent access to generic 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).
  • Loading branch information...
luccioman committed Jun 28, 2018
1 parent 38a3a5e commit e97580dfc713d7572dd95c3f4d4cc19a2d46b6a2
@@ -18,8 +18,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.util.Date;
import java.util.Iterator;

import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.data.UserDB;
@@ -30,7 +30,7 @@

public class ConfigAccountList_p {

public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) {
public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, @SuppressWarnings("unused") final serverObjects post, final serverSwitch env) {

final serverObjects prop = new serverObjects();
final Switchboard sb = (Switchboard) env;
@@ -54,7 +54,8 @@ public static serverObjects respond(@SuppressWarnings("unused") final RequestHea
prop.putHTML("userlist_" + numUsers + "_firstname", entry.getFirstName());
prop.putHTML("userlist_" + numUsers + "_address", entry.getAddress());
if (entry.getLastAccess() != null) {
prop.put("userlist_" + numUsers + "_lastaccess", GenericFormatter.FORMAT_SIMPLE.format(new Date(entry.getLastAccess())));
prop.put("userlist_" + numUsers + "_lastaccess",
GenericFormatter.formatSafely(entry.getLastAccess(), GenericFormatter.FORMAT_SIMPLE));
} else {
prop.put("userlist_" + numUsers + "_lastaccess", "never");
}
@@ -25,6 +25,7 @@
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.time.Instant;
import java.util.Date;

import org.apache.http.Header;
@@ -131,11 +132,11 @@ public static serverObjects respond(final RequestHeader header, final serverObje

if (status == 0 && post.getBoolean("iffresh")) {
long lastModified = getLastModified(sourceURL);
if (lastExecutionDate != null && lastModified != 0L
&& lastModified <= lastExecutionDate.getTime()) {
if (lastExecutionDate != null && lastModified != 0L && Instant.ofEpochMilli(lastModified)
.isBefore(lastExecutionDate.toInstant())) {
status = 5;
prop.put("import_status_lastImportDate",
GenericFormatter.FORMAT_SIMPLE.format(lastExecutionDate));
prop.put("import_status_lastImportDate", GenericFormatter
.formatSafely(lastExecutionDate.toInstant(), GenericFormatter.FORMAT_SIMPLE));

/* the import is not performed, but we increase here the api call count */
if(sb.tables != null) {
@@ -19,6 +19,7 @@

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
@@ -58,6 +59,8 @@ public static serverObjects respond(final RequestHeader header, final serverObje
final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects();

final DateFormat dateFormat = GenericFormatter.newSimpleDateFormat();

prop.put("showexec", 0);
prop.put("showtable", 0);

@@ -182,7 +185,7 @@ public static serverObjects respond(final RequestHeader header, final serverObje
final int time = row.get(WorkTables.TABLE_API_COL_APICALL_SCHEDULE_TIME, 0);
final String dateNextExecStr = entry.getValue().trim();
try {
final Date dateNextExec = GenericFormatter.FORMAT_SIMPLE.parse(dateNextExecStr);
final Date dateNextExec = dateFormat.parse(dateNextExecStr);

if(time != 0) { // Check there is effectively a schedule period on this row
if(dateNextExec.before(now)) {
@@ -390,8 +393,8 @@ public static serverObjects respond(final RequestHeader header, final serverObje
prop.put("showtable_list_" + count + "_pk", UTF8.String(row.getPK()));
prop.put("showtable_list_" + count + "_count", count);
prop.put("showtable_list_" + count + "_callcount", callcount);
prop.put("showtable_list_" + count + "_dateRecording", date_recording == null ? "-" : GenericFormatter.FORMAT_SIMPLE.format(date_recording));
prop.put("showtable_list_" + count + "_dateLastExec", date_last_exec == null ? "-" : GenericFormatter.FORMAT_SIMPLE.format(date_last_exec));
prop.put("showtable_list_" + count + "_dateRecording", date_recording == null ? "-" : dateFormat.format(date_recording));
prop.put("showtable_list_" + count + "_dateLastExec", date_last_exec == null ? "-" : dateFormat.format(date_last_exec));

prop.put("showtable_list_" + count + "_editableDateNext", time != 0);
final String enteredDateBeforeNow = nextExecDatesBeforeNow.get(rowPKStr);
@@ -407,7 +410,7 @@ public static serverObjects respond(final RequestHeader header, final serverObje
}

prop.put("showtable_list_" + count + "_editableDateNext_dateLastExecPattern", GenericFormatter.PATTERN_SIMPLE_REGEX);
prop.put("showtable_list_" + count + "_editableDateNext_dateNextExec", date_next_exec == null ? "-" : GenericFormatter.FORMAT_SIMPLE.format(date_next_exec));
prop.put("showtable_list_" + count + "_editableDateNext_dateNextExec", date_next_exec == null ? "-" : dateFormat.format(date_next_exec));
prop.put("showtable_list_" + count + "_editableDateNext_pk", rowPKStr);

prop.put("showtable_list_" + count + "_type", row.get(WorkTables.TABLE_API_COL_TYPE));
@@ -27,6 +27,13 @@
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
@@ -44,37 +51,159 @@
public static final String PATTERN_ANSIC = "EEE MMM d HH:mm:ss yyyy";
public static final String PATTERN_SIMPLE = "yyyy/MM/dd HH:mm:ss";

/** A regular expression matching the PATTERN_SIMPLE pattern (does not control last day of month (30/31 or 28/29 for february).
* Can be used as a HTML5 input field validation pattern */
public static final String PATTERN_SIMPLE_REGEX ="[0-9]{4}/(0[1-9]|1[012])/(0[1-9]|1[0-9]|2[0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}";

public static final SimpleDateFormat FORMAT_SHORT_DAY = new SimpleDateFormat(PATTERN_SHORT_DAY, Locale.US);
public static final SimpleDateFormat FORMAT_SHORT_MINUTE = new SimpleDateFormat(PATTERN_SHORT_MINUTE, Locale.US);
public static final SimpleDateFormat FORMAT_SHORT_SECOND = new SimpleDateFormat(PATTERN_SHORT_SECOND, Locale.US);
public static final SimpleDateFormat FORMAT_SHORT_MILSEC = new SimpleDateFormat(PATTERN_SHORT_MILSEC, Locale.US);
public static final SimpleDateFormat FORMAT_RFC1123_SHORT = new SimpleDateFormat(PATTERN_RFC1123_SHORT, Locale.US);
public static final SimpleDateFormat FORMAT_ANSIC = new SimpleDateFormat(PATTERN_ANSIC, Locale.US);
public static final SimpleDateFormat FORMAT_SIMPLE = new SimpleDateFormat(PATTERN_SIMPLE, Locale.US);

static {
// we want GMT times on the formats as well as they don't support any timezone
FORMAT_SHORT_DAY.setTimeZone(UTCtimeZone);
FORMAT_SHORT_SECOND.setTimeZone(UTCtimeZone);
FORMAT_SHORT_MILSEC.setTimeZone(UTCtimeZone);
}
/**
* A regular expression matching the PATTERN_SIMPLE pattern (does not control
* last day of month (30/31 or 28/29 for february). Can be used as a HTML5 input
* field validation pattern
*/
public static final String PATTERN_SIMPLE_REGEX = "[0-9]{4}/(0[1-9]|1[012])/(0[1-9]|1[0-9]|2[0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}";

/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SHORT_DAY} pattern with the US locale on the
* UTC time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_DAY = DateTimeFormatter
.ofPattern(PATTERN_SHORT_DAY.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneOffset.UTC);

/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SHORT_MINUTE} pattern with the US locale on
* the system time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_MINUTE = DateTimeFormatter
.ofPattern(PATTERN_SHORT_MINUTE.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneId.systemDefault());


/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SHORT_SECOND} pattern with the US locale on
* the UTC time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_SECOND = DateTimeFormatter
.ofPattern(PATTERN_SHORT_SECOND.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneOffset.UTC);

/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SHORT_MILSEC} pattern with the US locale on
* the UTC time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_MILSEC = new DateTimeFormatterBuilder()
.appendPattern(PATTERN_SHORT_MILSEC.replace("yyyy", "uuuu").replaceAll("SSS", ""))
.appendValue(ChronoField.MILLI_OF_SECOND, 3).toFormatter().withLocale(Locale.US)
.withZone(ZoneOffset.UTC);/* we can not use here the 'SSS' pattern for milliseconds on JDK 8 (see https://bugs.openjdk.java.net/browse/JDK-8031085) */

/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_RFC1123_SHORT} pattern with the US locale on
* the system time zone.
*/
public static final DateTimeFormatter FORMAT_RFC1123_SHORT = DateTimeFormatter
.ofPattern(PATTERN_RFC1123_SHORT.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneId.systemDefault());

/**
* A thread-safe date formatter using the {@link GenericFormatter#PATTERN_ANSIC}
* pattern with the US locale on the system time zone.
*/
public static final DateTimeFormatter FORMAT_ANSIC = DateTimeFormatter.ofPattern(PATTERN_ANSIC.replace("yyyy", "uuuu"), Locale.US)
.withZone(ZoneId.systemDefault());

/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SIMPLE} pattern (adapted for the DateTimeFormatter class) with the US locale on the
* system time zone.
*/
public static final DateTimeFormatter FORMAT_SIMPLE = DateTimeFormatter.ofPattern(PATTERN_SIMPLE.replace("yyyy", "uuuu"), Locale.US)
.withZone(ZoneId.systemDefault());

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_DAY} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortDayFormat() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_DAY, Locale.US);

// we want GMT times on the formats as well as they don't support any timezone
dateFormat.setTimeZone(UTCtimeZone);

return dateFormat;
}

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_MINUTE} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortMinuteFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_MINUTE, Locale.US);
}

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_SECOND} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortSecondFormat() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_SECOND, Locale.US);

// we want GMT times on the formats as well as they don't support any timezone
dateFormat.setTimeZone(UTCtimeZone);

return dateFormat;
}

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_MILSEC} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortMilsecFormat() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_MILSEC, Locale.US);

// we want GMT times on the formats as well as they don't support any timezone
dateFormat.setTimeZone(UTCtimeZone);

return dateFormat;
}

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_RFC1123_SHORT} pattern with the US
* locale.
*/
public static SimpleDateFormat newRfc1123ShortFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_RFC1123_SHORT, Locale.US);
}

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_ANSIC} pattern with the US locale.
*/
public static SimpleDateFormat newAnsicFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_ANSIC, Locale.US);
}

/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SIMPLE} pattern with the US locale.
*/
public static SimpleDateFormat newSimpleDateFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_SIMPLE, Locale.US);
}

public static final long time_second = 1000L;
public static final long time_minute = 60000L;
public static final long time_hour = 60 * time_minute;
public static final long time_day = 24 * time_hour;

public static final GenericFormatter SHORT_DAY_FORMATTER = new GenericFormatter(FORMAT_SHORT_DAY, time_minute);
public static final GenericFormatter SHORT_MINUTE_FORMATTER = new GenericFormatter(FORMAT_SHORT_MINUTE, time_second);
public static final GenericFormatter SHORT_SECOND_FORMATTER = new GenericFormatter(FORMAT_SHORT_SECOND, time_second);
public static final GenericFormatter SHORT_MILSEC_FORMATTER = new GenericFormatter(FORMAT_SHORT_MILSEC, 1);
public static final GenericFormatter RFC1123_SHORT_FORMATTER = new GenericFormatter(FORMAT_RFC1123_SHORT, time_minute);
public static final GenericFormatter ANSIC_FORMATTER = new GenericFormatter(FORMAT_ANSIC, time_second);
public static final GenericFormatter SIMPLE_FORMATTER = new GenericFormatter(FORMAT_SIMPLE, time_second);
public static final GenericFormatter SHORT_DAY_FORMATTER = new GenericFormatter(newShortDayFormat(), time_minute);
public static final GenericFormatter SHORT_MINUTE_FORMATTER = new GenericFormatter(newShortMinuteFormat(), time_second);
public static final GenericFormatter SHORT_SECOND_FORMATTER = new GenericFormatter(newShortSecondFormat(), time_second);
public static final GenericFormatter SHORT_MILSEC_FORMATTER = new GenericFormatter(newShortMilsecFormat(), 1);
public static final GenericFormatter RFC1123_SHORT_FORMATTER = new GenericFormatter(newRfc1123ShortFormat(), time_minute);
public static final GenericFormatter ANSIC_FORMATTER = new GenericFormatter(newAnsicFormat(), time_second);
public static final GenericFormatter SIMPLE_FORMATTER = new GenericFormatter(newSimpleDateFormat(), time_second);

private final SimpleDateFormat dateFormat;
private final long maxCacheDiff;
@@ -201,6 +330,59 @@ public static String UTCDiffString() {
}

private final static DecimalFormat D2 = new DecimalFormat("00");

/**
* Safely format the given time value using the given formatter. Fallback to
* ISO-8601 representation or to raw time value without exception when the
* format can not be applied.
*
* @param time
* a time value as millisecnods from Epoch (1970-01-01T00:00:00Z)
* @param formatter
* the formatter to use
* @return a String representation of the time value
*/
public static String formatSafely(final long time, final DateTimeFormatter formatter) {
String res;
try {
res = formatSafely(Instant.ofEpochMilli(time), formatter);
} catch (final DateTimeException e) {
/*
* Can occur on Instant.ofEpochMilli when the time value is greater than
* Instant.MAX.toEpochMilli() or lower than Instant.MIN.toEpochMilli()
*/
res = String.valueOf(time);
}
return res;
}

/**
* Safely format the given instant using the given formatter. Fallback to
* ISO-8601 representation without exception when the format can not be applied.
*
* @param instant
* the instant to format
* @param formatter
* the formatter to use
* @return a String representation of the time value
*/
public static String formatSafely(final Instant instant, final DateTimeFormatter formatter) {
String res;
if (instant == null) {
res = "";
} else {
try {
if (formatter != null) {
res = formatter.format(instant);
} else {
res = instant.toString();
}
} catch (final DateTimeException e) {
res = instant.toString();
}
}
return res;
}

public static void main(String[] args) {
System.out.println(UTCDiffString());
@@ -245,7 +245,7 @@ public HeaderFramework(final Map<String, String> othermap) {
// RFC 1036/850 (old) "Monday, 12-Nov-07 10:11:12 GMT"
FORMAT_RFC1036,
// ANSI C asctime() "Mon Nov 12 10:11:12 2007"
GenericFormatter.FORMAT_ANSIC,
GenericFormatter.newAnsicFormat(),
};


@@ -674,8 +674,8 @@ public static Date parseLine(final String text, final int timezoneOffset) {
// check standard date formats
try {d = CONFORM.parse(text);} catch (ParseException e) {}
//if (d == null) try {d = GenericFormatter.FORMAT_SHORT_DAY.parse(text);} catch (ParseException e) {} // did not work well and fired for wrong formats; do not use
if (d == null) try {d = GenericFormatter.FORMAT_RFC1123_SHORT.parse(text);} catch (ParseException e) {}
if (d == null) try {d = GenericFormatter.FORMAT_ANSIC.parse(text);} catch (ParseException e) {}
if (d == null) try {d = GenericFormatter.newRfc1123ShortFormat().parse(text);} catch (ParseException e) {}
if (d == null) try {d = GenericFormatter.newAnsicFormat().parse(text);} catch (ParseException e) {}

if (d == null) {
// check other date formats
@@ -103,7 +103,7 @@
private final ExecutorService executor;

// use our own formatter to prevent concurrency locks with other processes
private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_MILSEC, 1);
private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter(GenericFormatter.newShortMilsecFormat(), 1);


public ArrayStack(

0 comments on commit e97580d

Please sign in to comment.
You can’t perform that action at this time.