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 }