1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.logging;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.URL;
23 import java.net.URLClassLoader;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.Map;
30
31 /***
32 * A ClassLoader which sees only specified classes, and which can be
33 * set to do parent-first or child-first path lookup.
34 * <p>
35 * Note that this classloader is not "industrial strength"; users
36 * looking for such a class may wish to look at the Tomcat sourcecode
37 * instead. In particular, this class may not be threadsafe.
38 * <p>
39 * Note that the ClassLoader.getResources method isn't overloaded here.
40 * It would be nice to ensure that when child-first lookup is set the
41 * resources from the child are returned earlier in the list than the
42 * resources from the parent. However overriding this method isn't possible
43 * as the java 1.4 version of ClassLoader declares this method final
44 * (though the java 1.5 version has removed the final qualifier). As the
45 * ClassLoader javadoc doesn't specify the order in which resources
46 * are returned, it's valid to return the resources in any order (just
47 * untidy) so the inherited implementation is technically ok.
48 */
49
50 public class PathableClassLoader extends URLClassLoader {
51
52 private static final URL[] NO_URLS = new URL[0];
53
54 /***
55 * A map of package-prefix to ClassLoader. Any class which is in
56 * this map is looked up via the specified classloader instead of
57 * the classpath associated with this classloader or its parents.
58 * <p>
59 * This is necessary in order for the rest of the world to communicate
60 * with classes loaded via a custom classloader. As an example, junit
61 * testcases which are loaded via a custom classloader needs to see
62 * the same junit classes as the code invoking the testcase, otherwise
63 * they can't pass result objects back.
64 * <p>
65 * Normally, only a classloader created with a null parent needs to
66 * have any lookasides defined.
67 */
68 private HashMap lookasides = null;
69
70 /***
71 * See setParentFirst.
72 */
73 private boolean parentFirst = true;
74
75 /***
76 * Constructor.
77 */
78 public PathableClassLoader(ClassLoader parent) {
79 super(NO_URLS, parent);
80 }
81
82 /***
83 * Allow caller to explicitly add paths. Generally this not a good idea;
84 * use addLogicalLib instead, then define the location for that logical
85 * library in the build.xml file.
86 */
87 public void addURL(URL url) {
88 super.addURL(url);
89 }
90
91 /***
92 * Specify whether this classloader should ask the parent classloader
93 * to resolve a class first, before trying to resolve it via its own
94 * classpath.
95 * <p>
96 * Checking with the parent first is the normal approach for java, but
97 * components within containers such as servlet engines can use
98 * child-first lookup instead, to allow the components to override libs
99 * which are visible in shared classloaders provided by the container.
100 * <p>
101 * Note that the method getResources always behaves as if parentFirst=true,
102 * because of limitations in java 1.4; see the javadoc for method
103 * getResourcesInOrder for details.
104 * <p>
105 * This value defaults to true.
106 */
107 public void setParentFirst(boolean state) {
108 parentFirst = state;
109 }
110
111 /***
112 * For classes with the specified prefix, get them from the system
113 * classpath <i>which is active at the point this method is called</i>.
114 * <p>
115 * This method is just a shortcut for
116 * <pre>
117 * useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
118 * </pre>
119 */
120 public void useSystemLoader(String prefix) {
121 useExplicitLoader(prefix, ClassLoader.getSystemClassLoader());
122
123 }
124
125 /***
126 * Specify a classloader to use for specific java packages.
127 */
128 public void useExplicitLoader(String prefix, ClassLoader loader) {
129 if (lookasides == null) {
130 lookasides = new HashMap();
131 }
132 lookasides.put(prefix, loader);
133 }
134
135 /***
136 * Specify a collection of logical libraries. See addLogicalLib.
137 */
138 public void addLogicalLib(String[] logicalLibs) {
139 for(int i=0; i<logicalLibs.length; ++i) {
140 addLogicalLib(logicalLibs[i]);
141 }
142 }
143
144 /***
145 * Specify a logical library to be included in the classpath used to
146 * locate classes.
147 * <p>
148 * The specified lib name is used as a key into the system properties;
149 * there is expected to be a system property defined with that name
150 * whose value is a url that indicates where that logical library can
151 * be found. Typically this is the name of a jar file, or a directory
152 * containing class files.
153 * <p>
154 * Using logical library names allows the calling code to specify its
155 * desired classpath without knowing the exact location of the necessary
156 * classes.
157 */
158 public void addLogicalLib(String logicalLib) {
159 String filename = System.getProperty(logicalLib);
160 if (filename == null) {
161 throw new UnknownError(
162 "Logical lib [" + logicalLib + "] is not defined"
163 + " as a System property.");
164 }
165
166 try {
167 URL url = new File(filename).toURL();
168 addURL(url);
169 } catch(java.net.MalformedURLException e) {
170 throw new UnknownError(
171 "Invalid file [" + filename + "] for logical lib [" + logicalLib + "]");
172 }
173 }
174
175 /***
176 * Override ClassLoader method.
177 * <p>
178 * For each explicitly mapped package prefix, if the name matches the
179 * prefix associated with that entry then attempt to load the class via
180 * that entries' classloader.
181 */
182 protected Class loadClass(String name, boolean resolve)
183 throws ClassNotFoundException {
184
185 if (name.startsWith("java.") || name.startsWith("javax.")) {
186 return super.loadClass(name, resolve);
187 }
188
189 if (lookasides != null) {
190 for(Iterator i = lookasides.entrySet().iterator(); i.hasNext(); ) {
191 Map.Entry entry = (Map.Entry) i.next();
192 String prefix = (String) entry.getKey();
193 if (name.startsWith(prefix) == true) {
194 ClassLoader loader = (ClassLoader) entry.getValue();
195 Class clazz = Class.forName(name, resolve, loader);
196 return clazz;
197 }
198 }
199 }
200
201 if (parentFirst) {
202 return super.loadClass(name, resolve);
203 } else {
204
205
206
207
208
209
210
211 try {
212 Class clazz = findLoadedClass(name);
213 if (clazz == null) {
214 clazz = super.findClass(name);
215 }
216 if (resolve) {
217 resolveClass(clazz);
218 }
219 return clazz;
220 } catch(ClassNotFoundException e) {
221 return super.loadClass(name, resolve);
222 }
223 }
224 }
225
226 /***
227 * Same as parent class method except that when parentFirst is false
228 * the resource is looked for in the local classpath before the parent
229 * loader is consulted.
230 */
231 public URL getResource(String name) {
232 if (parentFirst) {
233 return super.getResource(name);
234 } else {
235 URL local = super.findResource(name);
236 if (local != null) {
237 return local;
238 }
239 return super.getResource(name);
240 }
241 }
242
243 /***
244 * Emulate a proper implementation of getResources which respects the
245 * setting for parentFirst.
246 * <p>
247 * Note that it's not possible to override the inherited getResources, as
248 * it's declared final in java1.4 (thought that's been removed for 1.5).
249 * The inherited implementation always behaves as if parentFirst=true.
250 */
251 public Enumeration getResourcesInOrder(String name) throws IOException {
252 if (parentFirst) {
253 return super.getResources(name);
254 } else {
255 Enumeration localUrls = super.findResources(name);
256
257 ClassLoader parent = getParent();
258 if (parent == null) {
259
260
261
262
263
264
265
266
267
268
269 return localUrls;
270 }
271 Enumeration parentUrls = parent.getResources(name);
272
273 ArrayList localItems = toList(localUrls);
274 ArrayList parentItems = toList(parentUrls);
275 localItems.addAll(parentItems);
276 return Collections.enumeration(localItems);
277 }
278 }
279
280 /***
281 *
282 * Clean implementation of list function of
283 * {@link java.utils.Collection} added in JDK 1.4
284 * @param en <code>Enumeration</code>, possibly null
285 * @return <code>ArrayList</code> containing the enumerated
286 * elements in the enumerated order, not null
287 */
288 private ArrayList toList(Enumeration en) {
289 ArrayList results = new ArrayList();
290 if (en != null) {
291 while (en.hasMoreElements()){
292 Object element = en.nextElement();
293 results.add(element);
294 }
295 }
296 return results;
297 }
298
299 /***
300 * Same as parent class method except that when parentFirst is false
301 * the resource is looked for in the local classpath before the parent
302 * loader is consulted.
303 */
304 public InputStream getResourceAsStream(String name) {
305 if (parentFirst) {
306 return super.getResourceAsStream(name);
307 } else {
308 URL local = super.findResource(name);
309 if (local != null) {
310 try {
311 return local.openStream();
312 } catch(IOException e) {
313
314
315 return null;
316 }
317 }
318 return super.getResourceAsStream(name);
319 }
320 }
321 }