View Javadoc
1   package com.atlassian.plugin.util;
2   
3   import java.util.Deque;
4   import java.util.LinkedList;
5   
6   /**
7    * This utility provides a thread local stack of {@link ClassLoader}s.
8    * The current "top" of the stack is the thread's current context class loader.
9    * This can be used when implementing delegating plugin {@link java.util.logging.Filter}s or {@link javax.servlet.Servlet}s
10   * that need to set the {@link ClassLoader} to the {@link com.atlassian.plugin.classloader.PluginClassLoader} the filter
11   * or servlet is declared in.
12   *
13   * @since 2.5.0
14   */
15  public class ClassLoaderStack {
16      // We don't override initialValue as tomcat logs warnings if there is an object
17      // left in the thread local (even if it is an empty list)
18      private static final ThreadLocal<Deque<ClassLoader>> classLoaderStack = new ThreadLocal<Deque<ClassLoader>>();
19  
20      /**
21       * Makes the given classLoader the new ContextClassLoader for this thread, and pushes the current ContextClassLoader
22       * onto a ThreadLocal stack so that we can do a {@link #pop} operation later to return to that ContextClassLoader.
23       *
24       * <p>
25       * Passing null is allowed and will act as a no-op. This means that you can safely {@link #pop} a ClassLoader and {@link #push} it back in
26       * and it will work safely whether the stack was empty at time of {@link #pop} or not.
27       *
28       * @param loader The new ClassLoader to set as ContextClassLoader.
29       */
30      public static void push(ClassLoader loader) {
31          if (loader == null) {
32              return;
33          }
34  
35          Deque<ClassLoader> stack = classLoaderStack.get();
36          if (stack == null) {
37              stack = new LinkedList<ClassLoader>();
38              classLoaderStack.set(stack);
39          }
40          stack.push(Thread.currentThread().getContextClassLoader());
41          Thread.currentThread().setContextClassLoader(loader);
42      }
43  
44      /**
45       * Pops the current ContextClassLoader off the stack, setting the new ContextClassLoader to the previous one on the stack.
46       * <ul>
47       * <li>If the stack is not empty, then the current ClassLoader is replaced by the previous one on the stack, and then returned.</li>
48       * <li>If the stack is empty, then null is returned and the current ContextClassLoader is not changed.</li>
49       * </ul>
50       *
51       * @return the previous ContextClassLoader that was just replaced, or null if the stack is empty.
52       */
53      public static ClassLoader pop() {
54          Deque<ClassLoader> stack = classLoaderStack.get();
55          if (stack == null || stack.isEmpty()) {
56              return null;
57          }
58          ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
59          Thread.currentThread().setContextClassLoader(stack.pop());
60          if (stack.isEmpty()) {
61              classLoaderStack.remove();
62          }
63  
64          return currentClassLoader;
65      }
66  }