Merge branch 'SANTUARIO-504' into trunk

git-svn-id: https://svn.apache.org/repos/asf/santuario/xml-security-java/trunk@1862064 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/xml/security/utils/WeakObjectPool.java b/src/main/java/org/apache/xml/security/utils/WeakObjectPool.java
deleted file mode 100644
index adf3dba..0000000
--- a/src/main/java/org/apache/xml/security/utils/WeakObjectPool.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * 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
- *
- * http://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.xml.security.utils;
-
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingDeque;
-
-/**
- * Abstract base class for pooling objects.  The two public methods are
- * {@link #getObject()} and ({@link #repool(Object)}.  Objects are held through
- * weak references so even objects that are not repooled are subject to garbage collection.
- *
- * Subclasses must implement the abstract {@link #createObject()}.
- * <p>
- *
- * Internally, the pool is stored in a java.util.concurrent.LinkedBlockingDeque
- * instance.
- */
-public abstract class WeakObjectPool<T, E extends Throwable> {
-
-    private static final Integer MARKER_VALUE = Integer.MAX_VALUE;//once here rather than auto-box it?
-
-    /** created, available objects to be checked out to clients */
-    private final BlockingQueue<WeakReference<T>> available;
-
-    /**
-     * Synchronized, identity map of loaned out objects (WeakHashMap);
-     * use to ensure we repool only object originating from here
-     * and do it once.
-     */
-    private final Map<T, Integer> onLoan;
-
-    /**
-     * The lone constructor.
-     */
-    protected WeakObjectPool() {
-        //alternative implementations: ArrayBlockingQueue has a fixed size
-        //  PriorityBlockingQueue: requires a dummy comparator; less memory but more overhead
-        available = new LinkedBlockingDeque<>();
-        this.onLoan = Collections.synchronizedMap(new WeakHashMap<T, Integer>());
-    }
-
-    /**
-     * Called whenever a new pool object is desired; subclasses must implement.
-     *
-     * @return object of the type desired by the subclass
-     * @throws E Throwable's subclass
-     */
-    protected abstract T createObject() throws E;
-
-
-    /**
-     * Subclasses can subclass to return a more specific type.
-     *
-     * @return an object from the pool; will block until an object is available
-     * @throws E
-     */
-    public T getObject() throws E {
-        WeakReference<T> ref;
-        T retValue = null;
-        do {
-            //remove any stale entries as well
-            ref = available.poll();
-        } while (ref != null && (retValue = ref.get()) == null);
-
-        if (retValue == null) {
-            //empty pool; create & add new one
-            retValue = createObject();
-        }
-        onLoan.put(retValue, MARKER_VALUE);
-        return retValue;
-    }
-
-
-    /**
-     * Adds the given object to the pool, provided that the object
-     * was created by this pool.
-     *
-     * @param obj the object to return to the pool
-     * @return whether the object was successfully added as available
-     */
-    public boolean repool(T obj) {
-        if (obj != null && onLoan.containsKey(obj)) {
-            //synchronize to protect against a caller returning the same object again...
-            synchronized (obj) {
-                //...and check to see that it was removed
-                if (onLoan.remove(obj) != null) {
-                    return available.offer(new WeakReference<T>(obj));
-                }
-            }
-        }
-        return false;
-    }
-}
diff --git a/src/main/java/org/apache/xml/security/utils/XMLUtils.java b/src/main/java/org/apache/xml/security/utils/XMLUtils.java
index b70aab3..92a1e81 100644
--- a/src/main/java/org/apache/xml/security/utils/XMLUtils.java
+++ b/src/main/java/org/apache/xml/security/utils/XMLUtils.java
@@ -26,12 +26,16 @@
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Base64;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ArrayBlockingQueue;
 
-import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
@@ -59,12 +63,9 @@
         AccessController.doPrivileged(
             (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("org.apache.xml.security.ignoreLineBreaks"));
 
-    @SuppressWarnings("unchecked")
-    private static final WeakObjectPool<DocumentBuilder, ParserConfigurationException> pools[] = new WeakObjectPool[2];
-    static {
-        pools[0] = new DocumentBuilderPool(false);
-        pools[1] = new DocumentBuilderPool(true);
-    }
+    private static int parserPoolSize =
+        AccessController.doPrivileged(
+            (PrivilegedAction<Integer>) () -> Integer.getInteger("org.apache.xml.security.parser.pool-size", 20));
 
     private static volatile String dsPrefix = "ds";
     private static volatile String ds11Prefix = "dsig11";
@@ -74,6 +75,11 @@
     private static final org.slf4j.Logger LOG =
         org.slf4j.LoggerFactory.getLogger(XMLUtils.class);
 
+    private static final Map<ClassLoader, Queue<DocumentBuilder>> DOCUMENT_BUILDERS =
+        Collections.synchronizedMap(new WeakHashMap<ClassLoader, Queue<DocumentBuilder>>());
+
+    private static final Map<ClassLoader, Queue<DocumentBuilder>> DOCUMENT_BUILDERS_DISALLOW_DOCTYPE =
+        Collections.synchronizedMap(new WeakHashMap<ClassLoader, Queue<DocumentBuilder>>());
 
     /**
      * Constructor XMLUtils
@@ -1004,7 +1010,7 @@
     }
 
     public static Document newDocument() throws ParserConfigurationException {
-        DocumentBuilder documentBuilder = createDocumentBuilder(true);
+        DocumentBuilder documentBuilder = getDocumentBuilder(true);
         Document doc = documentBuilder.newDocument();
         repoolDocumentBuilder(documentBuilder, true);
         return doc;
@@ -1015,7 +1021,7 @@
     }
 
     public static Document read(InputStream inputStream, boolean disAllowDocTypeDeclarations) throws ParserConfigurationException, SAXException, IOException {
-        DocumentBuilder documentBuilder = createDocumentBuilder(disAllowDocTypeDeclarations);
+        DocumentBuilder documentBuilder = getDocumentBuilder(disAllowDocTypeDeclarations);
         Document doc = documentBuilder.parse(inputStream);
         repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations);
         return doc;
@@ -1023,7 +1029,7 @@
 
     public static Document read(String uri, boolean disAllowDocTypeDeclarations)
         throws ParserConfigurationException, SAXException, IOException {
-        DocumentBuilder documentBuilder = createDocumentBuilder(disAllowDocTypeDeclarations);
+        DocumentBuilder documentBuilder = getDocumentBuilder(disAllowDocTypeDeclarations);
         Document doc = documentBuilder.parse(uri);
         repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations);
         return doc;
@@ -1035,26 +1041,12 @@
 
     public static Document read(InputSource inputSource, boolean disAllowDocTypeDeclarations)
         throws ParserConfigurationException, SAXException, IOException {
-        DocumentBuilder documentBuilder = createDocumentBuilder(disAllowDocTypeDeclarations);
+        DocumentBuilder documentBuilder = getDocumentBuilder(disAllowDocTypeDeclarations);
         Document doc = documentBuilder.parse(inputSource);
         repoolDocumentBuilder(documentBuilder, disAllowDocTypeDeclarations);
         return doc;
     }
 
-    private static DocumentBuilder createDocumentBuilder(
-        boolean disAllowDocTypeDeclarations
-    ) throws ParserConfigurationException {
-        int idx = getPoolsIndex(disAllowDocTypeDeclarations);
-        return pools[idx].getObject();
-    }
-
-
-    private static boolean repoolDocumentBuilder(DocumentBuilder db, boolean disAllowDocTypeDeclarations) {
-        db.reset();
-        int idx = getPoolsIndex(disAllowDocTypeDeclarations);
-        return pools[idx].repool(db);
-    }
-
     /**
      * Returns a byte-array representation of a <code>{@link BigInteger}</code>.
      * No sign-bit is output.
@@ -1100,34 +1092,75 @@
         return resizedBytes;
     }
 
-    private static final class DocumentBuilderPool
-        extends WeakObjectPool<DocumentBuilder, ParserConfigurationException> {
-
-        private final boolean disAllowDocTypeDeclarations;
-
-        public DocumentBuilderPool(boolean disAllowDocTypeDeclarations) {
-            this.disAllowDocTypeDeclarations = disAllowDocTypeDeclarations;
+    private static DocumentBuilder getDocumentBuilder(boolean disAllowDocTypeDeclarations) throws ParserConfigurationException {
+        ClassLoader loader = getContextClassLoader();
+        if (loader == null) {
+            loader = getClassLoader(XMLUtils.class);
+        }
+        if (loader == null) {
+            return createDocumentBuilder(disAllowDocTypeDeclarations);
         }
 
-        @Override
-        protected DocumentBuilder createObject() throws ParserConfigurationException {
-            DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
-            dfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
-            if (disAllowDocTypeDeclarations) {
-                dfactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+        Map<ClassLoader, Queue<DocumentBuilder>> docBuilderCache =
+            disAllowDocTypeDeclarations ? DOCUMENT_BUILDERS_DISALLOW_DOCTYPE : DOCUMENT_BUILDERS;
+        Queue<DocumentBuilder> queue = docBuilderCache.get(loader);
+        if (queue == null) {
+            queue = new ArrayBlockingQueue<>(parserPoolSize);
+            docBuilderCache.put(loader, queue);
+        }
+
+        DocumentBuilder db = queue.poll();
+        if (db == null) {
+            db = createDocumentBuilder(disAllowDocTypeDeclarations);
+        }
+        return db;
+    }
+
+    private static DocumentBuilder createDocumentBuilder(boolean disAllowDocTypeDeclarations) throws ParserConfigurationException {
+        DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+        f.setNamespaceAware(true);
+        f.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", disAllowDocTypeDeclarations);
+        return f.newDocumentBuilder();
+    }
+
+    private static void repoolDocumentBuilder(DocumentBuilder db, boolean disAllowDocTypeDeclarations) {
+        ClassLoader loader = getContextClassLoader();
+        if (loader == null) {
+            loader = getClassLoader(XMLUtils.class);
+        }
+        if (loader != null) {
+            Queue<DocumentBuilder> queue =
+                disAllowDocTypeDeclarations ? DOCUMENT_BUILDERS_DISALLOW_DOCTYPE.get(loader) : DOCUMENT_BUILDERS.get(loader);
+            if (queue != null) {
+                db.reset();
+                queue.offer(db);
             }
-            dfactory.setNamespaceAware(true);
-            return dfactory.newDocumentBuilder();
         }
     }
 
-    /**
-     * Maps the boolean configuration options for the factories to the array index for the WeakObjectPool
-     * @param disAllowDocTypeDeclarations
-     * @return the index to the {@link #pools}
-     */
-    private static int getPoolsIndex(boolean disAllowDocTypeDeclarations) {
-        return disAllowDocTypeDeclarations ? 1 : 0;
+    private static ClassLoader getContextClassLoader() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+                public ClassLoader run() {
+                    return Thread.currentThread().getContextClassLoader();
+                }
+            });
+        }
+        return Thread.currentThread().getContextClassLoader();
+    }
+
+    private static ClassLoader getClassLoader(final Class<?> clazz) {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+                public ClassLoader run() {
+                    return clazz.getClassLoader();
+                }
+            });
+        }
+        return clazz.getClassLoader();
     }
 
 }