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