001 /** 002 * 003 * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable. 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.classloader; 018 019 import java.io.IOException; 020 import java.io.File; 021 import java.net.URL; 022 import java.net.URI; 023 import java.net.URLClassLoader; 024 import java.security.AccessControlContext; 025 import java.security.AccessController; 026 import java.security.CodeSource; 027 import java.security.PrivilegedAction; 028 import java.security.PrivilegedExceptionAction; 029 import java.security.PrivilegedActionException; 030 import java.security.cert.Certificate; 031 import java.util.Collection; 032 import java.util.Enumeration; 033 import java.util.jar.Attributes; 034 import java.util.jar.Manifest; 035 036 import org.apache.geronimo.kernel.config.MultiParentClassLoader; 037 import org.apache.geronimo.kernel.repository.Artifact; 038 039 /** 040 * The JarFileClassLoader that loads classes and resources from a list of JarFiles. This method is simmilar to URLClassLoader 041 * except it properly closes JarFiles when the classloader is destroyed so that the file read lock will be released, and 042 * the jar file can be modified and deleted. 043 * <p> 044 * Note: This implementation currently does not work reliably on windows, since the jar URL handler included with the Sun JavaVM 045 * holds a read lock on the JarFile, and this lock is not released when the jar url is dereferenced. To fix this a 046 * replacement for the jar url handler must be written. 047 * 048 * @author Dain Sundstrom 049 * @version $Id: JarFileClassLoader.java 471382 2006-11-05 09:01:39Z djencks $ 050 * @since 2.0 051 */ 052 public class JarFileClassLoader extends MultiParentClassLoader { 053 private static final URL[] EMPTY_URLS = new URL[0]; 054 055 private final UrlResourceFinder resourceFinder = new UrlResourceFinder(); 056 private final AccessControlContext acc; 057 058 /** 059 * Creates a JarFileClassLoader that is a child of the system class loader. 060 * @param id the name of this class loader 061 * @param urls a list of URLs from which classes and resources should be loaded 062 */ 063 public JarFileClassLoader(Artifact id, URL[] urls) { 064 super(id, EMPTY_URLS); 065 this.acc = AccessController.getContext(); 066 addURLs(urls); 067 } 068 069 /** 070 * Creates a JarFileClassLoader that is a child of the specified class loader. 071 * @param id the name of this class loader 072 * @param urls a list of URLs from which classes and resources should be loaded 073 * @param parent the parent of this class loader 074 */ 075 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader parent) { 076 super(id, EMPTY_URLS, parent); 077 this.acc = AccessController.getContext(); 078 addURLs(urls); 079 } 080 081 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { 082 super(id, EMPTY_URLS, parent, inverseClassLoading, hiddenClasses, nonOverridableClasses); 083 this.acc = AccessController.getContext(); 084 addURLs(urls); 085 } 086 087 /** 088 * Creates a named class loader as a child of the specified parents. 089 * @param id the name of this class loader 090 * @param urls the urls from which this class loader will classes and resources 091 * @param parents the parents of this class loader 092 */ 093 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader[] parents) { 094 super(id, EMPTY_URLS, parents); 095 this.acc = AccessController.getContext(); 096 addURLs(urls); 097 } 098 099 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, Collection hiddenClasses, Collection nonOverridableClasses) { 100 super(id, EMPTY_URLS, parents, inverseClassLoading, hiddenClasses, nonOverridableClasses); 101 this.acc = AccessController.getContext(); 102 addURLs(urls); 103 } 104 105 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { 106 super(id, EMPTY_URLS, parents, inverseClassLoading, hiddenClasses, nonOverridableClasses); 107 this.acc = AccessController.getContext(); 108 addURLs(urls); 109 } 110 111 public JarFileClassLoader(JarFileClassLoader source) { 112 super(source); 113 this.acc = AccessController.getContext(); 114 addURLs(source.getURLs()); 115 } 116 117 public static ClassLoader copy(ClassLoader source) { 118 if (source instanceof JarFileClassLoader) { 119 return new JarFileClassLoader((JarFileClassLoader) source); 120 } else if (source instanceof MultiParentClassLoader) { 121 return new MultiParentClassLoader((MultiParentClassLoader) source); 122 } else if (source instanceof URLClassLoader) { 123 return new URLClassLoader(((URLClassLoader)source).getURLs(), source.getParent()); 124 } else { 125 return new URLClassLoader(new URL[0], source); 126 } 127 } 128 129 ClassLoader copy() { 130 return JarFileClassLoader.copy(this); 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 public URL[] getURLs() { 137 return resourceFinder.getUrls(); 138 } 139 140 /** 141 * {@inheritDoc} 142 */ 143 public void addURL(final URL url) { 144 AccessController.doPrivileged(new PrivilegedAction() { 145 public Object run() { 146 resourceFinder.addUrl(url); 147 return null; 148 } 149 }, acc); 150 } 151 152 /** 153 * Adds an array of urls to the end of this class loader. 154 * @param urls the URLs to add 155 */ 156 protected void addURLs(final URL[] urls) { 157 AccessController.doPrivileged(new PrivilegedAction() { 158 public Object run() { 159 resourceFinder.addUrls(urls); 160 return null; 161 } 162 }, acc); 163 } 164 165 /** 166 * {@inheritDoc} 167 */ 168 public void destroy() { 169 resourceFinder.destroy(); 170 super.destroy(); 171 } 172 173 /** 174 * {@inheritDoc} 175 */ 176 public URL findResource(final String resourceName) { 177 return (URL) AccessController.doPrivileged(new PrivilegedAction() { 178 public Object run() { 179 return resourceFinder.findResource(resourceName); 180 } 181 }, acc); 182 } 183 184 /** 185 * {@inheritDoc} 186 */ 187 public Enumeration findResources(final String resourceName) throws IOException { 188 // todo this is not right 189 // first get the resources from the parent classloaders 190 Enumeration parentResources = super.findResources(resourceName); 191 192 // get the classes from my urls 193 Enumeration myResources = (Enumeration) AccessController.doPrivileged(new PrivilegedAction() { 194 public Object run() { 195 return resourceFinder.findResources(resourceName); 196 } 197 }, acc); 198 199 // join the two together 200 Enumeration resources = new UnionEnumeration(parentResources, myResources); 201 return resources; 202 } 203 204 /** 205 * {@inheritDoc} 206 */ 207 protected String findLibrary(String libraryName) { 208 // if the libraryName is actually a directory it is invalid 209 int pathEnd = libraryName.lastIndexOf('/'); 210 if (pathEnd == libraryName.length() - 1) { 211 throw new IllegalArgumentException("libraryName ends with a '/' character: " + libraryName); 212 } 213 214 // get the name if the library file 215 final String resourceName; 216 if (pathEnd < 0) { 217 resourceName = System.mapLibraryName(libraryName); 218 } else { 219 String path = libraryName.substring(0, pathEnd + 1); 220 String file = libraryName.substring(pathEnd + 1); 221 resourceName = path + System.mapLibraryName(file); 222 } 223 224 // get a resource handle to the library 225 ResourceHandle resourceHandle = (ResourceHandle) AccessController.doPrivileged(new PrivilegedAction() { 226 public Object run() { 227 return resourceFinder.getResource(resourceName); 228 } 229 }, acc); 230 231 if (resourceHandle == null) { 232 return null; 233 } 234 235 // the library must be accessable on the file system 236 URL url = resourceHandle.getUrl(); 237 if (!"file".equals(url.getProtocol())) { 238 return null; 239 } 240 241 String path = new File(URI.create(url.toString())).getPath(); 242 return path; 243 } 244 245 /** 246 * {@inheritDoc} 247 */ 248 protected Class findClass(final String className) throws ClassNotFoundException { 249 try { 250 return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { 251 public Object run() throws ClassNotFoundException { 252 // first think check if we are allowed to define the package 253 SecurityManager securityManager = System.getSecurityManager(); 254 if (securityManager != null) { 255 String packageName; 256 int packageEnd = className.lastIndexOf('.'); 257 if (packageEnd >= 0) { 258 packageName = className.substring(0, packageEnd); 259 securityManager.checkPackageDefinition(packageName); 260 } 261 } 262 263 264 // convert the class name to a file name 265 String resourceName = className.replace('.', '/') + ".class"; 266 267 // find the class file resource 268 ResourceHandle resourceHandle = resourceFinder.getResource(resourceName); 269 if (resourceHandle == null) { 270 throw new ClassNotFoundException(className); 271 } 272 273 byte[] bytes; 274 Manifest manifest; 275 try { 276 // get the bytes from the class file 277 bytes = resourceHandle.getBytes(); 278 279 // get the manifest for defining the packages 280 manifest = resourceHandle.getManifest(); 281 } catch (IOException e) { 282 throw new ClassNotFoundException(className, e); 283 } 284 285 // get the certificates for the code source 286 Certificate[] certificates = resourceHandle.getCertificates(); 287 288 // the code source url is used to define the package and as the security context for the class 289 URL codeSourceUrl = resourceHandle.getCodeSourceUrl(); 290 291 // define the package (required for security) 292 definePackage(className, codeSourceUrl, manifest); 293 294 // this is the security context of the class 295 CodeSource codeSource = new CodeSource(codeSourceUrl, certificates); 296 297 // load the class into the vm 298 Class clazz = defineClass(className, bytes, 0, bytes.length, codeSource); 299 return clazz; 300 } 301 }, acc); 302 } catch (PrivilegedActionException e) { 303 throw (ClassNotFoundException) e.getException(); 304 } 305 } 306 307 private void definePackage(String className, URL jarUrl, Manifest manifest) { 308 int packageEnd = className.lastIndexOf('.'); 309 if (packageEnd < 0) { 310 return; 311 } 312 313 String packageName = className.substring(0, packageEnd); 314 String packagePath = packageName.replace('.', '/') + "/"; 315 316 Attributes packageAttributes = null; 317 Attributes mainAttributes = null; 318 if (manifest != null) { 319 packageAttributes = manifest.getAttributes(packagePath); 320 mainAttributes = manifest.getMainAttributes(); 321 } 322 Package pkg = getPackage(packageName); 323 if (pkg != null) { 324 if (pkg.isSealed()) { 325 if (!pkg.isSealed(jarUrl)) { 326 throw new SecurityException("Package was already sealed with another URL: package=" + packageName + ", url=" + jarUrl); 327 } 328 } else { 329 if (isSealed(packageAttributes, mainAttributes)) { 330 throw new SecurityException("Package was already been loaded and not sealed: package=" + packageName + ", url=" + jarUrl); 331 } 332 } 333 } else { 334 String specTitle = getAttribute(Attributes.Name.SPECIFICATION_TITLE, packageAttributes, mainAttributes); 335 String specVendor = getAttribute(Attributes.Name.SPECIFICATION_VENDOR, packageAttributes, mainAttributes); 336 String specVersion = getAttribute(Attributes.Name.SPECIFICATION_VERSION, packageAttributes, mainAttributes); 337 String implTitle = getAttribute(Attributes.Name.IMPLEMENTATION_TITLE, packageAttributes, mainAttributes); 338 String implVendor = getAttribute(Attributes.Name.IMPLEMENTATION_VENDOR, packageAttributes, mainAttributes); 339 String implVersion = getAttribute(Attributes.Name.IMPLEMENTATION_VERSION, packageAttributes, mainAttributes); 340 341 URL sealBase = null; 342 if (isSealed(packageAttributes, mainAttributes)) { 343 sealBase = jarUrl; 344 } 345 346 definePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); 347 } 348 } 349 350 private String getAttribute(Attributes.Name name, Attributes packageAttributes, Attributes mainAttributes) { 351 if (packageAttributes != null) { 352 String value = packageAttributes.getValue(name); 353 if (value != null) { 354 return value; 355 } 356 } 357 if (mainAttributes != null) { 358 return mainAttributes.getValue(name); 359 } 360 return null; 361 } 362 363 private boolean isSealed(Attributes packageAttributes, Attributes mainAttributes) { 364 String sealed = getAttribute(Attributes.Name.SEALED, packageAttributes, mainAttributes); 365 if (sealed == null) { 366 return false; 367 } 368 return "true".equalsIgnoreCase(sealed); 369 } 370 }