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