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 }