XMLBEANS-502: Allow to manually clear ThreadLocals via a new ThreadLocalUtil.clearAllThreadLocals()
 
This allows to avoid memory leaks or log warnings in applications that 
make heavy use of thread-pools or web-containers, e.g. Tomcat

git-svn-id: https://svn.apache.org/repos/asf/xmlbeans/trunk@1855161 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/CHANGES.txt b/CHANGES.txt
index 03a9ec0..997fe58 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 Changes in V3.0.3 since V3.0.2
 
+* XMLBEANS-502: Allow to clear all ThreadLocals from the current thread
 * XMLBEANS-503: Allow to specify -nowarn in the Ant task
 * XMLBEANS-537: Add missing StscState.end() to avoid memory leaks
 * XMLBEANS-532: Streamline build.xml and update tests to Junit4
diff --git a/src/common/org/apache/xmlbeans/impl/common/SystemCache.java b/src/common/org/apache/xmlbeans/impl/common/SystemCache.java
index 580a1f7..0520c59 100644
--- a/src/common/org/apache/xmlbeans/impl/common/SystemCache.java
+++ b/src/common/org/apache/xmlbeans/impl/common/SystemCache.java
@@ -95,6 +95,10 @@
 
     private ThreadLocal tl_saxLoaders = new ThreadLocal();
 
+    public void clearThreadLocals() {
+        tl_saxLoaders.remove();
+    }
+
     public Object getSaxLoader()
     {
         SoftReference s = (SoftReference) tl_saxLoaders.get();
diff --git a/src/store/org/apache/xmlbeans/impl/store/CharUtil.java b/src/store/org/apache/xmlbeans/impl/store/CharUtil.java
index c3b8fa0..2c8f86d 100755
--- a/src/store/org/apache/xmlbeans/impl/store/CharUtil.java
+++ b/src/store/org/apache/xmlbeans/impl/store/CharUtil.java
@@ -909,6 +909,10 @@
     private static ThreadLocal tl_charUtil =
         new ThreadLocal() { protected Object initialValue() { return new SoftReference(new CharUtil( CHARUTIL_INITIAL_BUFSIZE )); } };
 
+    public static void clearThreadLocals() {
+        tl_charUtil.remove();
+    }
+
     private CharIterator _charIter = new CharIterator();
 
     // TODO - 64 is kinda arbitrary.  Perhaps it should be configurable.
diff --git a/src/store/org/apache/xmlbeans/impl/store/Locale.java b/src/store/org/apache/xmlbeans/impl/store/Locale.java
index e63466d..ab6230f 100755
--- a/src/store/org/apache/xmlbeans/impl/store/Locale.java
+++ b/src/store/org/apache/xmlbeans/impl/store/Locale.java
@@ -1897,6 +1897,10 @@
             }
         };
 
+    public static void clearThreadLocals() {
+        tl_scrubBuffer.remove();
+    }
+
     static ScrubBuffer getScrubBuffer(int wsr)
     {
         SoftReference softRef = (SoftReference) tl_scrubBuffer.get();
diff --git a/src/typeimpl/org/apache/xmlbeans/impl/schema/SchemaTypeLoaderImpl.java b/src/typeimpl/org/apache/xmlbeans/impl/schema/SchemaTypeLoaderImpl.java
index 04c8e47..a4e9472 100644
--- a/src/typeimpl/org/apache/xmlbeans/impl/schema/SchemaTypeLoaderImpl.java
+++ b/src/typeimpl/org/apache/xmlbeans/impl/schema/SchemaTypeLoaderImpl.java
@@ -71,6 +71,13 @@
         private ThreadLocal _cachedTypeSystems =
             new ThreadLocal() { protected Object initialValue() { return new ArrayList(); } };
 
+        @Override
+        public void clearThreadLocals() {
+            _cachedTypeSystems.remove();
+
+            super.clearThreadLocals();
+        }
+
         public SchemaTypeLoader getFromTypeLoaderCache(ClassLoader cl)
         {
             ArrayList a = (ArrayList) _cachedTypeSystems.get();
diff --git a/src/typeimpl/org/apache/xmlbeans/impl/schema/StscState.java b/src/typeimpl/org/apache/xmlbeans/impl/schema/StscState.java
index c03a094..f464200 100644
--- a/src/typeimpl/org/apache/xmlbeans/impl/schema/StscState.java
+++ b/src/typeimpl/org/apache/xmlbeans/impl/schema/StscState.java
@@ -1267,6 +1267,10 @@
 
     private static ThreadLocal tl_stscStack = new ThreadLocal();
 
+    public static void clearThreadLocals() {
+        tl_stscStack.remove();
+    }
+
     public static StscState start()
     {
         StscStack stscStack = (StscStack) tl_stscStack.get();
diff --git a/src/typeimpl/org/apache/xmlbeans/impl/values/NamespaceContext.java b/src/typeimpl/org/apache/xmlbeans/impl/values/NamespaceContext.java
index 692197c..f7be564 100644
--- a/src/typeimpl/org/apache/xmlbeans/impl/values/NamespaceContext.java
+++ b/src/typeimpl/org/apache/xmlbeans/impl/values/NamespaceContext.java
@@ -90,6 +90,10 @@
 
     private static ThreadLocal tl_namespaceContextStack = new ThreadLocal();
 
+    public static void clearThreadLocals() {
+        tl_namespaceContextStack.remove();
+    }
+
     private static NamespaceContextStack getNamespaceContextStack()
     {
         NamespaceContextStack namespaceContextStack = (NamespaceContextStack) tl_namespaceContextStack.get();
diff --git a/src/xmlpublic/org/apache/xmlbeans/ThreadLocalUtil.java b/src/xmlpublic/org/apache/xmlbeans/ThreadLocalUtil.java
new file mode 100644
index 0000000..614cade
--- /dev/null
+++ b/src/xmlpublic/org/apache/xmlbeans/ThreadLocalUtil.java
@@ -0,0 +1,46 @@
+/*   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.xmlbeans;
+
+import org.apache.xmlbeans.impl.common.SystemCache;
+import org.apache.xmlbeans.impl.schema.StscState;
+import org.apache.xmlbeans.impl.store.CharUtil;
+import org.apache.xmlbeans.impl.store.Locale;
+import org.apache.xmlbeans.impl.values.NamespaceContext;
+
+public class ThreadLocalUtil {
+
+    /**
+     * Clear {@link ThreadLocal}s of the current thread.
+     *
+     * This can be used to clean out a thread before "returning"
+     * it to a thread-pool or a Web-Container like Tomcat.
+     */
+    public static void clearAllThreadLocals() {
+        // clear thread locals in all classes which may hold some
+        XmlBeans.clearThreadLocals();
+        XmlFactoryHook.ThreadContext.clearThreadLocals();
+        StscState.clearThreadLocals();
+        CharUtil.clearThreadLocals();
+        Locale.clearThreadLocals();
+        NamespaceContext.clearThreadLocals();
+
+        // SystemCache is not a singleton, but also creates ThreadLocals,
+        // so we get the current instance and clean it out as well
+        SystemCache systemCache = SystemCache.get();
+        systemCache.clearThreadLocals();
+    }
+}
diff --git a/src/xmlpublic/org/apache/xmlbeans/XmlBeans.java b/src/xmlpublic/org/apache/xmlbeans/XmlBeans.java
index 4d271bf..9cfd544 100644
--- a/src/xmlpublic/org/apache/xmlbeans/XmlBeans.java
+++ b/src/xmlpublic/org/apache/xmlbeans/XmlBeans.java
@@ -90,6 +90,11 @@
             }
         };
 
+    public static void clearThreadLocals() {
+        // clear thread local here
+        _threadLocalLoaderQNameCache.remove();
+    }
+
     /**
      * Returns a thread local QNameCache
      */
diff --git a/src/xmlpublic/org/apache/xmlbeans/XmlFactoryHook.java b/src/xmlpublic/org/apache/xmlbeans/XmlFactoryHook.java
index 6aca07f..55e7cc8 100644
--- a/src/xmlpublic/org/apache/xmlbeans/XmlFactoryHook.java
+++ b/src/xmlpublic/org/apache/xmlbeans/XmlFactoryHook.java
@@ -92,6 +92,10 @@
     {
         private static ThreadLocal threadHook = new ThreadLocal();
 
+        public static void clearThreadLocals() {
+            threadHook.remove();
+        }
+
         /**
          * Returns the current thread's hook, or null if none.
          */ 
diff --git a/test/src/misc/checkin/CharUtilTests.java b/test/src/misc/checkin/CharUtilTests.java
index ecf65c1..28302e5 100644
--- a/test/src/misc/checkin/CharUtilTests.java
+++ b/test/src/misc/checkin/CharUtilTests.java
@@ -22,6 +22,7 @@
 import java.util.Random;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 public class CharUtilTests
 {
@@ -232,4 +233,11 @@
     }
 
     private Random _rnd = new Random( 0 );
+
+    @Test
+    public void testThreadLocal() {
+        assertNotNull("Should always get a CharUtil from ThreadLocals", CharUtil.getThreadLocalCharUtil());
+        CharUtil.clearThreadLocals();
+        assertNotNull("Should always get a CharUtil from ThreadLocals", CharUtil.getThreadLocalCharUtil());
+    }
 }
\ No newline at end of file
diff --git a/test/src/misc/detailed/SystemCacheTests.java b/test/src/misc/detailed/SystemCacheTests.java
index 36a6acd..16bfaee 100644
--- a/test/src/misc/detailed/SystemCacheTests.java
+++ b/test/src/misc/detailed/SystemCacheTests.java
@@ -20,10 +20,11 @@
 import org.junit.Test;

 

 import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertNull;

 

 public class SystemCacheTests {

     @Test

-    public void testSystemCacheImplFromAPITest() throws Throwable {

+    public void testSystemCacheImplFromAPITest() {

         // store the default SystemCache implementation before switch

         SystemCache defaultImpl = SystemCache.get();

 

@@ -33,11 +34,25 @@
         SystemCacheTestImpl testImpl = new SystemCacheTestImpl();

         SystemCache.set(testImpl);

         assertEquals("misc.detailed.SystemCacheTestImpl", testImpl.getClass().getName());

-        assertEquals(testImpl.getAccessed(), 1);

+        assertEquals(SystemCacheTestImpl.getAccessed(), 1);

 

         // switch back to default impl

         SystemCache.set(defaultImpl);

         assertEquals("org.apache.xmlbeans.impl.common.SystemCache", defaultImpl.getClass().getName());

     }

 

+    @Test

+    public void testClearThreadLocal() {

+        SystemCache cache = SystemCache.get();

+        String saxLoader = "object is not cast currently...";

+

+        cache.setSaxLoader(saxLoader);

+        assertEquals(saxLoader, cache.getSaxLoader());

+

+        cache.clearThreadLocals();

+        assertNull(cache.getSaxLoader());

+

+        cache.setSaxLoader(saxLoader);

+        assertEquals(saxLoader, cache.getSaxLoader());

+    }

 }

diff --git a/test/src/misc/detailed/ThreadLocalUtilTest.java b/test/src/misc/detailed/ThreadLocalUtilTest.java
new file mode 100644
index 0000000..b0335c8
--- /dev/null
+++ b/test/src/misc/detailed/ThreadLocalUtilTest.java
@@ -0,0 +1,44 @@
+/*   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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 misc.detailed;
+
+import org.apache.xmlbeans.ThreadLocalUtil;
+import org.apache.xmlbeans.impl.common.SystemCache;
+import org.apache.xmlbeans.impl.schema.StscState;
+import org.apache.xmlbeans.impl.store.CharUtil;
+import org.junit.Test;
+
+public class ThreadLocalUtilTest {
+    @Test
+    public void testClearThreadLocalsNoData() {
+        // simply calling it without any thread locals should work
+        ThreadLocalUtil.clearAllThreadLocals();
+    }
+
+    @Test
+    public void testClearThreadLocalsWithData() {
+        // calling it with thread locals should work as well
+        CharUtil.getThreadLocalCharUtil();
+
+        SystemCache cache = SystemCache.get();
+        String saxLoader = "object is not cast currently...";
+        cache.setSaxLoader(saxLoader);
+        StscState.start();
+        StscState.end();
+
+        ThreadLocalUtil.clearAllThreadLocals();
+    }
+}