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 }