1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 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.Map; 29 30 import junit.framework.Assert; 31 32 /** 33 * A ClassLoader which sees only specified classes, and which can be 34 * set to do parent-first or child-first path lookup. 35 * <p> 36 * Note that this class loader is not "industrial strength"; users 37 * looking for such a class may wish to look at the Tomcat sourcecode 38 * instead. In particular, this class may not be threadsafe. 39 * <p> 40 * Note that the ClassLoader.getResources method isn't overloaded here. 41 * It would be nice to ensure that when child-first lookup is set the 42 * resources from the child are returned earlier in the list than the 43 * resources from the parent. However overriding this method isn't possible 44 * as the Java 1.4 version of ClassLoader declares this method final 45 * (though the Java 1.5 version has removed the final qualifier). As the 46 * ClassLoader Javadoc doesn't specify the order in which resources 47 * are returned, it's valid to return the resources in any order (just 48 * untidy) so the inherited implementation is technically ok. 49 */ 50 51 public class PathableClassLoader extends URLClassLoader { 52 53 private static final URL[] NO_URLS = {}; 54 55 /** 56 * A map of package-prefix to ClassLoader. Any class which is in 57 * this map is looked up via the specified class loader instead of 58 * the classpath associated with this class loader or its parents. 59 * <p> 60 * This is necessary in order for the rest of the world to communicate 61 * with classes loaded via a custom class loader. As an example, junit 62 * tests which are loaded via a custom class loader needs to see 63 * the same junit classes as the code invoking the test, otherwise 64 * they can't pass result objects back. 65 * <p> 66 * Normally, only a class loader created with a null parent needs to 67 * have any lookasides defined. 68 */ 69 private HashMap lookasides; 70 71 /** 72 * See setParentFirst. 73 */ 74 private boolean parentFirst = true; 75 76 /** 77 * Constructs a new instance. 78 * <p> 79 * Often, null is passed as the parent, that is, the parent of the new 80 * instance is the bootloader. This ensures that the classpath is 81 * totally clean; nothing but the standard Java library will be 82 * present. 83 * <p> 84 * When using a null parent class loader with a junit test, it *is* 85 * necessary for the junit library to also be visible. In this case, it 86 * is recommended that the following code be used: 87 * <pre> 88 * pathableLoader.useExplicitLoader( 89 * "junit.", 90 * junit.framework.Test.class.getClassLoader()); 91 * </pre> 92 * Note that this works regardless of whether junit is on the system 93 * classpath, or whether it has been loaded by some test framework that 94 * creates its own class loader to run unit tests in (eg maven2's 95 * Surefire plugin). 96 */ 97 public PathableClassLoader(final ClassLoader parent) { 98 super(NO_URLS, parent); 99 } 100 101 /** 102 * Specify a logical library to be included in the classpath used to 103 * locate classes. 104 * <p> 105 * The specified lib name is used as a key into the system properties; 106 * there is expected to be a system property defined with that name 107 * whose value is a url that indicates where that logical library can 108 * be found. Typically this is the name of a jar file, or a directory 109 * containing class files. 110 * <p> 111 * If there is no system property, but the class loader that loaded 112 * this class is a URLClassLoader then the set of URLs that the 113 * class loader uses for its classpath is scanned; any jar in the 114 * URL set whose name starts with the specified string is added to 115 * the classpath managed by this instance. 116 * <p> 117 * Using logical library names allows the calling code to specify its 118 * desired classpath without knowing the exact location of the necessary 119 * classes. 120 */ 121 public void addLogicalLib(final String logicalLib) { 122 // first, check the system properties 123 final String fileName = System.getProperty(logicalLib); 124 if (fileName != null) { 125 try { 126 final File file = new File(fileName); 127 if (!file.exists()) { 128 Assert.fail("Unable to add logical library " + fileName); 129 } 130 final URL libUrl = file.toURL(); 131 addURL(libUrl); 132 return; 133 } catch (final java.net.MalformedURLException e) { 134 throw new UnknownError( 135 "Invalid file [" + fileName + "] for logical lib [" + logicalLib + "]"); 136 } 137 } 138 139 // now check the classpath for a similar-named lib 140 final URL libUrl = libFromClasspath(logicalLib); 141 if (libUrl != null) { 142 addURL(libUrl); 143 return; 144 } 145 146 // lib not found 147 throw new UnknownError( 148 "Logical lib [" + logicalLib + "] is not defined" 149 + " as a System property."); 150 } 151 152 /** 153 * Specify a collection of logical libraries. See addLogicalLib. 154 */ 155 public void addLogicalLib(final String[] logicalLibs) { 156 for (final String logicalLib : logicalLibs) { 157 addLogicalLib(logicalLib); 158 } 159 } 160 161 /** 162 * Allow caller to explicitly add paths. Generally this not a good idea; 163 * use addLogicalLib instead, then define the location for that logical 164 * library in the build.xml file. 165 */ 166 @Override 167 public void addURL(final URL url) { 168 super.addURL(url); 169 } 170 171 /** 172 * Same as parent class method except that when parentFirst is false 173 * the resource is looked for in the local classpath before the parent 174 * loader is consulted. 175 */ 176 @Override 177 public URL getResource(final String name) { 178 if (parentFirst) { 179 return super.getResource(name); 180 } 181 final URL local = super.findResource(name); 182 if (local != null) { 183 return local; 184 } 185 return super.getResource(name); 186 } 187 188 /** 189 * Same as parent class method except that when parentFirst is false 190 * the resource is looked for in the local classpath before the parent 191 * loader is consulted. 192 */ 193 @Override 194 public InputStream getResourceAsStream(final String name) { 195 if (parentFirst) { 196 return super.getResourceAsStream(name); 197 } 198 final URL local = super.findResource(name); 199 if (local != null) { 200 try { 201 return local.openStream(); 202 } catch (final IOException e) { 203 // TODO: check if this is right or whether we should 204 // fall back to trying parent. The Javadoc doesn't say... 205 return null; 206 } 207 } 208 return super.getResourceAsStream(name); 209 } 210 211 /** 212 * Emulate a proper implementation of getResources which respects the 213 * setting for parentFirst. 214 * <p> 215 * Note that it's not possible to override the inherited getResources, as 216 * it's declared final in java1.4 (thought that's been removed for 1.5). 217 * The inherited implementation always behaves as if parentFirst=true. 218 */ 219 public Enumeration getResourcesInOrder(final String name) throws IOException { 220 if (parentFirst) { 221 return super.getResources(name); 222 } 223 final Enumeration localUrls = super.findResources(name); 224 225 final ClassLoader parent = getParent(); 226 if (parent == null) { 227 // Alas, there is no method to get matching resources 228 // from a null (BOOT) parent class loader. Calling 229 // ClassLoader.getSystemClassLoader isn't right. Maybe 230 // calling Class.class.getResources(name) would do? 231 // 232 // However for the purposes of unit tests, we can 233 // simply assume that no relevant resources are 234 // loadable from the parent; unit tests will never be 235 // putting any of their resources in a "boot" class loader 236 // path! 237 return localUrls; 238 } 239 final Enumeration parentUrls = parent.getResources(name); 240 241 final ArrayList localItems = toList(localUrls); 242 final ArrayList parentItems = toList(parentUrls); 243 localItems.addAll(parentItems); 244 return Collections.enumeration(localItems); 245 } 246 247 /** 248 * If the class loader that loaded this class has this logical lib in its 249 * path, then return the matching URL otherwise return null. 250 * <p> 251 * This only works when the class loader loading this class is an instance 252 * of URLClassLoader and thus has a getURLs method that returns the classpath 253 * it uses when loading classes. However in practice, the vast majority of the 254 * time this type is the class loader used. 255 * <p> 256 * The classpath of the class loader for this instance is scanned, and any 257 * jarfile in the path whose name starts with the logicalLib string is 258 * considered a match. For example, passing "foo" will match a url 259 * of {@code file:///some/where/foo-2.7.jar}. 260 * <p> 261 * When multiple classpath entries match the specified logicalLib string, 262 * the one with the shortest file name component is returned. This means that 263 * if "foo-1.1.jar" and "foobar-1.1.jar" are in the path, then a logicalLib 264 * name of "foo" will match the first entry above. 265 */ 266 private URL libFromClasspath(final String logicalLib) { 267 final ClassLoader cl = this.getClass().getClassLoader(); 268 if (!(cl instanceof URLClassLoader)) { 269 return null; 270 } 271 272 final URLClassLoader ucl = (URLClassLoader) cl; 273 final URL[] path = ucl.getURLs(); 274 URL shortestMatch = null; 275 int shortestMatchLen = Integer.MAX_VALUE; 276 for (final URL u : path) { 277 // extract the file name bit on the end of the URL 278 String fileName = u.toString(); 279 if (!fileName.endsWith(".jar")) { 280 // not a jarfile, ignore it 281 continue; 282 } 283 284 final int lastSlash = fileName.lastIndexOf('/'); 285 if (lastSlash >= 0) { 286 fileName = fileName.substring(lastSlash+1); 287 } 288 289 // ok, this is a candidate 290 if (fileName.startsWith(logicalLib) && fileName.length() < shortestMatchLen) { 291 shortestMatch = u; 292 shortestMatchLen = fileName.length(); 293 } 294 } 295 296 return shortestMatch; 297 } 298 299 /** 300 * Override ClassLoader method. 301 * <p> 302 * For each explicitly mapped package prefix, if the name matches the 303 * prefix associated with that entry then attempt to load the class via 304 * that entries' class loader. 305 */ 306 @Override 307 protected Class loadClass(final String name, final boolean resolve) 308 throws ClassNotFoundException { 309 // just for performance, check java and javax 310 if (name.startsWith("java.") || name.startsWith("javax.")) { 311 return super.loadClass(name, resolve); 312 } 313 314 if (lookasides != null) { 315 for (final Object element : lookasides.entrySet()) { 316 final Map.Entry entry = (Map.Entry) element; 317 final String prefix = (String) entry.getKey(); 318 if (name.startsWith(prefix)) { 319 final ClassLoader loader = (ClassLoader) entry.getValue(); 320 return Class.forName(name, resolve, loader); 321 } 322 } 323 } 324 325 if (parentFirst) { 326 return super.loadClass(name, resolve); 327 } 328 try { 329 Class clazz = findLoadedClass(name); 330 if (clazz == null) { 331 clazz = super.findClass(name); 332 } 333 if (resolve) { 334 resolveClass(clazz); 335 } 336 return clazz; 337 } catch (final ClassNotFoundException e) { 338 return super.loadClass(name, resolve); 339 } 340 } 341 342 /** 343 * Specify whether this class loader should ask the parent class loader 344 * to resolve a class first, before trying to resolve it via its own 345 * classpath. 346 * <p> 347 * Checking with the parent first is the normal approach for java, but 348 * components within containers such as servlet engines can use 349 * child-first lookup instead, to allow the components to override libs 350 * which are visible in shared class loaders provided by the container. 351 * <p> 352 * Note that the method getResources always behaves as if parentFirst=true, 353 * because of limitations in Java 1.4; see the Javadoc for method 354 * getResourcesInOrder for details. 355 * <p> 356 * This value defaults to true. 357 */ 358 public void setParentFirst(final boolean state) { 359 parentFirst = state; 360 } 361 362 /** 363 * 364 * Clean implementation of list function of 365 * {@link java.util.Collection} added in JDK 1.4 366 * @param en {@code Enumeration}, possibly null 367 * @return {@code ArrayList} containing the enumerated 368 * elements in the enumerated order, not null 369 */ 370 private ArrayList toList(final Enumeration en) { 371 final ArrayList results = new ArrayList(); 372 if (en != null) { 373 while (en.hasMoreElements()) { 374 final Object element = en.nextElement(); 375 results.add(element); 376 } 377 } 378 return results; 379 } 380 381 /** 382 * Specify a class loader to use for specific Java packages. 383 * <p> 384 * The specified class loader is normally a loader that is NOT 385 * an ancestor of this class loader. In particular, this loader 386 * may have the bootloader as its parent, but be configured to 387 * see specific other classes (eg the junit library loaded 388 * via the system class loader). 389 * <p> 390 * The differences between using this method, and using 391 * addLogicalLib are: 392 * <ul> 393 * <li>If code calls getClassLoader on a class loaded via 394 * "lookaside", then traces up its inheritance chain, it 395 * will see the "real" class loaders. When the class is remapped 396 * into this class loader via addLogicalLib, the class loader 397 * chain seen is this object plus ancestors. 398 * <li>If two different jars contain classes in the same 399 * package, then it is not possible to load both jars into 400 * the same "lookaside" class loader (eg the system class loader) 401 * then map one of those subsets from here. Of course they could 402 * be loaded into two different "lookaside" class loaders and 403 * then a prefix used to map from here to one of those class loaders. 404 * </ul> 405 */ 406 public void useExplicitLoader(final String prefix, final ClassLoader loader) { 407 if (lookasides == null) { 408 lookasides = new HashMap(); 409 } 410 lookasides.put(prefix, loader); 411 } 412 413 /** 414 * For classes with the specified prefix, get them from the system 415 * classpath <i>which is active at the point this method is called</i>. 416 * <p> 417 * This method is just a shortcut for 418 * <pre> 419 * useExplicitLoader(prefix, ClassLoader.getSystemClassLoader()); 420 * </pre> 421 * <p> 422 * Of course, this assumes that the classes of interest are already 423 * in the classpath of the system class loader. 424 */ 425 public void useSystemLoader(final String prefix) { 426 useExplicitLoader(prefix, ClassLoader.getSystemClassLoader()); 427 428 } 429 }