View Javadoc

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      private static final Logger log = LoggerFactory.getLogger(ChainingClassLoader.class);
25  
26      private static final String SERVICES_PREFIX = "META-INF/services";
27      private static final String ALTERNATE_SERVICES_PREFIX = "META-INF/alternate-services";
28      private static final Set<String> ALTERNATE_SERVICES = ImmutableSet.of(
29              SERVICES_PREFIX + "/com.sun.jersey.server.impl.model.method.dispatch.ResourceMethodDispatchProvider",
30              SERVICES_PREFIX + "/com.sun.jersey.spi.container.ContainerProvider",
31              SERVICES_PREFIX + "/com.sun.jersey.spi.container.ContainerRequestFilter",
32              SERVICES_PREFIX + "/com.sun.jersey.spi.container.WebApplicationProvider",
33              SERVICES_PREFIX + "/com.sun.jersey.spi.HeaderDelegateProvider",
34              SERVICES_PREFIX + "/com.sun.jersey.spi.StringReaderProvider",
35              SERVICES_PREFIX + "/javax.ws.rs.ext.MessageBodyReader",
36              SERVICES_PREFIX + "/javax.ws.rs.ext.MessageBodyWriter",
37              SERVICES_PREFIX + "/javax.ws.rs.ext.RuntimeDelegate");
38  
39      /**
40       * The list of classloader to delegate to.
41       */
42      private final List<ClassLoader> classLoaders;
43  
44      public ChainingClassLoader(ClassLoader... classLoaders) {
45          Validate.noNullElements(classLoaders, "ClassLoader arguments cannot be null");
46          this.classLoaders = Arrays.asList(classLoaders);
47      }
48  
49      @Override
50      public Class loadClass(String name) throws ClassNotFoundException {
51          for (ClassLoader classloader : classLoaders) {
52              try {
53                  return classloader.loadClass(name);
54              } catch (ClassNotFoundException e) {
55                  // ignoring until we reach the end of the list since we are chaining
56              }
57          }
58          throw new ClassNotFoundException(name);
59      }
60  
61      @Override
62      public Enumeration<URL> getResources(String name) throws IOException {
63          return new ResourcesEnumeration(getAlternativeResourceName(name), classLoaders);
64      }
65  
66      @Override
67      public URL getResource(String name) {
68          final String realResourceName = getAlternativeResourceName(name);
69          for (ClassLoader classloader : classLoaders) {
70              final URL url = classloader.getResource(realResourceName);
71              if (url != null) {
72                  return url;
73              }
74          }
75          return null;
76      }
77  
78      @Override
79      public InputStream getResourceAsStream(String name) {
80          final String realResourceName = getAlternativeResourceName(name);
81          for (ClassLoader classloader : classLoaders) {
82              final InputStream inputStream = classloader.getResourceAsStream(realResourceName);
83              if (inputStream != null) {
84                  return inputStream;
85              }
86          }
87  
88          if (!name.equals(realResourceName)) {
89              //looks like we didn't find anything with the alternate resourcename.  Lets fall back to the
90              //original resource name!
91              log.debug("No resource found with alternate resourceName '{}'. Falling back to original name '{}'.", realResourceName, name);
92              for (ClassLoader classloader : classLoaders) {
93                  final InputStream inputStream = classloader.getResourceAsStream(name);
94                  if (inputStream != null) {
95                      return inputStream;
96                  }
97              }
98          }
99          return null;
100     }
101 
102     private String getAlternativeResourceName(String name) {
103         if (ALTERNATE_SERVICES.contains(name)) {
104             log.debug("Service '{}' is registered as an alternate service.", name);
105             return StringUtils.replace(name, SERVICES_PREFIX, ALTERNATE_SERVICES_PREFIX, 1);
106         } else {
107             return name;
108         }
109     }
110 
111     @Override
112     public synchronized void setDefaultAssertionStatus(boolean enabled) {
113         for (ClassLoader classloader : classLoaders) {
114             classloader.setDefaultAssertionStatus(enabled);
115         }
116     }
117 
118     @Override
119     public synchronized void setPackageAssertionStatus(String packageName, boolean enabled) {
120         for (ClassLoader classloader : classLoaders) {
121             classloader.setPackageAssertionStatus(packageName, enabled);
122         }
123     }
124 
125     @Override
126     public synchronized void setClassAssertionStatus(String className, boolean enabled) {
127         for (ClassLoader classloader : classLoaders) {
128             classloader.setClassAssertionStatus(className, enabled);
129         }
130     }
131 
132     @Override
133     public synchronized void clearAssertionStatus() {
134         for (ClassLoader classloader : classLoaders) {
135             classloader.clearAssertionStatus();
136         }
137     }
138 
139     private static final class ResourcesEnumeration implements Enumeration<URL> {
140         private final List<Enumeration<URL>> resources;
141         private final String resourceName;
142 
143         ResourcesEnumeration(String resourceName, List<ClassLoader> classLoaders) throws IOException {
144             this.resourceName = resourceName;
145             this.resources = new LinkedList<Enumeration<URL>>();
146             for (ClassLoader classLoader : classLoaders) {
147                 resources.add(classLoader.getResources(resourceName));
148             }
149         }
150 
151         public boolean hasMoreElements() {
152             for (Enumeration<URL> resource : resources) {
153                 if (resource.hasMoreElements()) {
154                     return true;
155                 }
156             }
157 
158             return false;
159         }
160 
161         public URL nextElement() {
162             for (Enumeration<URL> resource : resources) {
163                 if (resource.hasMoreElements()) {
164                     return resource.nextElement();
165                 }
166             }
167             throw new NoSuchElementException(resourceName);
168         }
169     }
170 }