View Javadoc
1   package com.atlassian.plugin.util;
2   
3   import com.google.common.base.Predicates;
4   import com.google.common.collect.ImmutableList;
5   import com.google.common.collect.Iterables;
6   import org.slf4j.Logger;
7   import org.slf4j.LoggerFactory;
8   
9   import java.io.IOException;
10  import java.io.InputStream;
11  import java.net.URL;
12  import java.util.Collections;
13  import java.util.Enumeration;
14  import java.util.LinkedList;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.NoSuchElementException;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  import static com.google.common.base.Preconditions.checkState;
21  
22  /**
23   * A class loader that delegates to a list of class loaders. The order is important as classes and resources will be
24   * loaded from the first classloader that can load them.  This class is optimized for a small number of classloaders.
25   *
26   * @since 2.6.0
27   */
28  public class ChainingClassLoader extends ClassLoader {
29      private static final Logger log = LoggerFactory.getLogger(ChainingClassLoader.class);
30  
31      /**
32       * The list of classloader to delegate to.
33       */
34      private final List<ClassLoader> classLoaders;
35  
36      /**
37       * Map of which resources are overridden by other resources
38       */
39      private final Map<String, String> resourceRedirects;
40  
41      /**
42       * Constructs a chaining classloader
43       *
44       * @param classLoaders The classloaders to delegate to, in order
45       */
46      public ChainingClassLoader(ClassLoader... classLoaders) {
47          this(Collections.<String, String>emptyMap(), classLoaders);
48      }
49  
50      /**
51       * Constructs a classloader that overrides certain resources
52       *
53       * @param resourceRedirects The map of resources to redirect
54       * @param classLoaders      The classloaders to delegate to, in order
55       */
56      public ChainingClassLoader(Map<String, String> resourceRedirects, ClassLoader... classLoaders) {
57          super(null);
58          this.resourceRedirects = checkNotNull(resourceRedirects);
59          this.classLoaders = ImmutableList.copyOf(classLoaders);
60          checkState(!Iterables.any(this.classLoaders, Predicates.isNull()), "ClassLoader arguments cannot be null");
61      }
62  
63      @Override
64      public Class loadClass(String name) throws ClassNotFoundException {
65          for (ClassLoader classloader : classLoaders) {
66              try {
67                  return classloader.loadClass(name);
68              } catch (ClassNotFoundException e) {
69                  // ignoring until we reach the end of the list since we are chaining
70              }
71          }
72          throw new ClassNotFoundException(name);
73      }
74  
75      @Override
76      public Enumeration<URL> getResources(String name) throws IOException {
77          return new ResourcesEnumeration(getAlternativeResourceName(name), classLoaders);
78      }
79  
80      @Override
81      public URL getResource(String name) {
82          final String realResourceName = getAlternativeResourceName(name);
83          for (ClassLoader classloader : classLoaders) {
84              final URL url = classloader.getResource(realResourceName);
85              if (url != null) {
86                  return url;
87              }
88          }
89          return null;
90      }
91  
92      @Override
93      public InputStream getResourceAsStream(String name) {
94          final String realResourceName = getAlternativeResourceName(name);
95          for (ClassLoader classloader : classLoaders) {
96              final InputStream inputStream = classloader.getResourceAsStream(realResourceName);
97              if (inputStream != null) {
98                  return inputStream;
99              }
100         }
101 
102         if (!name.equals(realResourceName)) {
103             //looks like we didn't find anything with the alternate resourcename.  Lets fall back to the
104             //original resource name!
105             log.debug("No resource found with alternate resourceName '{}'. Falling back to original name '{}'.", realResourceName, name);
106             for (ClassLoader classloader : classLoaders) {
107                 final InputStream inputStream = classloader.getResourceAsStream(name);
108                 if (inputStream != null) {
109                     return inputStream;
110                 }
111             }
112         }
113         return null;
114     }
115 
116     private String getAlternativeResourceName(String name) {
117         String resultName = name;
118         if (resourceRedirects.containsKey(name)) {
119             String redirectedName = resourceRedirects.get(name);
120             log.debug("Redirecting resource '{}' to '{}'", name, redirectedName);
121             resultName = redirectedName;
122         }
123         return resultName;
124     }
125 
126     @Override
127     public synchronized void setDefaultAssertionStatus(boolean enabled) {
128         for (ClassLoader classloader : classLoaders) {
129             classloader.setDefaultAssertionStatus(enabled);
130         }
131     }
132 
133     @Override
134     public synchronized void setPackageAssertionStatus(String packageName, boolean enabled) {
135         for (ClassLoader classloader : classLoaders) {
136             classloader.setPackageAssertionStatus(packageName, enabled);
137         }
138     }
139 
140     @Override
141     public synchronized void setClassAssertionStatus(String className, boolean enabled) {
142         for (ClassLoader classloader : classLoaders) {
143             classloader.setClassAssertionStatus(className, enabled);
144         }
145     }
146 
147     @Override
148     public synchronized void clearAssertionStatus() {
149         for (ClassLoader classloader : classLoaders) {
150             classloader.clearAssertionStatus();
151         }
152     }
153 
154     private static final class ResourcesEnumeration implements Enumeration<URL> {
155         private final List<Enumeration<URL>> resources;
156         private final String resourceName;
157 
158         ResourcesEnumeration(String resourceName, List<ClassLoader> classLoaders) throws IOException {
159             this.resourceName = resourceName;
160             this.resources = new LinkedList<Enumeration<URL>>();
161             for (ClassLoader classLoader : classLoaders) {
162                 resources.add(classLoader.getResources(resourceName));
163             }
164         }
165 
166         public boolean hasMoreElements() {
167             for (Enumeration<URL> resource : resources) {
168                 if (resource.hasMoreElements()) {
169                     return true;
170                 }
171             }
172 
173             return false;
174         }
175 
176         public URL nextElement() {
177             for (Enumeration<URL> resource : resources) {
178                 if (resource.hasMoreElements()) {
179                     return resource.nextElement();
180                 }
181             }
182             throw new NoSuchElementException(resourceName);
183         }
184     }
185 }