View Javadoc

1   /**
2    *
3    * Copyright 2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.geronimo.kernel.config;
18  
19  import java.beans.Introspector;
20  import java.io.IOException;
21  import java.io.ObjectInputStream;
22  import java.io.ObjectOutputStream;
23  import java.io.ObjectStreamClass;
24  import java.lang.reflect.Field;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.net.URLStreamHandlerFactory;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.geronimo.kernel.repository.Artifact;
37  
38  /**
39   * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class
40   * loader model to support a list of parent class loaders.  Each operation that accesses a parent, has been replaced
41   * with a operation that checks each parent in order.  This getParent method of this class will always return null,
42   * which may be interpreted by the calling code to mean that this class loader is a direct child of the system class
43   * loader.
44   *
45   * @version $Rev: 471382 $ $Date: 2006-11-05 01:01:39 -0800 (Sun, 05 Nov 2006) $
46   */
47  public class MultiParentClassLoader extends URLClassLoader {
48      private final Artifact id;
49      private final ClassLoader[] parents;
50      private final boolean inverseClassLoading;
51      private final String[] hiddenClasses;
52      private final String[] nonOverridableClasses;
53      private final String[] hiddenResources;
54      private final String[] nonOverridableResources;
55      private boolean destroyed = false;
56  
57      /**
58       * Creates a named class loader with no parents.
59       *
60       * @param id   the id of this class loader
61       * @param urls the urls from which this class loader will classes and resources
62       */
63      public MultiParentClassLoader(Artifact id, URL[] urls) {
64          super(urls);
65          this.id = id;
66          parents = new ClassLoader[]{ClassLoader.getSystemClassLoader()};
67          inverseClassLoading = false;
68          hiddenClasses = new String[0];
69          nonOverridableClasses = new String[0];
70          hiddenResources = new String[0];
71          nonOverridableResources = new String[0];
72      }
73  
74  
75      /**
76       * Creates a named class loader as a child of the specified parent.
77       *
78       * @param id     the id of this class loader
79       * @param urls   the urls from which this class loader will classes and resources
80       * @param parent the parent of this class loader
81       */
82      public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent) {
83          this(id, urls, new ClassLoader[]{parent});
84      }
85  
86      public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
87          this(id, urls, new ClassLoader[]{parent}, inverseClassLoading, hiddenClasses, nonOverridableClasses);
88      }
89  
90      /**
91       * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
92       * for accessing the urls..
93       *
94       * @param id      the id of this class loader
95       * @param urls    the urls from which this class loader will classes and resources
96       * @param parent  the parent of this class loader
97       * @param factory the URLStreamHandlerFactory used to access the urls
98       */
99      public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
100         this(id, urls, new ClassLoader[]{parent}, factory);
101     }
102 
103     /**
104      * Creates a named class loader as a child of the specified parents.
105      *
106      * @param id      the id of this class loader
107      * @param urls    the urls from which this class loader will classes and resources
108      * @param parents the parents of this class loader
109      */
110     public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents) {
111         super(urls);
112         this.id = id;
113         this.parents = copyParents(parents);
114         inverseClassLoading = false;
115         hiddenClasses = new String[0];
116         nonOverridableClasses = new String[0];
117         hiddenResources = new String[0];
118         nonOverridableResources = new String[0];
119     }
120 
121     public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, Collection hiddenClasses, Collection nonOverridableClasses) {
122         this(id, urls, parents, inverseClassLoading, (String[]) hiddenClasses.toArray(new String[hiddenClasses.size()]), (String[]) nonOverridableClasses.toArray(new String[nonOverridableClasses.size()]));
123     }
124 
125     public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
126         super(urls);
127         this.id = id;
128         this.parents = copyParents(parents);
129         this.inverseClassLoading = inverseClassLoading;
130         this.hiddenClasses = hiddenClasses;
131         this.nonOverridableClasses = nonOverridableClasses;
132         hiddenResources = toResources(hiddenClasses);
133         nonOverridableResources = toResources(nonOverridableClasses);
134     }
135 
136     public MultiParentClassLoader(MultiParentClassLoader source) {
137         this(source.id, source.getURLs(), deepCopyParents(source.parents), source.inverseClassLoading, source.hiddenClasses, source.nonOverridableClasses);
138     }
139 
140     static ClassLoader copy(ClassLoader source) {
141         if (source instanceof MultiParentClassLoader) {
142             return new MultiParentClassLoader((MultiParentClassLoader) source);
143         } else if (source instanceof URLClassLoader) {
144             return new URLClassLoader(((URLClassLoader)source).getURLs(), source.getParent());
145         } else {
146             return new URLClassLoader(new URL[0], source);
147         }
148     }
149 
150     ClassLoader copy() {
151         return MultiParentClassLoader.copy(this);
152     }
153 
154     private String[] toResources(String[] classes) {
155         String[] resources = new String[classes.length];
156         for (int i = 0; i < classes.length; i++) {
157             String className = classes[i];
158             resources[i] = className.replace('.', '/');
159         }
160         return resources;
161     }
162 
163     /**
164      * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory
165      * for accessing the urls..
166      *
167      * @param id      the id of this class loader
168      * @param urls    the urls from which this class loader will classes and resources
169      * @param parents the parents of this class loader
170      * @param factory the URLStreamHandlerFactory used to access the urls
171      */
172     public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) {
173         super(urls, null, factory);
174         this.id = id;
175         this.parents = copyParents(parents);
176         inverseClassLoading = false;
177         hiddenClasses = new String[0];
178         nonOverridableClasses = new String[0];
179         hiddenResources = new String[0];
180         nonOverridableResources = new String[0];
181     }
182 
183     private static ClassLoader[] copyParents(ClassLoader[] parents) {
184         ClassLoader[] newParentsArray = new ClassLoader[parents.length];
185         for (int i = 0; i < parents.length; i++) {
186             ClassLoader parent = parents[i];
187             if (parent == null) {
188                 throw new NullPointerException("parent[" + i + "] is null");
189             }
190             newParentsArray[i] = parent;
191         }
192         return newParentsArray;
193     }
194 
195     private static ClassLoader[] deepCopyParents(ClassLoader[] parents) {
196         ClassLoader[] newParentsArray = new ClassLoader[parents.length];
197         for (int i = 0; i < parents.length; i++) {
198             ClassLoader parent = parents[i];
199             if (parent == null) {
200                 throw new NullPointerException("parent[" + i + "] is null");
201             }
202             if (parent instanceof MultiParentClassLoader) {
203                 parent = ((MultiParentClassLoader)parent).copy();
204             }
205             newParentsArray[i] = parent;
206         }
207         return newParentsArray;
208     }
209 
210     /**
211      * Gets the id of this class loader.
212      *
213      * @return the id of this class loader
214      */
215     public Artifact getId() {
216         return id;
217     }
218 
219     /**
220      * Gets the parents of this class loader.
221      *
222      * @return the parents of this class loader
223      */
224     public ClassLoader[] getParents() {
225         return parents;
226     }
227 
228     public void addURL(URL url) {
229         // todo this needs a security check
230         super.addURL(url);
231     }
232 
233     protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
234         //
235         // Check if class is in the loaded classes cache
236         //
237         Class cachedClass = findLoadedClass(name);
238         if (cachedClass != null) {
239             return resolveClass(cachedClass, resolve);
240         }
241 
242         //
243         // if we are using inverse class loading, check local urls first
244         //
245         if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) {
246             try {
247                 Class clazz = findClass(name);
248                 return resolveClass(clazz, resolve);
249             } catch (ClassNotFoundException ignored) {
250             }
251         }
252 
253         //
254         // Check parent class loaders
255         //
256         if (!isHiddenClass(name)) {
257             for (int i = 0; i < parents.length; i++) {
258                 ClassLoader parent = parents[i];
259                 try {
260                     Class clazz = parent.loadClass(name);
261                     return resolveClass(clazz, resolve);
262                 } catch (ClassNotFoundException ignored) {
263                     // this parent didn't have the class; try the next one
264                 }
265             }
266         }
267 
268         //
269         // if we are not using inverse class loading, check local urls now
270         //
271         // don't worry about excluding non-overridable classes here... we
272         // have alredy checked he parent and the parent didn't have the
273         // class, so we can override now
274         if (!isDestroyed()) {
275             try {
276                 Class clazz = findClass(name);
277                 return resolveClass(clazz, resolve);
278             } catch (ClassNotFoundException ignored) {
279             }
280         }
281 
282         throw new ClassNotFoundException(name + " in classloader " + id);
283     }
284 
285     private boolean isNonOverridableClass(String name) {
286         for (int i = 0; i < nonOverridableClasses.length; i++) {
287             if (name.startsWith(nonOverridableClasses[i])) {
288                 return true;
289             }
290         }
291         return false;
292     }
293 
294     private boolean isHiddenClass(String name) {
295         for (int i = 0; i < hiddenClasses.length; i++) {
296             if (name.startsWith(hiddenClasses[i])) {
297                 return true;
298             }
299         }
300         return false;
301     }
302 
303     private Class resolveClass(Class clazz, boolean resolve) {
304         if (resolve) {
305             resolveClass(clazz);
306         }
307         return clazz;
308     }
309 
310     public URL getResource(String name) {
311         if (isDestroyed()) {
312             return null;
313         }
314 
315         //
316         // if we are using inverse class loading, check local urls first
317         //
318         if (inverseClassLoading && !isDestroyed() && !isNonOverridableResource(name)) {
319             URL url = findResource(name);
320             if (url != null) {
321                 return url;
322             }
323         }
324 
325         //
326         // Check parent class loaders
327         //
328         if (!isHiddenResource(name)) {
329             for (int i = 0; i < parents.length; i++) {
330                 ClassLoader parent = parents[i];
331                 URL url = parent.getResource(name);
332                 if (url != null) {
333                     return url;
334                 }
335             }
336         }
337 
338         //
339         // if we are not using inverse class loading, check local urls now
340         //
341         // don't worry about excluding non-overridable resources here... we
342         // have alredy checked he parent and the parent didn't have the
343         // resource, so we can override now
344         if (!isDestroyed()) {
345             // parents didn't have the resource; attempt to load it from my urls
346             return findResource(name);
347         }
348 
349         return null;
350     }
351 
352     public Enumeration findResources(String name) throws IOException {
353         if (isDestroyed()) {
354             return Collections.enumeration(Collections.EMPTY_SET);
355         }
356 
357         List resources = new ArrayList();
358 
359         //
360         // if we are using inverse class loading, add the resources from local urls first
361         //
362         if (inverseClassLoading && !isDestroyed()) {
363             List myResources = Collections.list(super.findResources(name));
364             resources.addAll(myResources);
365         }
366 
367         //
368         // Add parent resources
369         //
370         for (int i = 0; i < parents.length; i++) {
371             ClassLoader parent = parents[i];
372             List parentResources = Collections.list(parent.getResources(name));
373             resources.addAll(parentResources);
374         }
375 
376         //
377         // if we are not using inverse class loading, add the resources from local urls now
378         //
379         if (!inverseClassLoading && !isDestroyed()) {
380             List myResources = Collections.list(super.findResources(name));
381             resources.addAll(myResources);
382         }
383 
384         return Collections.enumeration(resources);
385     }
386 
387     private boolean isNonOverridableResource(String name) {
388         for (int i = 0; i < nonOverridableResources.length; i++) {
389             if (name.startsWith(nonOverridableResources[i])) {
390                 return true;
391             }
392         }
393         return false;
394     }
395 
396     private boolean isHiddenResource(String name) {
397         for (int i = 0; i < hiddenResources.length; i++) {
398             if (name.startsWith(hiddenResources[i])) {
399                 return true;
400             }
401         }
402         return false;
403     }
404 
405     public String toString() {
406         return "[" + getClass().getName() + " id=" + id + "]";
407     }
408 
409     public synchronized boolean isDestroyed() {
410         return destroyed;
411     }
412 
413     public void destroy() {
414         synchronized(this) {
415             if (destroyed) return;
416             destroyed = true;
417         }
418 
419         LogFactory.release(this);
420         clearSoftCache(ObjectInputStream.class, "subclassAudits");
421         clearSoftCache(ObjectOutputStream.class, "subclassAudits");
422         clearSoftCache(ObjectStreamClass.class, "localDescs");
423         clearSoftCache(ObjectStreamClass.class, "reflectors");
424 
425         // The beanInfoCache in java.beans.Introspector will hold on to Classes which
426         // it has introspected. If we don't flush the cache, we may run out of
427         // Permanent Generation space.
428         Introspector.flushCaches();
429     }
430 
431     private static final Object lock = new Object();
432     private static boolean clearSoftCacheFailed = false;
433 
434     private static void clearSoftCache(Class clazz, String fieldName) {
435         Map cache = null;
436         try {
437             Field f = clazz.getDeclaredField(fieldName);
438             f.setAccessible(true);
439             cache = (Map) f.get(null);
440         } catch (Throwable e) {
441             synchronized (lock) {
442                 if (!clearSoftCacheFailed) {
443                     clearSoftCacheFailed = true;
444                     LogFactory.getLog(MultiParentClassLoader.class).debug("Unable to clear SoftCache field " + fieldName + " in class " + clazz);
445                 }
446             }
447         }
448 
449         if (cache != null) {
450             synchronized (cache) {
451                 cache.clear();
452             }
453         }
454     }
455 
456 }