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 }