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