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    }