001    /**
002     *
003     * Copyright 2005 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  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.List;
033    import java.util.Map;
034    
035    import org.apache.commons.logging.LogFactory;
036    import org.apache.geronimo.kernel.repository.Artifact;
037    
038    /**
039     * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class
040     * loader model to support a list of parent class loaders.  Each operation that accesses a parent, has been replaced
041     * with a operation that checks each parent in order.  This getParent method of this class will always return null,
042     * which may be interpreted by the calling code to mean that this class loader is a direct child of the system class
043     * loader.
044     *
045     * @version $Rev: 471382 $ $Date: 2006-11-05 01:01:39 -0800 (Sun, 05 Nov 2006) $
046     */
047    public class MultiParentClassLoader extends URLClassLoader {
048        private final Artifact id;
049        private final ClassLoader[] parents;
050        private final boolean inverseClassLoading;
051        private final String[] hiddenClasses;
052        private final String[] nonOverridableClasses;
053        private final String[] hiddenResources;
054        private final String[] nonOverridableResources;
055        private boolean destroyed = false;
056    
057        /**
058         * Creates a named class loader with no parents.
059         *
060         * @param id   the id of this class loader
061         * @param urls the urls from which this class loader will classes and resources
062         */
063        public MultiParentClassLoader(Artifact id, URL[] urls) {
064            super(urls);
065            this.id = id;
066            parents = new ClassLoader[]{ClassLoader.getSystemClassLoader()};
067            inverseClassLoading = false;
068            hiddenClasses = new String[0];
069            nonOverridableClasses = new String[0];
070            hiddenResources = new String[0];
071            nonOverridableResources = new String[0];
072        }
073    
074    
075        /**
076         * Creates a named class loader as a child of the specified parent.
077         *
078         * @param id     the id of this class loader
079         * @param urls   the urls from which this class loader will classes and resources
080         * @param parent the parent of this class loader
081         */
082        public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent) {
083            this(id, urls, new ClassLoader[]{parent});
084        }
085    
086        public MultiParentClassLoader(Artifact id, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
087            this(id, urls, new ClassLoader[]{parent}, inverseClassLoading, hiddenClasses, nonOverridableClasses);
088        }
089    
090        /**
091         * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory
092         * for accessing the urls..
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         * @param parent  the parent of this class loader
097         * @param factory the URLStreamHandlerFactory used to access the urls
098         */
099        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    }