diff --git a/src/main/java/org/apache/commons/jxpath/xml/DocumentContainer.java b/src/main/java/org/apache/commons/jxpath/xml/DocumentContainer.java index 0832de076..15b6488b3 100644 --- a/src/main/java/org/apache/commons/jxpath/xml/DocumentContainer.java +++ b/src/main/java/org/apache/commons/jxpath/xml/DocumentContainer.java @@ -20,7 +20,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.jxpath.Container; import org.apache.commons.jxpath.JXPathException; @@ -39,14 +40,14 @@ public class DocumentContainer extends XMLParser2 implements Container { /** JDOM constant */ public static final String MODEL_JDOM = "JDOM"; private static final long serialVersionUID = -8713290334113427066L; - private static HashMap parserClasses = new HashMap<>(); + private static final Map parserClasses = new ConcurrentHashMap<>(); static { parserClasses.put(MODEL_DOM, "org.apache.commons.jxpath.xml.DOMParser"); parserClasses.put(MODEL_JDOM, "org.apache.commons.jxpath.xml.JDOMParser"); } - private static HashMap parsers = new HashMap<>(); + private static final Map parsers = new ConcurrentHashMap<>(); /** * Maps a model type to a parser. diff --git a/src/test/java/org/apache/commons/jxpath/xml/DocumentContainerConcurrencyTest.java b/src/test/java/org/apache/commons/jxpath/xml/DocumentContainerConcurrencyTest.java new file mode 100644 index 000000000..84c5f46be --- /dev/null +++ b/src/test/java/org/apache/commons/jxpath/xml/DocumentContainerConcurrencyTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.jxpath.xml; + +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.net.URL; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +/** + * Tests that the static parser registries in {@link DocumentContainer} tolerate concurrent access. + */ +class DocumentContainerConcurrencyTest { + + private static final int THREAD_COUNT = 12; + private static final int ITERATIONS = 400; + + @Test + void testConcurrentRegisterAndGetValue() throws InterruptedException { + final URL url = DocumentContainer.class.getResource("/org/apache/commons/jxpath/Vendor.xml"); + final AtomicReference failure = new AtomicReference<>(); + final Thread[] threads = new Thread[THREAD_COUNT]; + for (int t = 0; t < THREAD_COUNT; t++) { + final int tid = t; + threads[t] = new Thread(() -> { + for (int i = 0; i < ITERATIONS && failure.get() == null; i++) { + final String model = "model-" + tid + "-" + i; + try { + if ((tid & 1) == 0) { + DocumentContainer.registerXMLParser(model, "org.apache.commons.jxpath.xml.DOMParser"); + new DocumentContainer(url, model).getValue(); + } else { + DocumentContainer.registerXMLParser(model, new DOMParser()); + } + } catch (final Throwable th) { + failure.compareAndSet(null, th); + } + } + }); + } + for (final Thread thread : threads) { + thread.start(); + } + for (final Thread thread : threads) { + thread.join(); + } + assertNull(failure.get()); + } +}