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