1 /**
2 *
3 * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.geronimo.kernel.classloader;
18
19 import java.io.IOException;
20 import java.io.File;
21 import java.net.URL;
22 import java.net.URI;
23 import java.net.URLClassLoader;
24 import java.security.AccessControlContext;
25 import java.security.AccessController;
26 import java.security.CodeSource;
27 import java.security.PrivilegedAction;
28 import java.security.PrivilegedExceptionAction;
29 import java.security.PrivilegedActionException;
30 import java.security.cert.Certificate;
31 import java.util.Collection;
32 import java.util.Enumeration;
33 import java.util.jar.Attributes;
34 import java.util.jar.Manifest;
35
36 import org.apache.geronimo.kernel.config.MultiParentClassLoader;
37 import org.apache.geronimo.kernel.repository.Artifact;
38
39 /**
40 * The JarFileClassLoader that loads classes and resources from a list of JarFiles. This method is simmilar to URLClassLoader
41 * except it properly closes JarFiles when the classloader is destroyed so that the file read lock will be released, and
42 * the jar file can be modified and deleted.
43 * <p>
44 * Note: This implementation currently does not work reliably on windows, since the jar URL handler included with the Sun JavaVM
45 * holds a read lock on the JarFile, and this lock is not released when the jar url is dereferenced. To fix this a
46 * replacement for the jar url handler must be written.
47 *
48 * @author Dain Sundstrom
49 * @version $Id: JarFileClassLoader.java 471382 2006-11-05 09:01:39Z djencks $
50 * @since 2.0
51 */
52 public class JarFileClassLoader extends MultiParentClassLoader {
53 private static final URL[] EMPTY_URLS = new URL[0];
54
55 private final UrlResourceFinder resourceFinder = new UrlResourceFinder();
56 private final AccessControlContext acc;
57
58 /**
59 * Creates a JarFileClassLoader that is a child of the system class loader.
60 * @param id the name of this class loader
61 * @param urls a list of URLs from which classes and resources should be loaded
62 */
63 public JarFileClassLoader(Artifact id, URL[] urls) {
64 super(id, EMPTY_URLS);
65 this.acc = AccessController.getContext();
66 addURLs(urls);
67 }
68
69 /**
70 * Creates a JarFileClassLoader that is a child of the specified class loader.
71 * @param id the name of this class loader
72 * @param urls a list of URLs from which classes and resources should be loaded
73 * @param parent the parent of this class loader
74 */
75 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader parent) {
76 super(id, EMPTY_URLS, parent);
77 this.acc = AccessController.getContext();
78 addURLs(urls);
79 }
80
81 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) {
82 super(id, EMPTY_URLS, parent, inverseClassLoading, hiddenClasses, nonOverridableClasses);
83 this.acc = AccessController.getContext();
84 addURLs(urls);
85 }
86
87 /**
88 * Creates a named class loader as a child of the specified parents.
89 * @param id the name of this class loader
90 * @param urls the urls from which this class loader will classes and resources
91 * @param parents the parents of this class loader
92 */
93 public JarFileClassLoader(Artifact id, URL[] urls, ClassLoader[] parents) {
94 super(id, EMPTY_URLS, parents);
95 this.acc = AccessController.getContext();
96 addURLs(urls);
97 }
98
99 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 public Enumeration findResources(final String resourceName) throws IOException {
188
189
190 Enumeration parentResources = super.findResources(resourceName);
191
192
193 Enumeration myResources = (Enumeration) AccessController.doPrivileged(new PrivilegedAction() {
194 public Object run() {
195 return resourceFinder.findResources(resourceName);
196 }
197 }, acc);
198
199
200 Enumeration resources = new UnionEnumeration(parentResources, myResources);
201 return resources;
202 }
203
204 /**
205 * {@inheritDoc}
206 */
207 protected String findLibrary(String libraryName) {
208
209 int pathEnd = libraryName.lastIndexOf('/');
210 if (pathEnd == libraryName.length() - 1) {
211 throw new IllegalArgumentException("libraryName ends with a '/' character: " + libraryName);
212 }
213
214
215 final String resourceName;
216 if (pathEnd < 0) {
217 resourceName = System.mapLibraryName(libraryName);
218 } else {
219 String path = libraryName.substring(0, pathEnd + 1);
220 String file = libraryName.substring(pathEnd + 1);
221 resourceName = path + System.mapLibraryName(file);
222 }
223
224
225 ResourceHandle resourceHandle = (ResourceHandle) AccessController.doPrivileged(new PrivilegedAction() {
226 public Object run() {
227 return resourceFinder.getResource(resourceName);
228 }
229 }, acc);
230
231 if (resourceHandle == null) {
232 return null;
233 }
234
235
236 URL url = resourceHandle.getUrl();
237 if (!"file".equals(url.getProtocol())) {
238 return null;
239 }
240
241 String path = new File(URI.create(url.toString())).getPath();
242 return path;
243 }
244
245 /**
246 * {@inheritDoc}
247 */
248 protected Class findClass(final String className) throws ClassNotFoundException {
249 try {
250 return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() {
251 public Object run() throws ClassNotFoundException {
252
253 SecurityManager securityManager = System.getSecurityManager();
254 if (securityManager != null) {
255 String packageName;
256 int packageEnd = className.lastIndexOf('.');
257 if (packageEnd >= 0) {
258 packageName = className.substring(0, packageEnd);
259 securityManager.checkPackageDefinition(packageName);
260 }
261 }
262
263
264
265 String resourceName = className.replace('.', '/') + ".class";
266
267
268 ResourceHandle resourceHandle = resourceFinder.getResource(resourceName);
269 if (resourceHandle == null) {
270 throw new ClassNotFoundException(className);
271 }
272
273 byte[] bytes;
274 Manifest manifest;
275 try {
276
277 bytes = resourceHandle.getBytes();
278
279
280 manifest = resourceHandle.getManifest();
281 } catch (IOException e) {
282 throw new ClassNotFoundException(className, e);
283 }
284
285
286 Certificate[] certificates = resourceHandle.getCertificates();
287
288
289 URL codeSourceUrl = resourceHandle.getCodeSourceUrl();
290
291
292 definePackage(className, codeSourceUrl, manifest);
293
294
295 CodeSource codeSource = new CodeSource(codeSourceUrl, certificates);
296
297
298 Class clazz = defineClass(className, bytes, 0, bytes.length, codeSource);
299 return clazz;
300 }
301 }, acc);
302 } catch (PrivilegedActionException e) {
303 throw (ClassNotFoundException) e.getException();
304 }
305 }
306
307 private void definePackage(String className, URL jarUrl, Manifest manifest) {
308 int packageEnd = className.lastIndexOf('.');
309 if (packageEnd < 0) {
310 return;
311 }
312
313 String packageName = className.substring(0, packageEnd);
314 String packagePath = packageName.replace('.', '/') + "/";
315
316 Attributes packageAttributes = null;
317 Attributes mainAttributes = null;
318 if (manifest != null) {
319 packageAttributes = manifest.getAttributes(packagePath);
320 mainAttributes = manifest.getMainAttributes();
321 }
322 Package pkg = getPackage(packageName);
323 if (pkg != null) {
324 if (pkg.isSealed()) {
325 if (!pkg.isSealed(jarUrl)) {
326 throw new SecurityException("Package was already sealed with another URL: package=" + packageName + ", url=" + jarUrl);
327 }
328 } else {
329 if (isSealed(packageAttributes, mainAttributes)) {
330 throw new SecurityException("Package was already been loaded and not sealed: package=" + packageName + ", url=" + jarUrl);
331 }
332 }
333 } else {
334 String specTitle = getAttribute(Attributes.Name.SPECIFICATION_TITLE, packageAttributes, mainAttributes);
335 String specVendor = getAttribute(Attributes.Name.SPECIFICATION_VENDOR, packageAttributes, mainAttributes);
336 String specVersion = getAttribute(Attributes.Name.SPECIFICATION_VERSION, packageAttributes, mainAttributes);
337 String implTitle = getAttribute(Attributes.Name.IMPLEMENTATION_TITLE, packageAttributes, mainAttributes);
338 String implVendor = getAttribute(Attributes.Name.IMPLEMENTATION_VENDOR, packageAttributes, mainAttributes);
339 String implVersion = getAttribute(Attributes.Name.IMPLEMENTATION_VERSION, packageAttributes, mainAttributes);
340
341 URL sealBase = null;
342 if (isSealed(packageAttributes, mainAttributes)) {
343 sealBase = jarUrl;
344 }
345
346 definePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
347 }
348 }
349
350 private String getAttribute(Attributes.Name name, Attributes packageAttributes, Attributes mainAttributes) {
351 if (packageAttributes != null) {
352 String value = packageAttributes.getValue(name);
353 if (value != null) {
354 return value;
355 }
356 }
357 if (mainAttributes != null) {
358 return mainAttributes.getValue(name);
359 }
360 return null;
361 }
362
363 private boolean isSealed(Attributes packageAttributes, Attributes mainAttributes) {
364 String sealed = getAttribute(Attributes.Name.SEALED, packageAttributes, mainAttributes);
365 if (sealed == null) {
366 return false;
367 }
368 return "true".equalsIgnoreCase(sealed);
369 }
370 }