001: /*
002: * Copyright 2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.logging;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.net.URL;
023: import java.net.URLClassLoader;
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.Enumeration;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030:
031: /**
032: * A ClassLoader which sees only specified classes, and which can be
033: * set to do parent-first or child-first path lookup.
034: * <p>
035: * Note that this classloader is not "industrial strength"; users
036: * looking for such a class may wish to look at the Tomcat sourcecode
037: * instead. In particular, this class may not be threadsafe.
038: * <p>
039: * Note that the ClassLoader.getResources method isn't overloaded here.
040: * It would be nice to ensure that when child-first lookup is set the
041: * resources from the child are returned earlier in the list than the
042: * resources from the parent. However overriding this method isn't possible
043: * as the java 1.4 version of ClassLoader declares this method final
044: * (though the java 1.5 version has removed the final qualifier). As the
045: * ClassLoader javadoc doesn't specify the order in which resources
046: * are returned, it's valid to return the resources in any order (just
047: * untidy) so the inherited implementation is technically ok.
048: */
049:
050: public class PathableClassLoader extends URLClassLoader {
051:
052: private static final URL[] NO_URLS = new URL[0];
053:
054: /**
055: * A map of package-prefix to ClassLoader. Any class which is in
056: * this map is looked up via the specified classloader instead of
057: * the classpath associated with this classloader or its parents.
058: * <p>
059: * This is necessary in order for the rest of the world to communicate
060: * with classes loaded via a custom classloader. As an example, junit
061: * testcases which are loaded via a custom classloader needs to see
062: * the same junit classes as the code invoking the testcase, otherwise
063: * they can't pass result objects back.
064: * <p>
065: * Normally, only a classloader created with a null parent needs to
066: * have any lookasides defined.
067: */
068: private HashMap lookasides = null;
069:
070: /**
071: * See setParentFirst.
072: */
073: private boolean parentFirst = true;
074:
075: /**
076: * Constructor.
077: */
078: public PathableClassLoader(ClassLoader parent) {
079: super (NO_URLS, parent);
080: }
081:
082: /**
083: * Allow caller to explicitly add paths. Generally this not a good idea;
084: * use addLogicalLib instead, then define the location for that logical
085: * library in the build.xml file.
086: */
087: public void addURL(URL url) {
088: super .addURL(url);
089: }
090:
091: /**
092: * Specify whether this classloader should ask the parent classloader
093: * to resolve a class first, before trying to resolve it via its own
094: * classpath.
095: * <p>
096: * Checking with the parent first is the normal approach for java, but
097: * components within containers such as servlet engines can use
098: * child-first lookup instead, to allow the components to override libs
099: * 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("Logical lib [" + logicalLib
162: + "] is not defined" + " as a System property.");
163: }
164:
165: try {
166: URL url = new File(filename).toURL();
167: addURL(url);
168: } catch (java.net.MalformedURLException e) {
169: throw new UnknownError("Invalid file [" + filename
170: + "] for logical lib [" + logicalLib + "]");
171: }
172: }
173:
174: /**
175: * Override ClassLoader method.
176: * <p>
177: * For each explicitly mapped package prefix, if the name matches the
178: * prefix associated with that entry then attempt to load the class via
179: * that entries' classloader.
180: */
181: protected Class loadClass(String name, boolean resolve)
182: throws ClassNotFoundException {
183: // just for performance, check java and javax
184: if (name.startsWith("java.") || name.startsWith("javax.")) {
185: return super .loadClass(name, resolve);
186: }
187:
188: if (lookasides != null) {
189: for (Iterator i = lookasides.entrySet().iterator(); i
190: .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: // Implement child-first.
205: //
206: // It appears that the findClass method doesn't check whether the
207: // class has already been loaded. This seems odd to me, but without
208: // first checking via findLoadedClass we can get java.lang.LinkageError
209: // with message "duplicate class definition" which isn't good.
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)
252: throws IOException {
253: if (parentFirst) {
254: return super .getResources(name);
255: } else {
256: Enumeration localUrls = super .findResources(name);
257:
258: ClassLoader parent = getParent();
259: if (parent == null) {
260: // Alas, there is no method to get matching resources
261: // from a null (BOOT) parent classloader. Calling
262: // ClassLoader.getSystemClassLoader isn't right. Maybe
263: // calling Class.class.getResources(name) would do?
264: //
265: // However for the purposes of unit tests, we can
266: // simply assume that no relevant resources are
267: // loadable from the parent; unit tests will never be
268: // putting any of their resources in a "boot" classloader
269: // path!
270: return localUrls;
271: }
272: Enumeration parentUrls = parent.getResources(name);
273:
274: ArrayList localItems = toList(localUrls);
275: ArrayList parentItems = toList(parentUrls);
276: localItems.addAll(parentItems);
277: return Collections.enumeration(localItems);
278: }
279: }
280:
281: /**
282: *
283: * Clean implementation of list function of
284: * {@link java.utils.Collection} added in JDK 1.4
285: * @param en <code>Enumeration</code>, possibly null
286: * @return <code>ArrayList</code> containing the enumerated
287: * elements in the enumerated order, not null
288: */
289: private ArrayList toList(Enumeration en) {
290: ArrayList results = new ArrayList();
291: if (en != null) {
292: while (en.hasMoreElements()) {
293: Object element = en.nextElement();
294: results.add(element);
295: }
296: }
297: return results;
298: }
299:
300: /**
301: * Same as parent class method except that when parentFirst is false
302: * the resource is looked for in the local classpath before the parent
303: * loader is consulted.
304: */
305: public InputStream getResourceAsStream(String name) {
306: if (parentFirst) {
307: return super .getResourceAsStream(name);
308: } else {
309: URL local = super .findResource(name);
310: if (local != null) {
311: try {
312: return local.openStream();
313: } catch (IOException e) {
314: // TODO: check if this is right or whether we should
315: // fall back to trying parent. The javadoc doesn't say...
316: return null;
317: }
318: }
319: return super.getResourceAsStream(name);
320: }
321: }
322: }
|