1 package com.atlassian.plugin.util;
2
3 import com.google.common.collect.ImmutableList;
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.Collections;
11 import java.util.Enumeration;
12 import java.util.LinkedList;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.NoSuchElementException;
16 import java.util.Objects;
17
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static com.google.common.base.Preconditions.checkState;
20
21
22
23
24
25
26
27 public class ChainingClassLoader extends ClassLoader {
28 private static final Logger log = LoggerFactory.getLogger(ChainingClassLoader.class);
29
30
31
32
33 private final List<ClassLoader> classLoaders;
34
35
36
37
38 private final Map<String, String> resourceRedirects;
39
40
41
42
43
44
45 public ChainingClassLoader(ClassLoader... classLoaders) {
46 this(Collections.emptyMap(), classLoaders);
47 }
48
49
50
51
52
53
54
55 public ChainingClassLoader(Map<String, String> resourceRedirects, ClassLoader... classLoaders) {
56 super(null);
57 this.resourceRedirects = checkNotNull(resourceRedirects);
58 this.classLoaders = ImmutableList.copyOf(classLoaders);
59 checkState(this.classLoaders.stream().allMatch(Objects::nonNull), "ClassLoader arguments cannot be null");
60 }
61
62 @Override
63 public Class loadClass(String name) throws ClassNotFoundException {
64 for (ClassLoader classloader : classLoaders) {
65 try {
66 return classloader.loadClass(name);
67 } catch (ClassNotFoundException e) {
68
69 }
70 }
71 throw new ClassNotFoundException(name);
72 }
73
74 @Override
75 public Enumeration<URL> getResources(String name) throws IOException {
76 return new ResourcesEnumeration(getAlternativeResourceName(name), classLoaders);
77 }
78
79 @Override
80 public URL getResource(String name) {
81 final String realResourceName = getAlternativeResourceName(name);
82 for (ClassLoader classloader : classLoaders) {
83 final URL url = classloader.getResource(realResourceName);
84 if (url != null) {
85 return url;
86 }
87 }
88 return null;
89 }
90
91 @Override
92 public InputStream getResourceAsStream(String name) {
93 final String realResourceName = getAlternativeResourceName(name);
94 for (ClassLoader classloader : classLoaders) {
95 final InputStream inputStream = classloader.getResourceAsStream(realResourceName);
96 if (inputStream != null) {
97 return inputStream;
98 }
99 }
100
101 if (!name.equals(realResourceName)) {
102
103
104 log.debug("No resource found with alternate resourceName '{}'. Falling back to original name '{}'.", realResourceName, name);
105 for (ClassLoader classloader : classLoaders) {
106 final InputStream inputStream = classloader.getResourceAsStream(name);
107 if (inputStream != null) {
108 return inputStream;
109 }
110 }
111 }
112 return null;
113 }
114
115 private String getAlternativeResourceName(String name) {
116 String resultName = name;
117 if (resourceRedirects.containsKey(name)) {
118 String redirectedName = resourceRedirects.get(name);
119 log.debug("Redirecting resource '{}' to '{}'", name, redirectedName);
120 resultName = redirectedName;
121 }
122 return resultName;
123 }
124
125 @Override
126 public synchronized void setDefaultAssertionStatus(boolean enabled) {
127 for (ClassLoader classloader : classLoaders) {
128 classloader.setDefaultAssertionStatus(enabled);
129 }
130 }
131
132 @Override
133 public synchronized void setPackageAssertionStatus(String packageName, boolean enabled) {
134 for (ClassLoader classloader : classLoaders) {
135 classloader.setPackageAssertionStatus(packageName, enabled);
136 }
137 }
138
139 @Override
140 public synchronized void setClassAssertionStatus(String className, boolean enabled) {
141 for (ClassLoader classloader : classLoaders) {
142 classloader.setClassAssertionStatus(className, enabled);
143 }
144 }
145
146 @Override
147 public synchronized void clearAssertionStatus() {
148 for (ClassLoader classloader : classLoaders) {
149 classloader.clearAssertionStatus();
150 }
151 }
152
153 private static final class ResourcesEnumeration implements Enumeration<URL> {
154 private final List<Enumeration<URL>> resources;
155 private final String resourceName;
156
157 ResourcesEnumeration(String resourceName, List<ClassLoader> classLoaders) throws IOException {
158 this.resourceName = resourceName;
159 this.resources = new LinkedList<>();
160 for (ClassLoader classLoader : classLoaders) {
161 resources.add(classLoader.getResources(resourceName));
162 }
163 }
164
165 public boolean hasMoreElements() {
166 for (Enumeration<URL> resource : resources) {
167 if (resource.hasMoreElements()) {
168 return true;
169 }
170 }
171
172 return false;
173 }
174
175 public URL nextElement() {
176 for (Enumeration<URL> resource : resources) {
177 if (resource.hasMoreElements()) {
178 return resource.nextElement();
179 }
180 }
181 throw new NoSuchElementException(resourceName);
182 }
183 }
184 }