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.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: 487175 $ $Date: 2006-12-14 03:10:31 -0800 (Thu, 14 Dec 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            // This is a reasonable hack.  We can add some classes to the list below.
243            // Since we know these classes are in the system class loader let's not waste our
244            // time going through the hierarchy.
245            //
246            // The order is based on profiling the server.  It may not be optimal for all
247            // workloads.
248            
249            if ( name.startsWith("java.") ||
250                     name.equals("boolean")   ||
251                     name.equals("int")       ||
252                     name.equals("double")    ||
253                     name.equals("long")) {
254                Class clazz = ClassLoader.getSystemClassLoader().loadClass(name);
255                return resolveClass(clazz, resolve);
256            }
257            
258            //
259            // if we are using inverse class loading, check local urls first
260            //
261            if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) {
262                try {
263                    Class clazz = findClass(name);
264                    return resolveClass(clazz, resolve);
265                } catch (ClassNotFoundException ignored) {
266                }
267            }
268    
269            //
270            // Check parent class loaders
271            //
272            if (!isHiddenClass(name)) {
273                for (int i = 0; i < parents.length; i++) {
274                    ClassLoader parent = parents[i];
275                    try {
276                        Class clazz = parent.loadClass(name);
277                        return resolveClass(clazz, resolve);
278                    } catch (ClassNotFoundException ignored) {
279                        // this parent didn't have the class; try the next one
280                    }
281                }
282            }
283    
284            //
285            // if we are not using inverse class loading, check local urls now
286            //
287            // don't worry about excluding non-overridable classes here... we
288            // have alredy checked he parent and the parent didn't have the
289            // class, so we can override now
290            if (!isDestroyed()) {
291                try {
292                    Class clazz = findClass(name);
293                    return resolveClass(clazz, resolve);
294                } catch (ClassNotFoundException ignored) {
295                }
296            }
297    
298            throw new ClassNotFoundException(name + " in classloader " + id);
299        }
300    
301        private boolean isNonOverridableClass(String name) {
302            for (int i = 0; i < nonOverridableClasses.length; i++) {
303                if (name.startsWith(nonOverridableClasses[i])) {
304                    return true;
305                }
306            }
307            return false;
308        }
309    
310        private boolean isHiddenClass(String name) {
311            for (int i = 0; i < hiddenClasses.length; i++) {
312                if (name.startsWith(hiddenClasses[i])) {
313                    return true;
314                }
315            }
316            return false;
317        }
318    
319        private Class resolveClass(Class clazz, boolean resolve) {
320            if (resolve) {
321                resolveClass(clazz);
322            }
323            return clazz;
324        }
325    
326        public URL getResource(String name) {
327            if (isDestroyed()) {
328                return null;
329            }
330    
331            //
332            // if we are using inverse class loading, check local urls first
333            //
334            if (inverseClassLoading && !isDestroyed() && !isNonOverridableResource(name)) {
335                URL url = findResource(name);
336                if (url != null) {
337                    return url;
338                }
339            }
340    
341            //
342            // Check parent class loaders
343            //
344            if (!isHiddenResource(name)) {
345                for (int i = 0; i < parents.length; i++) {
346                    ClassLoader parent = parents[i];
347                    URL url = parent.getResource(name);
348                    if (url != null) {
349                        return url;
350                    }
351                }
352            }
353    
354            //
355            // if we are not using inverse class loading, check local urls now
356            //
357            // don't worry about excluding non-overridable resources here... we
358            // have alredy checked he parent and the parent didn't have the
359            // resource, so we can override now
360            if (!isDestroyed()) {
361                // parents didn't have the resource; attempt to load it from my urls
362                return findResource(name);
363            }
364    
365            return null;
366        }
367    
368        public Enumeration findResources(String name) throws IOException {
369            if (isDestroyed()) {
370                return Collections.enumeration(Collections.EMPTY_SET);
371            }
372    
373            List resources = new ArrayList();
374    
375            //
376            // if we are using inverse class loading, add the resources from local urls first
377            //
378            if (inverseClassLoading && !isDestroyed()) {
379                List myResources = Collections.list(super.findResources(name));
380                resources.addAll(myResources);
381            }
382    
383            //
384            // Add parent resources
385            //
386            for (int i = 0; i < parents.length; i++) {
387                ClassLoader parent = parents[i];
388                List parentResources = Collections.list(parent.getResources(name));
389                resources.addAll(parentResources);
390            }
391    
392            //
393            // if we are not using inverse class loading, add the resources from local urls now
394            //
395            if (!inverseClassLoading && !isDestroyed()) {
396                List myResources = Collections.list(super.findResources(name));
397                resources.addAll(myResources);
398            }
399    
400            return Collections.enumeration(resources);
401        }
402    
403        private boolean isNonOverridableResource(String name) {
404            for (int i = 0; i < nonOverridableResources.length; i++) {
405                if (name.startsWith(nonOverridableResources[i])) {
406                    return true;
407                }
408            }
409            return false;
410        }
411    
412        private boolean isHiddenResource(String name) {
413            for (int i = 0; i < hiddenResources.length; i++) {
414                if (name.startsWith(hiddenResources[i])) {
415                    return true;
416                }
417            }
418            return false;
419        }
420    
421        public String toString() {
422            return "[" + getClass().getName() + " id=" + id + "]";
423        }
424    
425        public synchronized boolean isDestroyed() {
426            return destroyed;
427        }
428    
429        public void destroy() {
430            synchronized(this) {
431                if (destroyed) return;
432                destroyed = true;
433            }
434    
435            LogFactory.release(this);
436            clearSoftCache(ObjectInputStream.class, "subclassAudits");
437            clearSoftCache(ObjectOutputStream.class, "subclassAudits");
438            clearSoftCache(ObjectStreamClass.class, "localDescs");
439            clearSoftCache(ObjectStreamClass.class, "reflectors");
440    
441            // The beanInfoCache in java.beans.Introspector will hold on to Classes which
442            // it has introspected. If we don't flush the cache, we may run out of
443            // Permanent Generation space.
444            Introspector.flushCaches();
445        }
446    
447        private static final Object lock = new Object();
448        private static boolean clearSoftCacheFailed = false;
449    
450        private static void clearSoftCache(Class clazz, String fieldName) {
451            Map cache = null;
452            try {
453                Field f = clazz.getDeclaredField(fieldName);
454                f.setAccessible(true);
455                cache = (Map) f.get(null);
456            } catch (Throwable e) {
457                synchronized (lock) {
458                    if (!clearSoftCacheFailed) {
459                        clearSoftCacheFailed = true;
460                        LogFactory.getLog(MultiParentClassLoader.class).debug("Unable to clear SoftCache field " + fieldName + " in class " + clazz);
461                    }
462                }
463            }
464    
465            if (cache != null) {
466                synchronized (cache) {
467                    cache.clear();
468                }
469            }
470        }
471    
472    }