View Javadoc

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         // todo this is not right
189         // first get the resources from the parent classloaders
190         Enumeration parentResources = super.findResources(resourceName);
191 
192         // get the classes from my urls
193         Enumeration myResources = (Enumeration) AccessController.doPrivileged(new PrivilegedAction() {
194             public Object run() {
195                 return resourceFinder.findResources(resourceName);
196             }
197         }, acc);
198 
199         // join the two together
200         Enumeration resources = new UnionEnumeration(parentResources, myResources);
201         return resources;
202     }
203 
204     /**
205      * {@inheritDoc}
206      */
207     protected String findLibrary(String libraryName) {
208         // if the libraryName is actually a directory it is invalid
209         int pathEnd = libraryName.lastIndexOf('/');
210         if (pathEnd == libraryName.length() - 1) {
211             throw new IllegalArgumentException("libraryName ends with a '/' character: " + libraryName);
212         }
213 
214         // get the name if the library file
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         // get a resource handle to the library
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         // the library must be accessable on the file system
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                     // first think check if we are allowed to define the package
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                     // convert the class name to a file name
265                     String resourceName = className.replace('.', '/') + ".class";
266 
267                     // find the class file resource
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                         // get the bytes from the class file
277                         bytes = resourceHandle.getBytes();
278 
279                         // get the manifest for defining the packages
280                         manifest = resourceHandle.getManifest();
281                     } catch (IOException e) {
282                         throw new ClassNotFoundException(className, e);
283                     }
284 
285                     // get the certificates for the code source
286                     Certificate[] certificates = resourceHandle.getCertificates();
287 
288                     // the code source url is used to define the package and as the security context for the class
289                     URL codeSourceUrl = resourceHandle.getCodeSourceUrl();
290 
291                     // define the package (required for security)
292                     definePackage(className, codeSourceUrl, manifest);
293 
294                     // this is the security context of the class
295                     CodeSource codeSource = new CodeSource(codeSourceUrl, certificates);
296 
297                     // load the class into the vm
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 }