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