You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2017/01/10 19:46:51 UTC

tika git commit: TIKA-2234 -- get rid of ThreadLocal

Repository: tika
Updated Branches:
  refs/heads/master 86dbde4ec -> 66f6310ee


TIKA-2234 -- get rid of ThreadLocal


Project: http://git-wip-us.apache.org/repos/asf/tika/repo
Commit: http://git-wip-us.apache.org/repos/asf/tika/commit/66f6310e
Tree: http://git-wip-us.apache.org/repos/asf/tika/tree/66f6310e
Diff: http://git-wip-us.apache.org/repos/asf/tika/diff/66f6310e

Branch: refs/heads/master
Commit: 66f6310eec8e8aade078f0ca32a07399008ef1b1
Parents: 86dbde4
Author: tballison <ta...@mitre.org>
Authored: Tue Jan 10 14:46:42 2017 -0500
Committer: tballison <ta...@mitre.org>
Committed: Tue Jan 10 14:46:42 2017 -0500

----------------------------------------------------------------------
 .../java/org/apache/tika/metadata/Metadata.java | 39 +-----------
 .../java/org/apache/tika/utils/DateUtils.java   | 34 +++++-----
 .../org/apache/tika/metadata/TestMetadata.java  | 65 +++++++++++++++++---
 .../parser/image/ImageMetadataExtractor.java    | 13 ++--
 .../OOXMLWordAndPowerPointTextHandler.java      |  4 +-
 5 files changed, 85 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tika/blob/66f6310e/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
----------------------------------------------------------------------
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java b/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
index 08e4fa8..a5528c9 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
@@ -16,14 +16,11 @@
  */
 package org.apache.tika.metadata;
 
-import static org.apache.tika.utils.DateUtils.MIDDAY;
-import static org.apache.tika.utils.DateUtils.UTC;
 import static org.apache.tika.utils.DateUtils.formatDate;
 
 import java.io.Serializable;
 import java.text.DateFormat;
 import java.text.DateFormatSymbols;
-import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
@@ -35,6 +32,7 @@ import java.util.Properties;
 import java.util.TimeZone;
 
 import org.apache.tika.metadata.Property.PropertyType;
+import org.apache.tika.utils.DateUtils;
 
 /**
  * A multi-valued metadata container.
@@ -92,24 +90,8 @@ public class Metadata implements CreativeCommons, Geographic, HttpHeaders,
     /**
      * Some parsers will have the date as a ISO-8601 string
      *  already, and will set that into the Metadata object.
-     * So we can return Date objects for these, this is the
-     *  list (in preference order) of the various ISO-8601
-     *  variants that we try when processing a date based
-     *  property.
      */
-    private static final DateFormat[] iso8601InputFormats = new DateFormat[] {
-        // yyyy-mm-ddThh...
-        createDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", UTC),   // UTC/Zulu
-        createDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", null),    // With timezone
-        createDateFormat("yyyy-MM-dd'T'HH:mm:ss", null),     // Without timezone
-        // yyyy-mm-dd hh...
-        createDateFormat("yyyy-MM-dd' 'HH:mm:ss'Z'", UTC),   // UTC/Zulu
-        createDateFormat("yyyy-MM-dd' 'HH:mm:ssZ", null),    // With timezone
-        createDateFormat("yyyy-MM-dd' 'HH:mm:ss", null),     // Without timezone
-        // Date without time, set to Midday UTC
-        createDateFormat("yyyy-MM-dd", MIDDAY),              // Normal date format
-        createDateFormat("yyyy:MM:dd", MIDDAY),              // Image (IPTC/EXIF) format
-    };
+    private static final DateUtils DATE_UTILS = new DateUtils();
 
     private static DateFormat createDateFormat(String format, TimeZone timezone) {
         SimpleDateFormat sdf =
@@ -129,22 +111,7 @@ public class Metadata implements CreativeCommons, Geographic, HttpHeaders,
      * @return parsed date, or <code>null</code> if the date can't be parsed
      */
     private static synchronized Date parseDate(String date) {
-        // Java doesn't like timezones in the form ss+hh:mm
-        // It only likes the hhmm form, without the colon
-        int n = date.length();
-        if (date.charAt(n - 3) == ':'
-            && (date.charAt(n - 6) == '+' || date.charAt(n - 6) == '-')) {
-            date = date.substring(0, n - 3) + date.substring(n - 2);
-        }
-
-        // Try several different ISO-8601 variants
-        for (DateFormat format : iso8601InputFormats) {
-            try {
-                return format.parse(date);
-            } catch (ParseException ignore) {
-            }
-        }
-        return null;
+        return DATE_UTILS.tryToParse(date);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/tika/blob/66f6310e/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
----------------------------------------------------------------------
diff --git a/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java b/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
index 6ccc74e..a358320 100644
--- a/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/utils/DateUtils.java
@@ -50,32 +50,25 @@ public class DateUtils {
      */
     public static final TimeZone MIDDAY = TimeZone.getTimeZone("GMT-12:00");
 
-    private static ThreadLocal<DateFormat> createDateFormat(String format, TimeZone timezone) {
+    private static DateFormat createDateFormat(String format, TimeZone timezone) {
         final SimpleDateFormat sdf =
                 new SimpleDateFormat(format, new DateFormatSymbols(Locale.US));
         if (timezone != null) {
             sdf.setTimeZone(timezone);
         }
-        return new ThreadLocal<DateFormat>() {
-            @Override
-            public DateFormat initialValue() {
-                return sdf;
-            }
-        };
+        return sdf;
     }
 
     /**
-     * Some parsers will have the date as a ISO-8601 string
-     *  already, and will set that into the Metadata object.
      * So we can return Date objects for these, this is the
      *  list (in preference order) of the various ISO-8601
      *  variants that we try when processing a date based
      *  property.
      */
-    private static final List<ThreadLocal<DateFormat>> ISO_8601_INPUT_FORMATS = loadDateFormats();
+    private final List<DateFormat> iso8601InputFormats = loadDateFormats();
 
-    private static List<ThreadLocal<DateFormat>> loadDateFormats() {
-        List<ThreadLocal<DateFormat>> dateFormats = new ArrayList<>();
+    private List<DateFormat> loadDateFormats() {
+        List<DateFormat> dateFormats = new ArrayList<>();
         // yyyy-mm-ddThh...
         dateFormats.add(createDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", UTC));   // UTC/Zulu
         dateFormats.add(createDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", null));    // With timezone
@@ -149,15 +142,24 @@ public class DateUtils {
     /**
      * Tries to parse the date string; returns null if no parse was possible.
      *
-     * This is thread safe because it relies on threadlocal dateformats.
+     * This is not thread safe!  Wrap in synchronized or create new {@link DateUtils}
+     * for each class.
      *
      * @param dateString
      * @return
      */
-    public static Date tryToParse(String dateString) {
-        for (ThreadLocal<DateFormat> df : ISO_8601_INPUT_FORMATS) {
+    public Date tryToParse(String dateString) {
+        // Java doesn't like timezones in the form ss+hh:mm
+        // It only likes the hhmm form, without the colon
+        int n = dateString.length();
+        if (dateString.charAt(n - 3) == ':'
+                && (dateString.charAt(n - 6) == '+' || dateString.charAt(n - 6) == '-')) {
+            dateString = dateString.substring(0, n - 3) + dateString.substring(n - 2);
+        }
+
+        for (DateFormat df : iso8601InputFormats) {
             try {
-                return df.get().parse(dateString);
+                return df.parse(dateString);
             } catch (java.text.ParseException e){
 
             }

http://git-wip-us.apache.org/repos/asf/tika/blob/66f6310e/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java
----------------------------------------------------------------------
diff --git a/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java b/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java
index b9bb14f..e44b277 100644
--- a/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java
+++ b/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java
@@ -17,14 +17,7 @@
 package org.apache.tika.metadata;
 
 //JDK imports
-import java.util.Date;
-import java.util.Properties;
 
-import org.apache.tika.utils.DateUtils;
-import org.junit.Test;
-
-
-//Junit imports
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -32,6 +25,24 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.tika.utils.DateUtils;
+import org.junit.Test;
+
+//Junit imports
+
 /**
  * JUnit based tests of class {@link org.apache.tika.metadata.Metadata}.
  */
@@ -372,5 +383,43 @@ public class TestMetadata {
        // Fetch as the aliases
        assertEquals(message, meta.get(Metadata.DESCRIPTION));
        assertEquals(message, meta.get("testDescriptionAlt"));
-    }    
+    }
+
+    @Test
+    public void testMultithreadedDates() throws Exception {
+        int numThreads = 10;
+        ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
+        ExecutorCompletionService<Integer> executorCompletionService = new ExecutorCompletionService<Integer>(executorService);
+        for (int i = 0; i < numThreads; i++) {
+            executorCompletionService.submit(new MetadataDateAdder());
+        }
+        int finished = 0;
+        while (finished < numThreads) {
+            Future<Integer> future = executorCompletionService.take();
+            if (future != null && future.isDone()) {
+                Integer retVal = future.get();
+                finished++;
+            }
+        }
+
+    }
+
+    private class MetadataDateAdder implements Callable<Integer> {
+        private final Random random = new Random();
+        @Override
+        public Integer call() throws Exception {
+            for (int i = 0; i < 1000; i++) {
+                Metadata m = new Metadata();
+                long start = new Date().getTime();
+                start += random.nextInt(1000000);
+                Date now = new Date(start);
+                DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+                m.set(TikaCoreProperties.CREATED, df.format(now));
+                df.setTimeZone(TimeZone.getTimeZone("UTC"));
+                assertTrue(Math.abs(now.getTime() - m.getDate(TikaCoreProperties.CREATED).getTime()) < 2000);
+
+            }
+            return 1;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/tika/blob/66f6310e/tika-parsers/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
----------------------------------------------------------------------
diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java b/tika-parsers/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
index ccc84d8..0c2cf4d 100644
--- a/tika-parsers/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
+++ b/tika-parsers/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
@@ -341,12 +341,9 @@ public class ImageMetadataExtractor {
 
     static class ExifHandler implements DirectoryHandler {
         // There's a new ExifHandler for each file processed, so this is thread safe
-        private static final ThreadLocal<SimpleDateFormat> DATE_UNSPECIFIED_TZ = new ThreadLocal<SimpleDateFormat>() {
-            @Override
-            protected SimpleDateFormat initialValue() {
-                return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
-            }
-        };
+        private final SimpleDateFormat dateUnspecifiedTz =
+                new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
+
 
         public boolean supports(Class<? extends Directory> directoryType) {
             return directoryType == ExifIFD0Directory.class ||
@@ -487,7 +484,7 @@ public class ImageMetadataExtractor {
                 // Unless we have GPS time we don't know the time zone so date must be set
                 // as ISO 8601 datetime without timezone suffix (no Z or +/-)
                 if (original != null) {
-                    String datetimeNoTimeZone = DATE_UNSPECIFIED_TZ.get().format(original); // Same time zone as Metadata Extractor uses
+                    String datetimeNoTimeZone = dateUnspecifiedTz.format(original); // Same time zone as Metadata Extractor uses
                     metadata.set(TikaCoreProperties.CREATED, datetimeNoTimeZone);
                     metadata.set(Metadata.ORIGINAL_DATE, datetimeNoTimeZone);
                 }
@@ -495,7 +492,7 @@ public class ImageMetadataExtractor {
             if (directory.containsTag(ExifIFD0Directory.TAG_DATETIME)) {
                 Date datetime = directory.getDate(ExifIFD0Directory.TAG_DATETIME);
                 if (datetime != null) {
-                    String datetimeNoTimeZone = DATE_UNSPECIFIED_TZ.get().format(datetime);
+                    String datetimeNoTimeZone = dateUnspecifiedTz.format(datetime);
                     metadata.set(TikaCoreProperties.MODIFIED, datetimeNoTimeZone);
                     // If Date/Time Original does not exist this might be creation date
                     if (metadata.get(TikaCoreProperties.CREATED) == null) {

http://git-wip-us.apache.org/repos/asf/tika/blob/66f6310e/tika-parsers/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java
----------------------------------------------------------------------
diff --git a/tika-parsers/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java b/tika-parsers/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java
index 8cd84d9..b3a9f68 100644
--- a/tika-parsers/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java
+++ b/tika-parsers/src/main/java/org/apache/tika/parser/microsoft/ooxml/OOXMLWordAndPowerPointTextHandler.java
@@ -144,7 +144,7 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
 
     private OOXMLWordAndPowerPointTextHandler.EditType editType = OOXMLWordAndPowerPointTextHandler.EditType.NONE;
 
-
+    private DateUtils dateUtils = new DateUtils();
     public OOXMLWordAndPowerPointTextHandler(XWPFBodyContentsHandler bodyContentsHandler,
                                              Map<String, String> hyperlinks) {
         this.bodyContentsHandler = bodyContentsHandler;
@@ -313,7 +313,7 @@ public class OOXMLWordAndPowerPointTextHandler extends DefaultHandler {
         String editDateString = atts.getValue(W_NS, "date");
         Date editDate = null;
         if (editDateString != null) {
-            editDate = DateUtils.tryToParse(editDateString);
+            editDate = dateUtils.tryToParse(editDateString);
         }
         bodyContentsHandler.startEditedSection(editAuthor, editDate, editType);
         this.editType = editType;