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