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 }