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 }