001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.kernel.config; 018 019 import java.beans.Introspector; 020 import java.io.IOException; 021 import java.io.ObjectInputStream; 022 import java.io.ObjectOutputStream; 023 import java.io.ObjectStreamClass; 024 import java.lang.reflect.Field; 025 import java.net.URL; 026 import java.net.URLClassLoader; 027 import java.net.URLStreamHandlerFactory; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.Collections; 031 import java.util.Enumeration; 032 import java.util.HashSet; 033 import java.util.LinkedList; 034 import java.util.List; 035 import java.util.Map; 036 import java.util.Set; 037 038 import org.apache.commons.logging.Log; 039 import org.apache.commons.logging.LogFactory; 040 import org.apache.geronimo.kernel.classloader.UnionEnumeration; 041 import org.apache.geronimo.kernel.repository.Artifact; 042 import org.apache.geronimo.kernel.util.ClassLoaderRegistry; 043 044 /** 045 * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class 046 * loader model to support a list of parent class loaders. Each operation that accesses a parent, has been replaced 047 * with a operation that checks each parent in order. This getParent method of this class will always return null, 048 * which may be interpreted by the calling code to mean that this class loader is a direct child of the system class 049 * loader. 050 * 051 * @version $Rev: 562112 $ $Date: 2007-08-02 10:05:47 -0400 (Thu, 02 Aug 2007) $ 052 */ 053 public class MultiParentClassLoader extends URLClassLoader { 054 private static final Log log = LogFactory.getLog(MultiParentClassLoader.class); 055 private final Artifact id; 056 private final ClassLoader[] parents; 057 private final boolean inverseClassLoading; 058 private final String[] hiddenClasses; 059 private final String[] nonOverridableClasses; 060 private final String[] hiddenResources; 061 private final String[] nonOverridableResources; 062 private boolean destroyed = false; 063 064 // I used this pattern as its temporary and with the static final we get compile time 065 // optimizations. 066 private final static int classLoaderSearchMode; 067 private final static int ORIGINAL_SEARCH = 1; 068 private final static int OPTIMIZED_SEARCH = 2; 069 070 static { 071 // Extract the classLoaderSearchMode if specified. If not, default to "safe". 072 String mode = System.getProperty("Xorg.apache.geronimo.kernel.config.MPCLSearchOption"); 073 int runtimeMode = OPTIMIZED_SEARCH; // Default to optimized 074 String runtimeModeMessage = "Original Classloading"; 075 if (mode != null) { 076 if (mode.equals("safe")) { 077 runtimeMode = ORIGINAL_SEARCH; 078 runtimeModeMessage = "Safe ClassLoading"; 079 } else if (mode.equals("optimized")) 080 runtimeMode = OPTIMIZED_SEARCH; 081 } 082 083 classLoaderSearchMode = runtimeMode; 084 log.info("ClassLoading behaviour has changed. The "+runtimeModeMessage+" mode is in effect. If you are experiencing a problem\n"+ 085 "you can change the behaviour by specifying -DXorg.apache.geronimo.kernel.config.MPCLSearchOption= property. Specify \n"+ 086 "=\"safe\" to revert to the original behaviour. This is a temporary change until we decide whether or not to make it\n"+ 087 "permanent for the 2.0 release"); 088 } 089 090 /** 091 * Creates a named class loader with no parents. 092 * 093 * @param id the id of this class loader 094 * @param urls the urls from which this class loader will classes and resources 095 */ 096 public MultiParentClassLoader(Artifact id, URL[] urls) { 097 super(urls); 098 this.id = id; 099 parents = new ClassLoader[]{ClassLoader.getSystemClassLoader()}; 100 inverseClassLoading = false; 101 hiddenClasses = new String[0]; 102 nonOverridableClasses = new String[0]; 103 hiddenResources = new String[0]; 104 nonOverridableResources = new String[0]; 105 ClassLoaderRegistry.add(this); 106 } 107 108 109 /** 110 * Creates a named class loader as a child of the specified parent. 111 * 112 * @param id the id of this class loader 113 * @param urls the urls from which this class loader will classes and resources 114 * @param parent the parent of this class loader 115 */ 116 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent) { 117 this(id, urls, new ClassLoader[]{parent}); 118 } 119 120 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { 121 this(id, urls, new ClassLoader[]{parent}, inverseClassLoading, hiddenClasses, nonOverridableClasses); 122 } 123 124 /** 125 * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory 126 * for accessing the urls.. 127 * 128 * @param id the id of this class loader 129 * @param urls the urls from which this class loader will classes and resources 130 * @param parent the parent of this class loader 131 * @param factory the URLStreamHandlerFactory used to access the urls 132 */ 133 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { 134 this(id, urls, new ClassLoader[]{parent}, factory); 135 } 136 137 /** 138 * Creates a named class loader as a child of the specified parents. 139 * 140 * @param id the id of this class loader 141 * @param urls the urls from which this class loader will classes and resources 142 * @param parents the parents of this class loader 143 */ 144 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents) { 145 super(urls); 146 this.id = id; 147 this.parents = copyParents(parents); 148 inverseClassLoading = false; 149 hiddenClasses = new String[0]; 150 nonOverridableClasses = new String[0]; 151 hiddenResources = new String[0]; 152 nonOverridableResources = new String[0]; 153 ClassLoaderRegistry.add(this); 154 } 155 156 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, Collection hiddenClasses, Collection nonOverridableClasses) { 157 this(id, urls, parents, inverseClassLoading, (String[]) hiddenClasses.toArray(new String[hiddenClasses.size()]), (String[]) nonOverridableClasses.toArray(new String[nonOverridableClasses.size()])); 158 } 159 160 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { 161 super(urls); 162 this.id = id; 163 this.parents = copyParents(parents); 164 this.inverseClassLoading = inverseClassLoading; 165 this.hiddenClasses = hiddenClasses; 166 this.nonOverridableClasses = nonOverridableClasses; 167 hiddenResources = toResources(hiddenClasses); 168 nonOverridableResources = toResources(nonOverridableClasses); 169 ClassLoaderRegistry.add(this); 170 } 171 172 public MultiParentClassLoader(MultiParentClassLoader source) { 173 this(source.id, source.getURLs(), deepCopyParents(source.parents), source.inverseClassLoading, source.hiddenClasses, source.nonOverridableClasses); 174 } 175 176 static ClassLoader copy(ClassLoader source) { 177 if (source instanceof MultiParentClassLoader) { 178 return new MultiParentClassLoader((MultiParentClassLoader) source); 179 } else if (source instanceof URLClassLoader) { 180 return new URLClassLoader(((URLClassLoader) source).getURLs(), source.getParent()); 181 } else { 182 return new URLClassLoader(new URL[0], source); 183 } 184 } 185 186 ClassLoader copy() { 187 return MultiParentClassLoader.copy(this); 188 } 189 190 private String[] toResources(String[] classes) { 191 String[] resources = new String[classes.length]; 192 for (int i = 0; i < classes.length; i++) { 193 String className = classes[i]; 194 resources[i] = className.replace('.', '/'); 195 } 196 return resources; 197 } 198 199 /** 200 * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory 201 * for accessing the urls.. 202 * 203 * @param id the id of this class loader 204 * @param urls the urls from which this class loader will classes and resources 205 * @param parents the parents of this class loader 206 * @param factory the URLStreamHandlerFactory used to access the urls 207 */ 208 public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) { 209 super(urls, null, factory); 210 this.id = id; 211 this.parents = copyParents(parents); 212 inverseClassLoading = false; 213 hiddenClasses = new String[0]; 214 nonOverridableClasses = new String[0]; 215 hiddenResources = new String[0]; 216 nonOverridableResources = new String[0]; 217 ClassLoaderRegistry.add(this); 218 } 219 220 private static ClassLoader[] copyParents(ClassLoader[] parents) { 221 ClassLoader[] newParentsArray = new ClassLoader[parents.length]; 222 for (int i = 0; i < parents.length; i++) { 223 ClassLoader parent = parents[i]; 224 if (parent == null) { 225 throw new NullPointerException("parent[" + i + "] is null"); 226 } 227 newParentsArray[i] = parent; 228 } 229 return newParentsArray; 230 } 231 232 private static ClassLoader[] deepCopyParents(ClassLoader[] parents) { 233 ClassLoader[] newParentsArray = new ClassLoader[parents.length]; 234 for (int i = 0; i < parents.length; i++) { 235 ClassLoader parent = parents[i]; 236 if (parent == null) { 237 throw new NullPointerException("parent[" + i + "] is null"); 238 } 239 if (parent instanceof MultiParentClassLoader) { 240 parent = ((MultiParentClassLoader) parent).copy(); 241 } 242 newParentsArray[i] = parent; 243 } 244 return newParentsArray; 245 } 246 247 /** 248 * Gets the id of this class loader. 249 * 250 * @return the id of this class loader 251 */ 252 public Artifact getId() { 253 return id; 254 } 255 256 /** 257 * Gets the parents of this class loader. 258 * 259 * @return the parents of this class loader 260 */ 261 public ClassLoader[] getParents() { 262 return parents; 263 } 264 265 public void addURL(URL url) { 266 // todo this needs a security check 267 super.addURL(url); 268 } 269 270 /** 271 * TODO This method should be removed and replaced with the best classLoading option. Its intent is to 272 * provide a way for folks to switch back to the old classLoader if this fix breaks something. 273 */ 274 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 275 if (classLoaderSearchMode == ORIGINAL_SEARCH) 276 return loadSafeClass(name, resolve); 277 else 278 return loadOptimizedClass(name, resolve); 279 } 280 281 /** 282 * This method executes the old class loading behaviour before optimization. 283 * 284 * @param name 285 * @param resolve 286 * @return 287 * @throws ClassNotFoundException 288 */ 289 protected synchronized Class<?> loadSafeClass(String name, boolean resolve) throws ClassNotFoundException { 290 // 291 // Check if class is in the loaded classes cache 292 // 293 Class cachedClass = findLoadedClass(name); 294 if (cachedClass != null) { 295 return resolveClass(cachedClass, resolve); 296 } 297 298 // This is a reasonable hack. We can add some classes to the list below. 299 // Since we know these classes are in the system class loader let's not waste our 300 // time going through the hierarchy. 301 // 302 // The order is based on profiling the server. It may not be optimal for all 303 // workloads. 304 305 if (name.startsWith("java.") || 306 name.equals("boolean") || 307 name.equals("int") || 308 name.equals("double") || 309 name.equals("long")) { 310 Class clazz = ClassLoader.getSystemClassLoader().loadClass(name); 311 return resolveClass(clazz, resolve); 312 } 313 314 // 315 // if we are using inverse class loading, check local urls first 316 // 317 if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) { 318 try { 319 Class clazz = findClass(name); 320 return resolveClass(clazz, resolve); 321 } catch (ClassNotFoundException ignored) { 322 } 323 } 324 325 // 326 // Check parent class loaders 327 // 328 if (!isHiddenClass(name)) { 329 for (ClassLoader parent : parents) { 330 try { 331 Class clazz = parent.loadClass(name); 332 return resolveClass(clazz, resolve); 333 } catch (ClassNotFoundException ignored) { 334 // this parent didn't have the class; try the next one 335 } 336 } 337 } 338 339 // 340 // if we are not using inverse class loading, check local urls now 341 // 342 // don't worry about excluding non-overridable classes here... we 343 // have alredy checked he parent and the parent didn't have the 344 // class, so we can override now 345 if (!isDestroyed()) { 346 try { 347 Class clazz = findClass(name); 348 return resolveClass(clazz, resolve); 349 } catch (ClassNotFoundException ignored) { 350 } 351 } 352 353 throw new ClassNotFoundException(name + " in classloader " + id); 354 } 355 356 /** 357 * 358 * Optimized classloading. 359 * 360 * This method is the normal way to resolve class loads. This method recursively calls its parents to resolve 361 * classloading requests. Here is the sequence of operations: 362 * 363 * 1. Call findClass to see if we already have this class loaded. 364 * 2. If not, call the SystemClassLoader which needs to be called anyway. 365 * 3. Check if inverse loading, if so look in our class loader. 366 * 4. Search our parents, recursively. Keeping track of which parents have already been called. 367 * Since MultiParentClassLoaders can appear more than once we do not search an already searched classloader. 368 * 5. Search our classloader. 369 * 370 */ 371 protected synchronized Class<?> loadOptimizedClass(String name, boolean resolve) throws ClassNotFoundException { 372 // System.err.println("Started load for class "+name+" in classloader "+this); 373 // 374 // Check if class is in the loaded classes cache 375 // 376 Class cachedClass = findLoadedClass(name); 377 if (cachedClass != null) { 378 return resolveClass(cachedClass, resolve); 379 } 380 381 // 382 // No dice, let's offer the primordial loader a shot... 383 // 384 try { 385 return resolveClass(findSystemClass(name), resolve); 386 } catch (ClassNotFoundException cnfe) { 387 // ignore...just being a good citizen. 388 } 389 390 // 391 // if we are using inverse class loading, check local urls first 392 // 393 if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) { 394 try { 395 Class clazz = findClass(name); 396 return resolveClass(clazz, resolve); 397 } catch (ClassNotFoundException ignored) { 398 } 399 } 400 401 // 402 // Check parent class loaders 403 // 404 if (!isHiddenClass(name)) { 405 try { 406 LinkedList<ClassLoader> visitedClassLoaders = new LinkedList<ClassLoader>(); 407 Class clazz = checkParents(name, resolve, visitedClassLoaders); 408 if (clazz != null) return resolveClass(clazz, resolve); 409 } catch (ClassNotFoundException cnfe) { 410 // ignore 411 } 412 } 413 414 // 415 // if we are not using inverse class loading, check local urls now 416 // 417 // don't worry about excluding non-overridable classes here... we 418 // have alredy checked he parent and the parent didn't have the 419 // class, so we can override now 420 if (!isDestroyed()) { 421 try { 422 Class clazz = findClass(name); 423 return resolveClass(clazz, resolve); 424 } catch (ClassNotFoundException ignored) { 425 } 426 } 427 428 throw new ClassNotFoundException(name + " in classloader " + id); 429 } 430 431 /** 432 * This method is an internal hook that allows us to be performant on Class lookups when multiparent 433 * classloaders are involved. We can bypass certain lookups that have already occurred in the initiating 434 * classloader. Also, we track the classLoaders that are visited by adding them to an already vistied list. 435 * In this way, we can bypass redundant checks for the same class. 436 * 437 * @param name 438 * @param visitedClassLoaders 439 * @return 440 * @throws ClassNotFoundException 441 */ 442 protected synchronized Class<?> loadClassInternal(String name, boolean resolve, LinkedList<ClassLoader> visitedClassLoaders) throws ClassNotFoundException { 443 // 444 // Check if class is in the loaded classes cache 445 // 446 Class cachedClass = findLoadedClass(name); 447 if (cachedClass != null) { 448 return resolveClass(cachedClass, resolve); 449 } 450 451 // 452 // Check parent class loaders 453 // 454 if (!isHiddenClass(name)) { 455 try { 456 Class clazz = checkParents(name, resolve, visitedClassLoaders); 457 if (clazz != null) return resolveClass(clazz,resolve); 458 } catch (ClassNotFoundException cnfe) { 459 // ignore 460 } 461 } 462 463 // 464 // if we are not using inverse class loading, check local urls now 465 // 466 // don't worry about excluding non-overridable classes here... we 467 // have alredy checked he parent and the parent didn't have the 468 // class, so we can override now 469 if (!isDestroyed()) { 470 Class clazz = findClass(name); 471 return resolveClass(clazz, resolve); 472 } 473 474 return null; // Caler is expecting a class. Null indicates CNFE and will save some time. 475 } 476 477 /** 478 * In order to optimize the classLoading process and visit a directed set of 479 * classloaders this internal method for Geronimo MultiParentClassLoaders 480 * is used. Effectively, as each classloader is visited it is passed a linked 481 * list of classloaders that have already been visited and can safely be skipped. 482 * This method assumes the context of an MPCL and is not for use external to this class. 483 * 484 * @param name 485 * @param visitedClassLoaders 486 * @return 487 * @throws ClassNotFoundException 488 */ 489 private synchronized Class<?> checkParents(String name, boolean resolve, LinkedList<ClassLoader> visitedClassLoaders) throws ClassNotFoundException { 490 for (ClassLoader parent : parents) { 491 // When we've encountered the primordial loader we are done. Since we've already looked there we do not need 492 // to repeat the check. Simply return null and all things will be handled nicely. 493 if (parent == ClassLoader.getSystemClassLoader()) return null; 494 if (!visitedClassLoaders.contains(parent)) { 495 visitedClassLoaders.add(parent); // Track that we've been here before 496 try { 497 if (parent instanceof MultiParentClassLoader) { 498 Class clazz = ((MultiParentClassLoader) parent).loadClassInternal(name, resolve, visitedClassLoaders); 499 if (clazz != null) return resolveClass(clazz, resolve); 500 } else { 501 return parent.loadClass(name); 502 } 503 } catch (ClassNotFoundException cnfe) { 504 // ignore 505 } 506 } 507 } 508 // To avoid yet another CNFE we'll simply return null and let the caller handle appropriately. 509 return null; 510 } 511 512 private boolean isNonOverridableClass(String name) { 513 for (String nonOverridableClass : nonOverridableClasses) { 514 if (name.startsWith(nonOverridableClass)) { 515 return true; 516 } 517 } 518 return false; 519 } 520 521 private boolean isHiddenClass(String name) { 522 for (String hiddenClass : hiddenClasses) { 523 if (name.startsWith(hiddenClass)) { 524 return true; 525 } 526 } 527 return false; 528 } 529 530 private Class resolveClass(Class clazz, boolean resolve) { 531 if (resolve) { 532 resolveClass(clazz); 533 } 534 return clazz; 535 } 536 537 public URL getResource(String name) { 538 if (isDestroyed()) { 539 return null; 540 } 541 542 // 543 // if we are using inverse class loading, check local urls first 544 // 545 if (inverseClassLoading && !isDestroyed() && !isNonOverridableResource(name)) { 546 URL url = findResource(name); 547 if (url != null) { 548 return url; 549 } 550 } 551 552 // 553 // Check parent class loaders 554 // 555 if (!isHiddenResource(name)) { 556 for (ClassLoader parent : parents) { 557 URL url = parent.getResource(name); 558 if (url != null) { 559 return url; 560 } 561 } 562 } 563 564 // 565 // if we are not using inverse class loading, check local urls now 566 // 567 // don't worry about excluding non-overridable resources here... we 568 // have alredy checked he parent and the parent didn't have the 569 // resource, so we can override now 570 if (!isDestroyed()) { 571 // parents didn't have the resource; attempt to load it from my urls 572 return findResource(name); 573 } 574 575 return null; 576 } 577 578 public Enumeration<URL> findResources(String name) throws IOException { 579 if (isDestroyed()) { 580 return Collections.enumeration(Collections.EMPTY_SET); 581 } 582 583 Set<ClassLoader> knownClassloaders = new HashSet<ClassLoader>(); 584 List<Enumeration<URL>> enumerations = new ArrayList<Enumeration<URL>>(); 585 586 recursiveFind(knownClassloaders, enumerations, name); 587 588 return new UnionEnumeration<URL>(enumerations); 589 } 590 591 protected void recursiveFind(Set<ClassLoader> knownClassloaders, List<Enumeration<URL>> enumerations, String name) throws IOException { 592 if (isDestroyed() || knownClassloaders.contains(this)) { 593 return; 594 } 595 knownClassloaders.add(this); 596 if (inverseClassLoading && !isNonOverridableResource(name)) { 597 enumerations.add(internalfindResources(name)); 598 } 599 if (!isHiddenResource(name)) { 600 for (ClassLoader parent : parents) { 601 if (parent instanceof MultiParentClassLoader) { 602 ((MultiParentClassLoader) parent).recursiveFind(knownClassloaders, enumerations, name); 603 } else { 604 if (!knownClassloaders.contains(parent)) { 605 enumerations.add(parent.getResources(name)); 606 knownClassloaders.add(parent); 607 } 608 } 609 } 610 } 611 if (!inverseClassLoading) { 612 enumerations.add(internalfindResources(name)); 613 } 614 } 615 616 protected Enumeration<URL> internalfindResources(String name) throws IOException { 617 return super.findResources(name); 618 } 619 620 private boolean isNonOverridableResource(String name) { 621 for (String nonOverridableResource : nonOverridableResources) { 622 if (name.startsWith(nonOverridableResource)) { 623 return true; 624 } 625 } 626 return false; 627 } 628 629 private boolean isHiddenResource(String name) { 630 for (String hiddenResource : hiddenResources) { 631 if (name.startsWith(hiddenResource)) { 632 return true; 633 } 634 } 635 return false; 636 } 637 638 public String toString() { 639 return "[" + getClass().getName() + " id=" + id + "]"; 640 } 641 642 public synchronized boolean isDestroyed() { 643 return destroyed; 644 } 645 646 public void destroy() { 647 synchronized (this) { 648 if (destroyed) return; 649 destroyed = true; 650 } 651 652 LogFactory.release(this); 653 clearSoftCache(ObjectInputStream.class, "subclassAudits"); 654 clearSoftCache(ObjectOutputStream.class, "subclassAudits"); 655 clearSoftCache(ObjectStreamClass.class, "localDescs"); 656 clearSoftCache(ObjectStreamClass.class, "reflectors"); 657 658 // The beanInfoCache in java.beans.Introspector will hold on to Classes which 659 // it has introspected. If we don't flush the cache, we may run out of 660 // Permanent Generation space. 661 Introspector.flushCaches(); 662 663 ClassLoaderRegistry.remove(this); 664 } 665 666 private static final Object lock = new Object(); 667 private static boolean clearSoftCacheFailed = false; 668 669 private static void clearSoftCache(Class clazz, String fieldName) { 670 Map cache = null; 671 try { 672 Field f = clazz.getDeclaredField(fieldName); 673 f.setAccessible(true); 674 cache = (Map) f.get(null); 675 } catch (Throwable e) { 676 synchronized (lock) { 677 if (!clearSoftCacheFailed) { 678 clearSoftCacheFailed = true; 679 LogFactory.getLog(MultiParentClassLoader.class).debug("Unable to clear SoftCache field " + fieldName + " in class " + clazz); 680 } 681 } 682 } 683 684 if (cache != null) { 685 synchronized (cache) { 686 cache.clear(); 687 } 688 } 689 } 690 691 protected void finalize() throws Throwable { 692 ClassLoaderRegistry.remove(this); 693 super.finalize(); 694 } 695 696 }