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