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 }