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              SERVICES_PREFIX + "/javax.xml.bind.JAXBContext");
40  
41      /**
42       * The list of classloader to delegate to.
43       */
44      private final List<ClassLoader> classLoaders;
45  
46      public ChainingClassLoader(ClassLoader... classLoaders)
47      {
48          Validate.noNullElements(classLoaders, "ClassLoader arguments cannot be null");
49          this.classLoaders = Arrays.asList(classLoaders);
50      }
51  
52      @Override
53      public Class loadClass(String name) throws ClassNotFoundException
54      {
55          for (ClassLoader classloader : classLoaders)
56          {
57              try
58              {
59                  return classloader.loadClass(name);
60              }
61              catch (ClassNotFoundException e)
62              {
63                  // ignoring until we reach the end of the list since we are chaining
64              }
65          }
66          throw new ClassNotFoundException(name);
67      }
68  
69      @Override
70      public Enumeration<URL> getResources(String name) throws IOException
71      {
72          return new ResourcesEnumeration(getAlternativeResourceName(name), classLoaders);
73      }
74  
75      @Override
76      public URL getResource(String name)
77      {
78          final String realResourceName = getAlternativeResourceName(name);
79          for (ClassLoader classloader : classLoaders)
80          {
81              final URL url = classloader.getResource(realResourceName);
82              if (url != null)
83              {
84                  return url;
85              }
86          }
87          return null;
88      }
89  
90      @Override
91      public InputStream getResourceAsStream(String name)
92      {
93          final String realResourceName = getAlternativeResourceName(name);
94          for (ClassLoader classloader : classLoaders)
95          {
96              final InputStream inputStream = classloader.getResourceAsStream(realResourceName);
97              if (inputStream != null)
98              {
99                  return inputStream;
100             }
101         }
102 
103         if (!name.equals(realResourceName))
104         {
105             //looks like we didn't find anything with the alternate resourcename.  Lets fall back to the
106             //original resource name!
107             log.debug("No resource found with alternate resourceName '{}'. Falling back to original name '{}'.", realResourceName, name);
108             for (ClassLoader classloader : classLoaders)
109             {
110                 final InputStream inputStream = classloader.getResourceAsStream(name);
111                 if (inputStream != null)
112                 {
113                     return inputStream;
114                 }
115             }
116         }
117         return null;
118     }
119 
120     private String getAlternativeResourceName(String name)
121     {
122         if (ALTERNATE_SERVICES.contains(name))
123         {
124             log.debug("Service '{}' is registered as an alternate service.", name);
125             return StringUtils.replace(name, SERVICES_PREFIX, ALTERNATE_SERVICES_PREFIX, 1);
126         }
127         else
128         {
129             return name;
130         }
131     }
132 
133     @Override
134     public synchronized void setDefaultAssertionStatus(boolean enabled)
135     {
136         for (ClassLoader classloader : classLoaders)
137         {
138             classloader.setDefaultAssertionStatus(enabled);
139         }
140     }
141 
142     @Override
143     public synchronized void setPackageAssertionStatus(String packageName, boolean enabled)
144     {
145         for (ClassLoader classloader : classLoaders)
146         {
147             classloader.setPackageAssertionStatus(packageName, enabled);
148         }
149     }
150 
151     @Override
152     public synchronized void setClassAssertionStatus(String className, boolean enabled)
153     {
154         for (ClassLoader classloader : classLoaders)
155         {
156             classloader.setClassAssertionStatus(className, enabled);
157         }
158     }
159 
160     @Override
161     public synchronized void clearAssertionStatus()
162     {
163         for (ClassLoader classloader : classLoaders)
164         {
165             classloader.clearAssertionStatus();
166         }
167     }
168 
169     private static final class ResourcesEnumeration implements Enumeration<URL>
170     {
171         private final List<Enumeration<URL>> resources;
172         private final String resourceName;
173 
174         ResourcesEnumeration(String resourceName, List<ClassLoader> classLoaders) throws IOException
175         {
176             this.resourceName = resourceName;
177             this.resources = new LinkedList<Enumeration<URL>>();
178             for (ClassLoader classLoader : classLoaders)
179             {
180                 resources.add(classLoader.getResources(resourceName));
181             }
182         }
183 
184         public boolean hasMoreElements()
185         {
186             for (Enumeration<URL> resource : resources)
187             {
188                 if (resource.hasMoreElements())
189                 {
190                     return true;
191                 }
192             }
193 
194             return false;
195         }
196 
197         public URL nextElement()
198         {
199             for (Enumeration<URL> resource : resources)
200             {
201                 if (resource.hasMoreElements())
202                 {
203                     return resource.nextElement();
204                 }
205             }
206             throw new NoSuchElementException(resourceName);
207         }
208     }
209 }