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.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 518426 2007-03-15 01:22:56Z 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 /* 188 public Enumeration findResources(final String resourceName) throws IOException { 189 // todo this is not right 190 // first get the resources from the parent classloaders 191 Enumeration parentResources = super.findResources(resourceName); 192 193 // get the classes from my urls 194 Enumeration myResources = (Enumeration) AccessController.doPrivileged(new PrivilegedAction() { 195 public Object run() { 196 return resourceFinder.findResources(resourceName); 197 } 198 }, acc); 199 200 // join the two together 201 Enumeration resources = new UnionEnumeration(parentResources, myResources); 202 return resources; 203 } 204 */ 205 206 protected Enumeration<URL> internalfindResources(final String name) throws IOException { 207 return AccessController.doPrivileged(new PrivilegedAction<Enumeration<URL>>() { 208 public Enumeration<URL> run() { 209 return resourceFinder.findResources(name); 210 } 211 }, acc); 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 protected String findLibrary(String libraryName) { 218 // if the libraryName is actually a directory it is invalid 219 int pathEnd = libraryName.lastIndexOf('/'); 220 if (pathEnd == libraryName.length() - 1) { 221 throw new IllegalArgumentException("libraryName ends with a '/' character: " + libraryName); 222 } 223 224 // get the name if the library file 225 final String resourceName; 226 if (pathEnd < 0) { 227 resourceName = System.mapLibraryName(libraryName); 228 } else { 229 String path = libraryName.substring(0, pathEnd + 1); 230 String file = libraryName.substring(pathEnd + 1); 231 resourceName = path + System.mapLibraryName(file); 232 } 233 234 // get a resource handle to the library 235 ResourceHandle resourceHandle = (ResourceHandle) AccessController.doPrivileged(new PrivilegedAction() { 236 public Object run() { 237 return resourceFinder.getResource(resourceName); 238 } 239 }, acc); 240 241 if (resourceHandle == null) { 242 return null; 243 } 244 245 // the library must be accessable on the file system 246 URL url = resourceHandle.getUrl(); 247 if (!"file".equals(url.getProtocol())) { 248 return null; 249 } 250 251 String path = new File(URI.create(url.toString())).getPath(); 252 return path; 253 } 254 255 /** 256 * {@inheritDoc} 257 */ 258 protected Class findClass(final String className) throws ClassNotFoundException { 259 try { 260 return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { 261 public Object run() throws ClassNotFoundException { 262 // first think check if we are allowed to define the package 263 SecurityManager securityManager = System.getSecurityManager(); 264 if (securityManager != null) { 265 String packageName; 266 int packageEnd = className.lastIndexOf('.'); 267 if (packageEnd >= 0) { 268 packageName = className.substring(0, packageEnd); 269 securityManager.checkPackageDefinition(packageName); 270 } 271 } 272 273 274 // convert the class name to a file name 275 String resourceName = className.replace('.', '/') + ".class"; 276 277 // find the class file resource 278 ResourceHandle resourceHandle = resourceFinder.getResource(resourceName); 279 if (resourceHandle == null) { 280 throw new ClassNotFoundException(className); 281 } 282 283 byte[] bytes; 284 Manifest manifest; 285 try { 286 // get the bytes from the class file 287 bytes = resourceHandle.getBytes(); 288 289 // get the manifest for defining the packages 290 manifest = resourceHandle.getManifest(); 291 } catch (IOException e) { 292 throw new ClassNotFoundException(className, e); 293 } 294 295 // get the certificates for the code source 296 Certificate[] certificates = resourceHandle.getCertificates(); 297 298 // the code source url is used to define the package and as the security context for the class 299 URL codeSourceUrl = resourceHandle.getCodeSourceUrl(); 300 301 // define the package (required for security) 302 definePackage(className, codeSourceUrl, manifest); 303 304 // this is the security context of the class 305 CodeSource codeSource = new CodeSource(codeSourceUrl, certificates); 306 307 // load the class into the vm 308 Class clazz = defineClass(className, bytes, 0, bytes.length, codeSource); 309 return clazz; 310 } 311 }, acc); 312 } catch (PrivilegedActionException e) { 313 throw (ClassNotFoundException) e.getException(); 314 } 315 } 316 317 private void definePackage(String className, URL jarUrl, Manifest manifest) { 318 int packageEnd = className.lastIndexOf('.'); 319 if (packageEnd < 0) { 320 return; 321 } 322 323 String packageName = className.substring(0, packageEnd); 324 String packagePath = packageName.replace('.', '/') + "/"; 325 326 Attributes packageAttributes = null; 327 Attributes mainAttributes = null; 328 if (manifest != null) { 329 packageAttributes = manifest.getAttributes(packagePath); 330 mainAttributes = manifest.getMainAttributes(); 331 } 332 Package pkg = getPackage(packageName); 333 if (pkg != null) { 334 if (pkg.isSealed()) { 335 if (!pkg.isSealed(jarUrl)) { 336 throw new SecurityException("Package was already sealed with another URL: package=" + packageName + ", url=" + jarUrl); 337 } 338 } else { 339 if (isSealed(packageAttributes, mainAttributes)) { 340 throw new SecurityException("Package was already been loaded and not sealed: package=" + packageName + ", url=" + jarUrl); 341 } 342 } 343 } else { 344 String specTitle = getAttribute(Attributes.Name.SPECIFICATION_TITLE, packageAttributes, mainAttributes); 345 String specVendor = getAttribute(Attributes.Name.SPECIFICATION_VENDOR, packageAttributes, mainAttributes); 346 String specVersion = getAttribute(Attributes.Name.SPECIFICATION_VERSION, packageAttributes, mainAttributes); 347 String implTitle = getAttribute(Attributes.Name.IMPLEMENTATION_TITLE, packageAttributes, mainAttributes); 348 String implVendor = getAttribute(Attributes.Name.IMPLEMENTATION_VENDOR, packageAttributes, mainAttributes); 349 String implVersion = getAttribute(Attributes.Name.IMPLEMENTATION_VERSION, packageAttributes, mainAttributes); 350 351 URL sealBase = null; 352 if (isSealed(packageAttributes, mainAttributes)) { 353 sealBase = jarUrl; 354 } 355 356 definePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); 357 } 358 } 359 360 private String getAttribute(Attributes.Name name, Attributes packageAttributes, Attributes mainAttributes) { 361 if (packageAttributes != null) { 362 String value = packageAttributes.getValue(name); 363 if (value != null) { 364 return value; 365 } 366 } 367 if (mainAttributes != null) { 368 return mainAttributes.getValue(name); 369 } 370 return null; 371 } 372 373 private boolean isSealed(Attributes packageAttributes, Attributes mainAttributes) { 374 String sealed = getAttribute(Attributes.Name.SEALED, packageAttributes, mainAttributes); 375 if (sealed == null) { 376 return false; 377 } 378 return "true".equalsIgnoreCase(sealed); 379 } 380 }