This is an automated email from the ASF dual-hosted git repository. tallison pushed a commit to branch TIKA-4393 in repository https://gitbox.apache.org/repos/asf/tika.git
commit 4eee00c202261130bd4d712823c361c2eec2d3ba Author: tallison <[email protected]> AuthorDate: Thu Mar 13 12:27:30 2025 -0400 TIKA-4393 --- CHANGES.txt | 4 ++ .../org/apache/tika/xmp/convert/TikaToXMP.java | 22 +++++------ .../java/org/apache/tika/xmp/TikaToXMPTest.java | 44 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a5feb090f..edcc7a7f0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,10 @@ Release 4.0.0-BETA1 - ??? * Headers are no longer injected into the body/content of MSG files (TIKA-4345). Please open a ticket if you need this behavior across email formats. + OTHER CHANGES + + * Fix concurrency bug in TikaToXMP (TIKA-4393) + Release 3.1.0 - ?? diff --git a/tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java b/tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java index 152b94191..73a2b4f02 100644 --- a/tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java +++ b/tika-xmp/src/main/java/org/apache/tika/xmp/convert/TikaToXMP.java @@ -36,10 +36,13 @@ import org.apache.tika.parser.odf.OpenDocumentParser; public class TikaToXMP { /** - * Map from mimetype to converter class Must only be accessed through - * <code>getConverterMap</code> + * Map from mimetype to converter class */ - private static Map<MediaType, Class<? extends ITikaToXMPConverter>> converterMap; + private static final Map<MediaType, Class<? extends ITikaToXMPConverter>> CONVERTER_MAP = new HashMap<>(); + + static { + initialize(); + } // --- public API implementation--- @@ -114,7 +117,7 @@ public class TikaToXMP { MediaType type = MediaType.parse(mimetype); if (type != null) { - return (getConverterMap().get(type) != null); + return (CONVERTER_MAP.get(type) != null); } return false; @@ -137,7 +140,7 @@ public class TikaToXMP { MediaType type = MediaType.parse(mimetype); if (type != null) { - Class<? extends ITikaToXMPConverter> clazz = getConverterMap().get(type); + Class<? extends ITikaToXMPConverter> clazz = CONVERTER_MAP.get(type); if (clazz != null) { try { converter = clazz.getDeclaredConstructor().newInstance(); @@ -154,13 +157,6 @@ public class TikaToXMP { // --- Private methods --- - private static Map<MediaType, Class<? extends ITikaToXMPConverter>> getConverterMap() { - if (converterMap == null) { - converterMap = new HashMap<>(); - initialize(); - } - return converterMap; - } /** * Initializes the map with supported converters. @@ -187,7 +183,7 @@ public class TikaToXMP { private static void addConverter(Set<MediaType> supportedTypes, Class<? extends ITikaToXMPConverter> converter) { for (MediaType type : supportedTypes) { - getConverterMap().put(type, converter); + CONVERTER_MAP.put(type, converter); } } } diff --git a/tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java b/tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java index b16358ee8..da25605f7 100644 --- a/tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java +++ b/tika-xmp/src/test/java/org/apache/tika/xmp/TikaToXMPTest.java @@ -23,6 +23,14 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +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 java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import com.adobe.internal.xmp.XMPConst; import com.adobe.internal.xmp.XMPException; import com.adobe.internal.xmp.XMPIterator; @@ -225,4 +233,40 @@ public class TikaToXMPTest { TikaToXMP.getConverter(null); }); } + + @Test + public void testMultithreaded() throws Exception { + int numThreads = 10; + final int numIterations = 100; + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + try { + ExecutorCompletionService<Integer> executorCompletionService = new ExecutorCompletionService<>(executorService); + for (int i = 0; i < numThreads; i++) { + executorCompletionService.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + for (int j = 0; j < numIterations; j++) { + Metadata m = new Metadata(); + setupOOXMLMetadata(m); + m.set(Metadata.CONTENT_TYPE, OOXML_MIMETYPE); + XMPMeta xmp = TikaToXMP.convert(m); + checkOOXMLMetadata(xmp); + } + return 1; + } + }); + } + int finished = 0; + while (finished < numThreads) { + Future<Integer> future = executorCompletionService.poll(1, TimeUnit.MINUTES); + if (future == null) { + throw new TimeoutException(); + } + future.get(); + finished++; + } + } finally { + executorService.shutdownNow(); + } + } }
