001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.core.framework.util;
017    
018    import java.lang.ref.WeakReference;
019    import java.lang.reflect.InvocationTargetException;
020    import java.lang.reflect.Method;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Collections;
024    import java.util.Set;
025    
026    /**
027     * ThreadLocal subclass that is capable of clearing instances of itself registered with threads in the VM.
028     */
029    public class ApplicationThreadLocal<T> extends ThreadLocal<T> {
030        /**
031         * A list of weak references to existing ApplicationThreadLocals.  These instances will be remove()d from all threads when
032         * {@link #clear()} is called.
033         */
034        private static final Collection<WeakReference<ApplicationThreadLocal>> threadLocals = Collections.synchronizedCollection(new ArrayList<WeakReference<ApplicationThreadLocal>>());
035    
036        public ApplicationThreadLocal() {
037            threadLocals.add(new WeakReference(this));
038        }
039    
040        /**
041         * Perform brittle hacks to unset ThreadLocal for a thread.
042         * NOTE: this method is not threadsafe. the underlying implementation probably reasonably assumes ThreadLocalMap remove is only
043         * going to be called from the owning thread. since our explicit purpose is to forcibly yank these thread locals from arbitrary
044         * threads, we are breaking this assumption. we're doing the best we can.
045         * @param t the thread
046         */
047        protected boolean remove(Thread t) {
048            return removeThreadLocal(t, this);
049        }
050    
051        /**
052         * Utility method for removing a specific ThreadLocal from a Thread
053         * @param thread the thread
054         * @param threadLocal the threadlocal to remove()
055         * @return true if successful false otherwise
056         */
057        public static boolean removeThreadLocal(Thread thread, ThreadLocal threadLocal) {
058            try {
059                // call package method getMap on self
060                Method getMap = ThreadLocal.class.getDeclaredMethod("getMap", Thread.class);
061                getMap.setAccessible(true);
062                Object map = getMap.invoke(threadLocal, thread);
063                // if ThreadLocalMap has been set
064                if (map != null) {
065                    // call private method remove on ThreadLocalMap
066                    Method remove = map.getClass().getDeclaredMethod("remove", ThreadLocal.class);
067                    remove.setAccessible(true);
068                    remove.invoke(map, threadLocal);
069                }
070                return true;
071                // we don't really have any recourse here, so just print the stack trace
072            } catch (NoSuchMethodException nsme) {
073                nsme.printStackTrace();
074            } catch (IllegalAccessException iae) {
075                iae.printStackTrace();
076            } catch (InvocationTargetException ite) {
077                ite.printStackTrace();
078            }
079            return false;
080        }
081    
082        /**
083         * Remove all ApplicationThreadLocal instances from threads.
084         */
085        public static boolean clear() {
086            // the default mutex for a SynchronizedCollection is self.
087            Collection<WeakReference<ApplicationThreadLocal>> toRemove;
088            synchronized (threadLocals) {
089                toRemove = new ArrayList<WeakReference<ApplicationThreadLocal>>(threadLocals);
090                // here we eagerly clear our list irrespective of the success of clearing individual ATLs (assuming there is really nothing we can do about it anyway)
091                threadLocals.clear();
092            }
093            boolean success = true;
094            Set<Thread> allThreads = Thread.getAllStackTraces().keySet();
095            for (WeakReference<ApplicationThreadLocal> ref: toRemove) {
096                ApplicationThreadLocal tl = ref.get();
097                // maybe we're lucky and it was garbage-collectible and already collected
098                if (tl != null) {
099                    // otherwise remove it from all Threads
100                    for (Thread t: allThreads) {
101                       success &= tl.remove(t);
102                    }
103                }
104            }
105            return success;
106        }
107    }