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.xbean.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.security.AccessControlContext;
024 import java.security.AccessController;
025 import java.security.CodeSource;
026 import java.security.PrivilegedAction;
027 import java.security.PrivilegedExceptionAction;
028 import java.security.PrivilegedActionException;
029 import java.security.cert.Certificate;
030 import java.util.Collection;
031 import java.util.Enumeration;
032 import java.util.jar.Attributes;
033 import 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 */
048 public 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 }