View Javadoc

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