001    /**
002     *
003     * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable.
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  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 471382 2006-11-05 09:01:39Z 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        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    }