Return-Path: Delivered-To: apmail-geronimo-scm-archive@www.apache.org Received: (qmail 9628 invoked from network); 12 Apr 2006 02:48:40 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 12 Apr 2006 02:48:40 -0000 Received: (qmail 72540 invoked by uid 500); 12 Apr 2006 02:48:39 -0000 Delivered-To: apmail-geronimo-scm-archive@geronimo.apache.org Received: (qmail 72401 invoked by uid 500); 12 Apr 2006 02:48:39 -0000 Mailing-List: contact scm-help@geronimo.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: dev@geronimo.apache.org List-Id: Delivered-To: mailing list scm@geronimo.apache.org Received: (qmail 72388 invoked by uid 99); 12 Apr 2006 02:48:38 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 11 Apr 2006 19:48:38 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Tue, 11 Apr 2006 19:48:36 -0700 Received: (qmail 9492 invoked by uid 65534); 12 Apr 2006 02:48:15 -0000 Message-ID: <20060412024815.9489.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r393370 [1/2] - in /geronimo/sandbox/classloader: ./ src/ src/java/ src/java/org/ src/java/org/apache/ src/java/org/apache/geronimo/ src/java/org/apache/geronimo/kernel/ src/java/org/apache/geronimo/kernel/classloader/ src/java/org/apache/g... Date: Wed, 12 Apr 2006 02:48:10 -0000 To: scm@geronimo.apache.org From: dain@apache.org X-Mailer: svnmailer-1.0.7 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: dain Date: Tue Apr 11 19:48:07 2006 New Revision: 393370 URL: http://svn.apache.org/viewcvs?rev=393370&view=rev Log: Basic implementation of nested jar support. Need performance tuning before it can be used. Added: geronimo/sandbox/classloader/ geronimo/sandbox/classloader/NOTICE.txt geronimo/sandbox/classloader/src/ geronimo/sandbox/classloader/src/java/ geronimo/sandbox/classloader/src/java/org/ geronimo/sandbox/classloader/src/java/org/apache/ geronimo/sandbox/classloader/src/java/org/apache/geronimo/ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/GenericClassLoader.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceFinder.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceHandle.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceLoader.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceUtils.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/URIClassLoader.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jar/ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jar/Handler.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachedJarFile.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachingJarOpener.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/Handler.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarCache.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarOpener.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlConnection.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlStreamHandler.java geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/TempJarCache.java geronimo/sandbox/classloader/src/test/ geronimo/sandbox/classloader/src/test/org/ geronimo/sandbox/classloader/src/test/org/apache/ geronimo/sandbox/classloader/src/test/org/apache/geronimo/ geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/ geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/ geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/jar/ geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/jar/JarUrlTest.java Added: geronimo/sandbox/classloader/NOTICE.txt URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/NOTICE.txt?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/NOTICE.txt (added) +++ geronimo/sandbox/classloader/NOTICE.txt Tue Apr 11 19:48:07 2006 @@ -0,0 +1,4 @@ +Portions of this class loader module were originally developed by +Dawid Kurzyniec as part of the Emory University utilities library +and was released to the public domain, as explained at +http://creativecommons.org/licenses/publicdomain Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/GenericClassLoader.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/GenericClassLoader.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/GenericClassLoader.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/GenericClassLoader.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,394 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.net.URLStreamHandler; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.SecureClassLoader; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.Attributes.Name; +import java.util.jar.Manifest; + +/** + * This class loader can be used to find class, resource and library + * {@link ResourceHandle handles} + * as well as load classes, resources and libraries using abstract + * {@link ResourceFinder} entity encapsulating the searching approach. + * Resource handles allow accessing meta-information (like Attributes, + * Certificates etc.) related to classes, resources and libraries prior to + * loading them. + *

+ * GenericClassLoader is intended to be used as a base for custom class + * loaders. In most applications, GenericClassLoader can be used directly -- + * the application-specific functionality of resource searching can often be + * completely delegated to the resource finder. See {@link URIClassLoader} + * for a concrete implementation using a simple resource finder. + * + * @version $Rev$ $Date$ + */ +public class GenericClassLoader extends SecureClassLoader { + + protected ResourceFinder finder; + private AccessControlContext acc; + + /** + * Creates new GenericClassLoader instance using specified + * {@link ResourceFinder} to find resources and having specified + * parent class loader. + */ + public GenericClassLoader(ResourceFinder finder, ClassLoader parent) { + super(parent); + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkCreateClassLoader(); + } + this.finder = finder; + acc = AccessController.getContext(); + } + + /** + * Creates new GenericClassLoader instance using specified + * {@link ResourceFinder} to find resources and with default + * parent class loader. + */ + public GenericClassLoader(ResourceFinder finder) { + super(); + // this is to make the stack depth consistent with 1.1 + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkCreateClassLoader(); + } + this.finder = finder; + //acc = AccessController.getContext(); + } + + /** + * Finds and loads the class with the specified name. + * + * @param name the name of the class + * @return the resulting class + * @throws ClassNotFoundException if the class could not be found + */ + protected Class findClass(final String name) throws ClassNotFoundException { + try { + return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws ClassNotFoundException { + String path = name.replace('.', '/').concat(".class"); + ResourceHandle h = finder.getResource(path); + if (h != null) { + try { + return defineClass(name, h); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } else { + throw new ClassNotFoundException(name); + } + } + }, acc); + } catch (java.security.PrivilegedActionException pae) { + throw (ClassNotFoundException) pae.getException(); + } + } + + protected Class defineClass(String name, ResourceHandle h) throws IOException { + int i = name.lastIndexOf('.'); + URL url = h.getCodeSourceURL(); + if (i != -1) { // check package + String pkgname = name.substring(0, i); + // check if package already loaded + Package pkg = getPackage(pkgname); + Manifest man = h.getManifest(); + if (pkg != null) { + // package found, so check package sealing + boolean ok; + if (pkg.isSealed()) { + // verify that code source URLs are the same + ok = pkg.isSealed(url); + } else { + // make sure we are not attempting to seal the package + // at this code source URL + ok = (man == null) || !isSealed(pkgname, man); + } + if (!ok) { + throw new SecurityException("sealing violation: " + name); + } + } else { + // package not yet defined + if (man != null) { + definePackage(pkgname, man, url); + } else { + definePackage(pkgname, null, null, null, null, null, null, null); + } + } + } + + // now read the class bytes and define the class + byte[] b = h.getBytes(); + java.security.cert.Certificate[] certs = h.getCertificates(); + CodeSource cs = new CodeSource(url, certs); + return defineClass(name, b, 0, b.length, cs); + } + + /** + * returns true if the specified package name is sealed according to the + * given manifest. + */ + private boolean isSealed(String name, Manifest man) { + String path = name.replace('.', '/').concat("/"); + Attributes attr = man.getAttributes(path); + String sealed = null; + if (attr != null) { + sealed = attr.getValue(Name.SEALED); + } + if (sealed == null) { + if ((attr = man.getMainAttributes()) != null) { + sealed = attr.getValue(Name.SEALED); + } + } + return "true".equalsIgnoreCase(sealed); + } + + /** + * Finds the resource with the specified name. + * + * @param name the name of the resource + * @return a URL for the resource, or null + * if the resource could not be found. + */ + protected URL findResource(final String name) { + return (URL) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.findResource(name); + } + }, acc); + } + + /** + * Returns an Enumeration of URLs representing all of the resources + * having the specified name. + * + * @param name the resource name + * @return an Enumeration of URLs + * @throws IOException if an I/O exception occurs + */ + protected Enumeration findResources(final String name) throws IOException { + return (Enumeration) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.findResources(name); + } + }, acc); + } + + /** + * Returns the absolute path name of a native library. The VM + * invokes this method to locate the native libraries that belong + * to classes loaded with this class loader. If this method returns + * null, the VM searches the library along the path + * specified as the java.library.path property. + * This method invoke {@link #getLibraryHandle} method to find handle + * of this library. If the handle is found and its URL protocol is "file", + * the system-dependent absolute library file path is returned. + * Otherwise this method returns null.

+ *

+ * Subclasses can override this method to provide specific approaches + * in library searching. + * + * @param libname the library name + * @return the absolute path of the native library + * @see System#loadLibrary(String) + * @see System#mapLibraryName(String) + */ + protected String findLibrary(String libname) { + ResourceHandle md = getLibraryHandle(libname); + if (md == null) return null; + URL url = md.getURL(); + if (!"file".equals(url.getProtocol())) return null; + return new File(URI.create(url.toString())).getPath(); + } +// +// /** +// * Gets the ResourceHandle object for the specified loaded class. +// * +// * @param clazz the Class +// * @return the ResourceHandle of the Class +// */ +// protected ResourceHandle getClassHandle(Class clazz) +// { +// return null; +// } + + /** + * Finds the ResourceHandle object for the class with the specified name. + * Unlike findClass(), this method does not load the class. + * + * @param name the name of the class + * @return the ResourceHandle of the class + */ + protected ResourceHandle getClassHandle(final String name) { + String path = name.replace('.', '/').concat(".class"); + return getResourceHandle(path); + } + + /** + * Finds the ResourceHandle object for the resource with the specified name. + * + * @param name the name of the resource + * @return the ResourceHandle of the resource + */ + protected ResourceHandle getResourceHandle(final String name) { + return (ResourceHandle) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.getResource(name); + } + }, acc); + } + + /** + * Finds the ResourceHandle object for the native library with the specified + * name. + * The library name must be '/'-separated path. The last part of this + * path is substituted by its system-dependent mapping (using + * {@link System#mapLibraryName(String)} method). Next, the + * ResourceFinder is used to look for the library as it + * was ordinary resource.

+ *

+ * Subclasses can override this method to provide specific approaches + * in library searching. + * + * @param name the name of the library + * @return the ResourceHandle of the library + */ + protected ResourceHandle getLibraryHandle(final String name) { + int idx = name.lastIndexOf('/'); + String path; + String simplename; + if (idx == -1) { + path = ""; + simplename = name; + } else if (idx == name.length() - 1) { // name.endsWith('/') + throw new IllegalArgumentException(name); + } else { + path = name.substring(0, idx + 1); // including '/' + simplename = name.substring(idx + 1); + } + return getResourceHandle(path + System.mapLibraryName(simplename)); + } + + /** + * Returns an Enumeration of ResourceHandle objects representing all of the + * resources having the specified name. + * + * @param name the name of the resource + * @return the ResourceHandle of the resource + */ + protected Enumeration getResourceHandles(final String name) { + return (Enumeration) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.getResources(name); + } + }, acc); + } + + /** + * Defines a new package by name in this ClassLoader. The attributes + * contained in the specified Manifest will be used to obtain package + * version and sealing information. For sealed packages, the additional + * URL specifies the code source URL from which the package was loaded. + * + * @param name the package name + * @param man the Manifest containing package version and sealing + * information + * @param url the code source url for the package, or null if none + * @return the newly defined Package object + * @throws IllegalArgumentException if the package name duplicates + * an existing package either in this class loader or one + * of its ancestors + */ + protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException { + String path = name.replace('.', '/').concat("/"); + URL sealBase = null; + + Attributes entryAttr = man.getAttributes(path); + Attributes mainAttr = man.getMainAttributes(); + + String specTitle = getManifestVal(entryAttr, mainAttr, Name.SPECIFICATION_TITLE); + String specVersion = getManifestVal(entryAttr, mainAttr, Name.SPECIFICATION_VERSION); + String specVendor = getManifestVal(entryAttr, mainAttr, Name.SPECIFICATION_VENDOR); + String implTitle = getManifestVal(entryAttr, mainAttr, Name.IMPLEMENTATION_TITLE); + String implVersion = getManifestVal(entryAttr, mainAttr, Name.IMPLEMENTATION_VERSION); + String implVendor = getManifestVal(entryAttr, mainAttr, Name.IMPLEMENTATION_VENDOR); + String sealed = getManifestVal(entryAttr, mainAttr, Name.SEALED); + + if ("true".equalsIgnoreCase(sealed)) sealBase = url; + + return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); + } + + private static String getManifestVal(Attributes entryAttr, Attributes mainAttr, Name key) { + String val = null; + if (entryAttr != null) { + val = entryAttr.getValue(key); + } + if (val == null && mainAttr != null) { + val = mainAttr.getValue(key); + } + return val; + } + + + public static URLStreamHandler getDefaultURLStreamHandler(String protocol) { + +// String pkgList = (String) java.security.AccessController.doPrivileged( +// new GetPropertyAction("java.protocol.handler.pkgs", "")); + + String pkgList = System.getProperty("java.protocol.handler.pkgs", ""); + + if (pkgList.length() > 0) pkgList += "|"; + pkgList += "sun.net.www.protocol"; + + StringTokenizer tokenizer = new StringTokenizer(pkgList, "|"); + + while (tokenizer.hasMoreTokens()) { + String pkg = tokenizer.nextToken().trim(); + try { + String clname = pkg + "." + protocol + ".Handler"; + Class cls; + try { + cls = Class.forName(clname); + } catch (ClassNotFoundException e) { + cls = Class.forName(clname, false, null); + } + return (URLStreamHandler) cls.newInstance(); + } catch (Exception e) { + // ignore and try next one + } + } + return null; + } + +} Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceFinder.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceFinder.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceFinder.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceFinder.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,63 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader; + +import java.net.URL; +import java.util.Enumeration; + +/** + * Abstraction of resource searching policy. Given resource name, the resource + * finder performs implementation-specific lookup, and, if it is able to locate + * the resource, returns the {@link ResourceHandle handle(s)} or URL(s) of it. + * + * @version $Rev$ $Date$ + */ +public interface ResourceFinder { + /** + * Find the resource by name and return URL of it if found. + * + * @param name the resource name + * @return resource URL or null if resource was not found + */ + public URL findResource(String name); + + /** + * Find all resources with given name and return enumeration of their URLs. + * + * @param name the resource name + * @return enumeration of resource URLs (possibly empty). + */ + public Enumeration findResources(String name); + + /** + * Get the resource by name and, if found, open connection to it and return + * the {@link ResourceHandle handle} of it. + * + * @param name the resource name + * @return resource handle or null if resource was not found + */ + public ResourceHandle getResource(String name); + + /** + * Get all resources with given name and return enumeration of their + * {@link ResourceHandle resource handles}. + * + * @param name the resource name + * @return enumeration of resource handles (possibly empty). + */ + public Enumeration getResources(String name); +} Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceHandle.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceHandle.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceHandle.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceHandle.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,154 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.cert.Certificate; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +/** + * This class represents a handle (a connection) to some resource, which may + * be a class, native library, text file, image, etc. Handles are returned + * by {@link ResourceLoader}'s get methods. + * Having the resource handle, in addition to accessing the resource data + * (using methods {@link #getInputStream} or {@link #getBytes}) as well as + * access resource metadata, such as attributes, certificates, etc. + *

+ * As soon as the handle is no longer in use, it should be explicitly + * {@link #close}d, similarly to I/O streams. + * + * @version $Rev$ $Date$ + */ +public abstract class ResourceHandle { + + /** + * Return the name of the resource. The name is a "/"-separated path + * name that identifies the resource. + */ + public abstract String getName(); + + /** + * Returns the URL of the resource. + */ + public abstract URL getURL(); + + /** + * Returns the CodeSource URL for the class or resource. + */ + public abstract URL getCodeSourceURL(); + + /** + * Returns and InputStream for reading this resource data. + */ + public abstract InputStream getInputStream() throws IOException; + + /** + * Returns the length of this resource data, or -1 if unknown. + */ + public abstract int getContentLength(); + + /** + * Returns this resource data as an array of bytes. + */ + public byte[] getBytes() throws IOException { + byte[] buf; + InputStream in = getInputStream(); + int len = getContentLength(); + try { + if (len != -1) { + // read exactly len bytes + buf = new byte[len]; + while (len > 0) { + int read = in.read(buf, buf.length - len, len); + if (read < 0) { + throw new IOException("unexpected EOF"); + } + len -= read; + } + } else { + // read until end of stream is reached + buf = new byte[2048]; + int total = 0; + while ((len = in.read(buf, total, buf.length - total)) >= 0) { + total += len; + if (total >= buf.length) { + byte[] aux = new byte[total * 2]; + System.arraycopy(buf, 0, aux, 0, total); + buf = aux; + } + } + // trim if necessary + if (total != buf.length) { + byte[] aux = new byte[total]; + System.arraycopy(buf, 0, aux, 0, total); + buf = aux; + } + } + } finally { + in.close(); + } + return buf; + } + + /** + * Returns the Manifest of the JAR file from which this resource + * was loaded, or null if none. + */ + public Manifest getManifest() throws IOException { + return null; + } + + /** + * Return the Certificates of the resource, or null if none. + */ + public Certificate[] getCertificates() { + return null; + } + + /** + * Return the Attributes of the resource, or null if none. + */ + public Attributes getAttributes() throws IOException { + Manifest m = getManifest(); + if (m == null) return null; + String entry = getURL().getFile(); + return m.getAttributes(entry); + } + + /** + * Closes a connection to the resource indentified by this handle. Releases + * any I/O objects associated with the handle. + */ + public void close() { + } +// +// /** +// * Ensures that {@link #release()} method is eventually called when this +// * object is finalized. +// */ +// protected void finalize() throws Throwable { +// super.finalize(); +// release(); +// } + + public String toString() { + return "[" + getName() + ": " + getURL() + "; code source: " + getCodeSourceURL() + "]"; + } +} Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceLoader.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceLoader.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceLoader.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceLoader.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,854 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.security.Permission; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import org.apache.geronimo.kernel.classloader.jarcache.JarUrlStreamHandler; + +/** + * This class aids in accessing remote resources referred by URLs. + * The URLs are resolved into {@link ResourceHandle resource handles} which can + * be used to access the resources directly and uniformly, regardless of the + * URL type. The resource loader + * is particularly useful when dealing with resources fetched from JAR files. + * It maintains the cache of opened JAR files (so that so that + * subsequent requests for resources coming from the same base Jar file can be + * handled efficiently). It fully supports JAR class-path (references from + * a JAR file to other JAR files) and JAR index (JAR containing information + * about content of other JARs). The caching policy of downloaded JAR files can + * be customized via the constructor parameter jarHandler; the + * default policy is to use separate cache per each + * ResourceLoader instance. + *

+ * This class is particularly useful when implementing custom class loaders. + * It provides bottom-level resource fetching functionality. By using one of + * the loader methods which accepts an array of URLs, it + * is straightforward to implement class-path searching similar to that + * of URLClassLoader, with JAR dependencies (Class-Path) + * properly resolved and with JAR indexes properly handled. + *

+ * This class provides two set of methods: get methods that return + * {@link ResourceHandle}s (or their enumerations) and find methods that + * return URLs (or their enumerations). If the resource is not found, + * null (or empty enumeration) is returned. Resource handles represent a + * connection to the resource and they should be closed when done + * processing, just like input streams. In contrast, find methods return + * URLs that can be used to open multiple connections to the resource. In + * typical class loader applications, when a single retrieval is sufficient, + * it is preferable to use get methods since they pose slightly smaller + * communication overhead. + * + * @version $Rev$ $Date$ + */ +public class ResourceLoader { + + private static final String JAR_INDEX_ENTRY_NAME = "META-INF/INDEX.LIST"; + + final URLStreamHandler jarHandler; + + final Map url2jarInfo = new HashMap(); + + /** + * Constructs new ResourceLoadeer with default JAR caching policy, that is, + * to create and use separate cache for this ResourceLoader instance. + */ + public ResourceLoader() { + this(new JarUrlStreamHandler()); + } + + /** + * Constructs new ResourceLoader with specified JAR file handler which can + * implement custom JAR caching policy. + * + * @param jarHandler JAR file handler + */ + public ResourceLoader(URLStreamHandler jarHandler) { + this.jarHandler = jarHandler; + } + + /** + * Gets resource with given name at the given source URL. If the URL points + * to a directory, the name is the file path relative to this directory. + * If the URL points to a JAR file, the name identifies an entry in that + * JAR file. If the URL points to a JAR file, the resource is not found + * in that JAR file, and the JAR file has Class-Path attribute, the + * JAR files identified in the Class-Path are also searched for the + * resource. + * + * @param source the source URL + * @param name the resource name + * @return handle representing the resource, or null if not found + */ + public ResourceHandle getResource(URL source, String name) { + return getResource(source, name, new HashSet(), null); + } + + /** + * Gets resource with given name at the given search path. The path is + * searched iteratively, one URL at a time. If the URL points + * to a directory, the name is the file path relative to this directory. + * If the URL points to the JAR file, the name identifies an entry in that + * JAR file. If the URL points to the JAR file, the resource is not found + * in that JAR file, and the JAR file has Class-Path attribute, the + * JAR files identified in the Class-Path are also searched for the + * resource. + * + * @param sources the source URL path + * @param name the resource name + * @return handle representing the resource, or null if not found + */ + public ResourceHandle getResource(URL[] sources, String name) { + Set visited = new HashSet(); + for (int i = 0; i < sources.length; i++) { + ResourceHandle h = getResource(sources[i], name, visited, null); + if (h != null) return h; + } + return null; + } + + /** + * Gets all resources with given name at the given source URL. If the URL + * points to a directory, the name is the file path relative to this + * directory. If the URL points to a JAR file, the name identifies an entry + * in that JAR file. If the URL points to a JAR file, the resource is not + * found in that JAR file, and the JAR file has Class-Path attribute, the + * JAR files identified in the Class-Path are also searched for the + * resource. + *

+ * The search is lazy, that is, "find next resource" operation is triggered + * by calling {@link Enumeration#hasMoreElements}. + * + * @param source the source URL + * @param name the resource name + * @return enumeration of resource handles representing the resources + */ + public Enumeration getResources(URL source, String name) { + return new ResourceEnumeration(new URL[]{source}, name, false); + } + + /** + * Gets all resources with given name at the given search path. If the URL + * points to a directory, the name is the file path relative to this + * directory. If the URL points to a JAR file, the name identifies an entry + * in that JAR file. If the URL points to a JAR file, the resource is not + * found in that JAR file, and the JAR file has Class-Path attribute, the + * JAR files identified in the Class-Path are also searched for the + * resource. + *

+ * The search is lazy, that is, "find next resource" operation is triggered + * by calling {@link Enumeration#hasMoreElements}. + * + * @param sources the source URL path + * @param name the resource name + * @return enumeration of resource handles representing the resources + */ + public Enumeration getResources(URL[] sources, String name) { + return new ResourceEnumeration((URL[]) sources.clone(), name, false); + } + +// public URL[] getResolvedSearchPath(URL[] sources) { +// List path = new ArrayList(); +// for (int i=0; i + * The search is lazy, that is, "find next resource" operation is triggered + * by calling {@link Enumeration#hasMoreElements}. + * + * @param source the source URL + * @param name the resource name + * @return enumeration of URLs of the resources + */ + public Enumeration findResources(URL source, String name) { + return new ResourceEnumeration(new URL[]{source}, name, true); + } + + /** + * Finds all resources with given name at the given search path. If the URL + * points to a directory, the name is the file path relative to this + * directory. If the URL points to a JAR file, the name identifies an entry + * in that JAR file. If the URL points to a JAR file, the resource is not + * found in that JAR file, and the JAR file has Class-Path attribute, the + * JAR files identified in the Class-Path are also searched for the + * resource. + *

+ * The search is lazy, that is, "find next resource" operation is triggered + * by calling {@link Enumeration#hasMoreElements}. + * + * @param sources the source URL path + * @param name the resource name + * @return enumeration of URLs of the resources + */ + public Enumeration findResources(URL[] sources, String name) { + return new ResourceEnumeration((URL[]) sources.clone(), name, true); + } + + + private URL findResource(final URL source, String name, Set visitedJars, Set skip) { + URL url; + name = ResourceUtils.canonizePath(name); + if (isDir(source)) { + // plain resource + try { + url = new URL(source, name); + } + catch (MalformedURLException e) { + return null; + } + if (skip != null && skip.contains(url)) return null; + final URLConnection conn; + try { + conn = url.openConnection(); + if (conn instanceof HttpURLConnection) { + HttpURLConnection httpConn = (HttpURLConnection) conn; + httpConn.setRequestMethod("HEAD"); + if (httpConn.getResponseCode() >= 400) return null; + } else { + conn.getInputStream().close(); + } + } + catch (IOException e) { + return null; + } + return url; + } else { + // we deal with a JAR file here + try { + ResourceHandle rh = getJarInfo(source).getResource(name, visitedJars, skip); + return (rh != null) ? rh.getURL() : null; + } + catch (MalformedURLException e) { + return null; + } + } + + } + + /** + * Test whether given URL points to a directory. URL is deemed to point + * to a directory if has non-null "file" component ending with "/". + * + * @param url the URL to test + * @return true if the URL points to a directory, false otherwise + */ + protected static boolean isDir(URL url) { + String file = url.getFile(); + return (file != null && file.endsWith("/")); + } + + private static class JarInfo { + final ResourceLoader loader; + final URL source; // "real" jar file path + final URL base; // "jar:{base}!/" + JarFile jar; + boolean resolved; + Permission perm; + URL[] classPath; + String[] index; + Map package2url; + + JarInfo(ResourceLoader loader, URL source) throws MalformedURLException { + this.loader = loader; + this.source = source; + if (source.getProtocol().equals("jar")) { + this.base = new URL("jar", "", -1, source.getPath() + "!/", loader.jarHandler); + } else { + this.base = new URL("jar", "", -1, source + "!/", loader.jarHandler); + } + } + + public ResourceHandle getResource(String name) { + return getResource(name, new HashSet()); + } + + ResourceHandle getResource(String name, Set visited) { + return getResource(name, visited, null); + } + +// void appendToResolvedClassPath(List path) { +// path.add(source); +// URL[] classPath; +// synchronized (this) { +// classPath = this.classPath; +// } +// if (classPath == null) return; +// for (int i=0; i 0) ? name.substring(0, idx) : name; + dependencies = (URL[]) package2url.get(prefix); + } else { + // classpath might be null only if it was a dependency of + // an indexed JAR with out-of-date index (the index brought + // us here but resource was not found in the JAR). But this + // (out-of-sync index) should be captured by + // getJarFileIfPossiblyContains. + assert classPath != null; + dependencies = classPath; + } + } + + if (dependencies == null) return null; + + for (int i = 0; i < dependencies.length; i++) { + URL cpUrl = dependencies[i]; + if (visited.contains(cpUrl)) continue; + JarInfo depJInfo; + try { + depJInfo = loader.getJarInfo(cpUrl); + ResourceHandle rh = depJInfo.getResource(name, visited, skip); + if (rh != null) return rh; + } + catch (MalformedURLException e) { + // continue with other URLs + } + } + + // not found + return null; + } + + synchronized void setIndex(List newIndex) { + if (jar != null) { + // already loaded; no need for index + return; + } + if (index != null) { + // verification - previously declared content must remain there + Set violating = new HashSet(Arrays.asList(index)); + violating.removeAll(newIndex); + if (!violating.isEmpty()) { + throw new RuntimeException("Invalid JAR index: " + + "the following entries were previously declared, but " + + "they are not present in the new index: " + + violating.toString()); + } + } + this.index = (String[]) newIndex.toArray(new String[newIndex.size()]); + Arrays.sort(this.index); + } + + public JarFile getJarFileIfPossiblyContains(String name) throws IOException { + Map indexes; + synchronized (this) { + if (jar != null) { + // make sure we would be allowed to load it ourselves + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkPermission(perm); + } + + // other thread may still be updating indexes of dependent + // JAR files + try { + while (!resolved) { + wait(); + } + } + catch (InterruptedException e) { + throw new IOException("Interrupted"); + } + return jar; + } + + if (index != null) { + // we may be able to respond negatively w/o loading the JAR + int pos = name.lastIndexOf('/'); + if (pos > 0) name = name.substring(0, pos); + if (Arrays.binarySearch(index, name) < 0) return null; + } + + // load the JAR + JarURLConnection conn = (JarURLConnection) base.openConnection(); + this.perm = conn.getPermission(); + JarFile jar = conn.getJarFile(); + // conservatively check if index is accurate, that is, does not + // contain entries which are not in the JAR file + if (index != null) { + Set indices = new HashSet(Arrays.asList(index)); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String indexEntry = entry.getName(); + // for non-top, find the package name + int pos = indexEntry.lastIndexOf('/'); + if (pos > 0) { + indexEntry = indexEntry.substring(0, pos); + } + indices.remove(indexEntry); + } + if (!indices.isEmpty()) { + throw new RuntimeException("Invalid JAR index: " + + "the following entries not found in JAR: " + + indices); + } + } + this.jar = jar; + + this.classPath = parseClassPath(jar, source, loader.jarHandler); + + indexes = parseJarIndex(this.source, jar); + indexes.remove(this.source.toExternalForm()); + + if (!indexes.isEmpty()) { + this.package2url = package2url(indexes); + } + } + // just loaded the JAR - need to resolve the index + try { + for (Iterator itr = indexes.entrySet().iterator(); itr.hasNext();) { + Map.Entry entry = (Map.Entry) itr.next(); + URL url = (URL) entry.getKey(); + if (url.toExternalForm().equals(this.source.toExternalForm())) { + continue; + } + List index = (List) entry.getValue(); + loader.getJarInfo(url).setIndex(index); + } + } + finally { + synchronized (this) { + this.resolved = true; + notifyAll(); + } + } + return jar; + } + } + + private static Map package2url(Map indexes) { + Map prefix2url = new HashMap(); + for (Iterator i = indexes.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + URL url = (URL) entry.getKey(); + List idx = (List) entry.getValue(); + for (Iterator j = idx.iterator(); j.hasNext();) { + String prefix = (String) j.next(); + List prefixList = (List) prefix2url.get(prefix); + if (prefixList == null) { + prefixList = new ArrayList(); + prefix2url.put(prefix, prefixList); + } + prefixList.add(url); + } + } + + // replace lists with arrays + for (Iterator i = prefix2url.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + List list = (List) entry.getValue(); + entry.setValue(list.toArray(new URL[list.size()])); + } + return prefix2url; + } + + private JarInfo getJarInfo(URL url) throws MalformedURLException { + JarInfo jinfo; + synchronized (url2jarInfo) { + // fix: no longer use url.equals, since it distinguishes between + // "" and null in the host part of file URLs. The ""-type urls are + // correct but "null"-type ones come from file.toURI().toURL() + // on 1.4.1. (It is fixed in 1.4.2) + jinfo = (JarInfo) url2jarInfo.get(url.toExternalForm()); + if (jinfo == null) { + jinfo = new JarInfo(this, url); + url2jarInfo.put(url.toExternalForm(), jinfo); + } + } + return jinfo; + } + + private static class JarResourceHandle extends ResourceHandle { + final JarFile jar; + final JarEntry jentry; + final URL url; + final URL codeSource; + + JarResourceHandle(JarFile jar, JarEntry jentry, URL url, URL codeSource) { + this.jar = jar; + this.jentry = jentry; + this.url = url; + this.codeSource = codeSource; + } + + public String getName() { + return jentry.getName(); + } + + public URL getURL() { + return url; + } + + public URL getCodeSourceURL() { + return codeSource; + } + + public InputStream getInputStream() throws IOException { + return jar.getInputStream(jentry); + } + + public int getContentLength() { + return (int) jentry.getSize(); + } + + public Manifest getManifest() throws IOException { + return jar.getManifest(); + } + + public Attributes getAttributes() throws IOException { + return jentry.getAttributes(); + } + + public Certificate[] getCertificates() { + return jentry.getCertificates(); + } + + public void close() { + } + } + + private static Map parseJarIndex(URL cxt, JarFile jar) throws IOException { + JarEntry entry = jar.getJarEntry(JAR_INDEX_ENTRY_NAME); + if (entry == null) return Collections.EMPTY_MAP; + InputStream is = jar.getInputStream(entry); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); + + Map result = new LinkedHashMap(); + + String line; + + // skip version-info + do { + line = reader.readLine(); + } + while (line != null && line.trim().length() > 0); + + URL currentURL; + List currentList = null; + while (true) { + // skip the blank line + line = reader.readLine(); + if (line == null) { + return result; + } + + currentURL = new URL(cxt, line); + currentList = new ArrayList(); + result.put(currentURL, currentList); + + while (true) { + line = reader.readLine(); + if (line == null || line.trim().length() == 0) break; + currentList.add(line); + } + } + } + + private static URL[] parseClassPath(JarFile jar, URL source, URLStreamHandler jarHandler) throws IOException { + Manifest man = jar.getManifest(); + if (man == null) return new URL[0]; + Attributes attr = man.getMainAttributes(); + if (attr == null) return new URL[0]; + String cp = attr.getValue(Attributes.Name.CLASS_PATH); + if (cp == null) return new URL[0]; + StringTokenizer tokenizer = new StringTokenizer(cp); + List cpList = new ArrayList(); + URI sourceURI; + if ("jar".equals(source.getProtocol())) { + sourceURI = URI.create(source.getPath()); + } else { + sourceURI = URI.create(source.toString()); + } + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + try { + try { + URI uri = new URI(token); + if (!uri.isAbsolute()) { + uri = sourceURI.resolve(uri); + } + if ("jar".equals(source.getProtocol())) { + cpList.add(new URL("jar", null, -1, uri.toString(), jarHandler)); + } else { + cpList.add(uri.toURL()); + } + } + catch (URISyntaxException e) { + // tolerate malformed URIs for backward-compatibility + URL url = new URL(source, token); + cpList.add(url); + } + } + catch (MalformedURLException e) { + throw new IOException(e.getMessage()); + } + } + return (URL[]) cpList.toArray(new URL[cpList.size()]); + } + + private class ResourceEnumeration implements Enumeration { + final URL[] urls; + final String name; + final boolean findOnly; + int idx; + Object next; + Set previousURLs = new HashSet(); + + ResourceEnumeration(URL[] urls, String name, boolean findOnly) { + this.urls = urls; + this.name = name; + this.findOnly = findOnly; + this.idx = 0; + } + + public boolean hasMoreElements() { + fetchNext(); + return (next != null); + } + + public Object nextElement() { + fetchNext(); + Object next = this.next; + this.next = null; + if (next == null) throw new NoSuchElementException(); + return next; + } + + private void fetchNext() { + if (next != null) return; + while (idx < urls.length) { + Object found; + if (findOnly) { + URL url = findResource(urls[idx], name, new HashSet(), previousURLs); + if (url != null) { + previousURLs.add(url); + next = url; + return; + } + } else { + ResourceHandle h = getResource(urls[idx], name, new HashSet(), + previousURLs); + if (h != null) { + previousURLs.add(h.getURL()); + next = h; + return; + } + } + idx++; + } + } + } +} Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceUtils.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceUtils.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceUtils.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/ResourceUtils.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,139 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader; + +import java.net.URI; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility methods related to remote resource access. + * + * @version $Rev$ $Date$ + */ +public class ResourceUtils { + + private static final Pattern dotInMiddlePattern = Pattern.compile("/\\./"); + private static final Pattern dotAtBegPattern = Pattern.compile("^\\./"); + private static final Pattern dotAtEndPattern = Pattern.compile("/\\.$"); + private static final Pattern multipleSlashPattern = Pattern.compile("//+"); + private static final Pattern initialParentPattern = Pattern.compile("/?(\\.\\./)*"); + private static final Pattern embeddedParentPattern = Pattern.compile("[^/]+/\\.\\./"); + private static final Pattern trailingParentPattern = Pattern.compile("[^/]+/\\.\\.$"); + private static final Pattern initialAbsParentPattern = Pattern.compile("^/(\\.\\./)+"); + private static final Pattern initialAbsSingleParentPattern = Pattern.compile("^/\\.\\.$"); + + private ResourceUtils() { + } + + /** + * Checks if the URI points to the local file. + * + * @param uri the uri to check + * @return true if the URI points to a local file + */ + public static boolean isLocalFile(URI uri) { + if (!uri.isAbsolute()) return false; + if (uri.isOpaque()) return false; + if (!"file".equalsIgnoreCase(uri.getScheme())) return false; + if (uri.getAuthority() != null) return false; + if (uri.getFragment() != null) return false; + if (uri.getQuery() != null) return false; + if ("".equals(uri.getPath())) return false; + return true; + } + + /** + * Returns the path converted to the canonic form. Examples: + *

+     *  "/aaa/b/"                                      ->  "/aaa/b/"
+     *  "/aaa/b/c/../.."                               ->  "/aaa/"
+     *  "/aaa/../bbb/cc/./.././../dd/eee/fff/."        ->  "/dd/eee/fff/"
+     *  "../aaa/../././bbb/./../ccc/"                  ->  "../ccc/"
+     *  "aa/ddfdd/./sadfd/.././sdafa/../../.././././"  ->  ""
+     *  "./aaa/."                                      ->  "aaa/"
+     *  ".///aa//bb/"                                  ->  "aa/bb/"
+     *  "../../aaa"                                    ->  "../../aaa"
+     *  "/../../aaa"                                   ->  "/aaa"
+     * 
+ */ + public static String canonizePath(String path) { + StringBuffer buf = new StringBuffer(path); + StringBuffer aux = new StringBuffer(); + while (replaceAll(buf, aux, dotInMiddlePattern, "/", 0)) { + ; + } + replaceAll(buf, aux, multipleSlashPattern, "/", 0); + replaceFirst(buf, aux, dotAtBegPattern, "", 0); + replaceFirst(buf, aux, dotAtEndPattern, "/", 0); + + int pos = 0; + while (pos < buf.length()) { + pos += skipPrefix(buf, aux, initialParentPattern, pos); + if (!replaceFirst(buf, aux, embeddedParentPattern, "", pos)) { + break; + } + } + + replaceFirst(buf, aux, trailingParentPattern, "", pos); + + replaceFirst(buf, aux, initialAbsParentPattern, "/", 0); + replaceFirst(buf, aux, initialAbsSingleParentPattern, "/", 0); + return buf.toString(); + } + + public static boolean isAbsolute(String path) { + return (path.length() > 0 && path.charAt(0) == '/'); + } + + private static boolean replaceFirst(StringBuffer buf, StringBuffer aux, Pattern pattern, String replacement, int pos) { + boolean chg = false; + aux.setLength(0); + Matcher matcher = pattern.matcher(buf); + if (matcher.find(pos)) { + matcher.appendReplacement(aux, replacement); + chg = true; + } + matcher.appendTail(aux); + buf.setLength(0); + buf.append(aux); + return chg; + } + + private static boolean replaceAll(StringBuffer buf, StringBuffer aux, Pattern pattern, String replacement, int pos) { + aux.setLength(0); + Matcher matcher = pattern.matcher(buf); + boolean found = matcher.find(pos); + boolean chg = found; + while (found) { + matcher.appendReplacement(aux, replacement); + found = matcher.find(); + } + matcher.appendTail(aux); + buf.setLength(0); + buf.append(aux); + return chg; + } + + private static int skipPrefix(StringBuffer buf, StringBuffer aux, Pattern pattern, int pos) { + Matcher matcher = initialParentPattern.matcher(buf); + if (matcher.find(pos)) { + return matcher.end() - pos; + } + return 0; + } +} Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/URIClassLoader.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/URIClassLoader.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/URIClassLoader.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/URIClassLoader.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,430 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLStreamHandler; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.util.Enumeration; +import java.util.jar.Attributes; +import java.util.jar.Attributes.Name; +import java.util.jar.Manifest; + +/** + * Equivalent of java.net.URLClassloader but without bugs related to ill-formed + * URLs and with customizable JAR caching policy. The standard URLClassLoader + * accepts URLs containing spaces and other characters which are forbidden in + * the URI syntax, according to the RFC 2396. + * As a workaround to this problem, Java escapes and un-escapes URLs in various + * arbitrary places; however, this is inconsistent and leads to numerous + * problems with URLs referring to local files with spaces in the path. SUN + * acknowledges the problem, but refuses to modify the behavior for + * compatibility reasons; see Java Bug Parade 4273532, 4466485. + *

+ * Additionally, the JAR caching policy used by + * URLClassLoader is system-wide and inflexible: once downloaded JAR files are + * never re-downloaded, even if one creates a fresh instance of the class + * loader that happens to have the same URL in its search path. In fact, that + * policy is a security vulnerability: it is possible to crash any URL class + * loader, thus affecting potentially separate part of the system, by creating + * URL connection to one of the URLs of that class loader search path and + * closing the associated JAR file. + * See Java Bug Parade 4405789, 4388666, 4639900. + *

+ * This class avoids these problems by 1) using URIs instead of URLs for the + * search path (thus enforcing strict syntax conformance and defining precise + * escaping semantics), and 2) using custom URLStreamHandler which ensures + * per-classloader JAR caching policy. + * + * @version $Rev$ $Date$ + */ +public class URIClassLoader extends URLClassLoader { + + final URIResourceFinder finder; + final AccessControlContext acc; + + /** + * Creates URIClassLoader with the specified search path. + * + * @param uris the search path + */ + public URIClassLoader(URI[] uris) { + this(uris, (URLStreamHandler) null); + } + + /** + * Creates URIClassLoader with the specified search path. + * + * @param uris the search path + * @param jarHandler stream handler for JAR files; implements caching policy + */ + public URIClassLoader(URI[] uris, URLStreamHandler jarHandler) { + super(new URL[0]); + this.finder = new URIResourceFinder(uris, jarHandler); + this.acc = AccessController.getContext(); + } + + /** + * Creates URIClassLoader with the specified search path and parent class + * loader. + * + * @param uris the search path + * @param parent the parent class loader. + */ + public URIClassLoader(URI[] uris, ClassLoader parent) { + this(uris, parent, null); + } + + /** + * Creates URIClassLoader with the specified search path and parent class + * loader. + * + * @param uris the search path + * @param parent the parent class loader. + * @param jarHandler stream handler for JAR files; implements caching policy + */ + public URIClassLoader(URI[] uris, ClassLoader parent, URLStreamHandler jarHandler) { + super(new URL[0], parent); + this.finder = new URIResourceFinder(uris, jarHandler); + this.acc = AccessController.getContext(); + } + + /** + * Add specified URI at the end of the search path. + * + * @param uri the URI to add + */ + protected void addURI(URI uri) { + finder.addURI(uri); + } + + /** + * Add specified URL at the end of the search path. + * + * @param url the URL to add + * @deprecated use addURI + */ + protected void addURL(URL url) { + finder.addURI(URI.create(url.toExternalForm())); + } + + public URL[] getURLs() { + return (URL[]) finder.getUrls().clone(); + } + +// public URL[] getAllResolvedURLs() { +// return finder.getResolvedSearchPath(); +// } + + /** + * Finds and loads the class with the specified name. + * + * @param name the name of the class + * @return the resulting class + * @throws ClassNotFoundException if the class could not be found + */ + protected Class findClass(final String name) throws ClassNotFoundException { + try { + return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws ClassNotFoundException { + String path = name.replace('.', '/').concat(".class"); + ResourceHandle h = finder.getResource(path); + if (h != null) { + try { + return defineClass(name, h); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } else { + throw new ClassNotFoundException(name); + } + } + }, acc); + } catch (java.security.PrivilegedActionException pae) { + throw (ClassNotFoundException) pae.getException(); + } + } + + protected Class defineClass(String name, ResourceHandle h) throws IOException { + int i = name.lastIndexOf('.'); + URL url = h.getCodeSourceURL(); + if (i != -1) { // check package + String pkgname = name.substring(0, i); + // check if package already loaded + Package pkg = getPackage(pkgname); + Manifest man = h.getManifest(); + if (pkg != null) { + // package found, so check package sealing + boolean ok; + if (pkg.isSealed()) { + // verify that code source URLs are the same + ok = pkg.isSealed(url); + } else { + // make sure we are not attempting to seal the package + // at this code source URL + ok = (man == null) || !isSealed(pkgname, man); + } + if (!ok) { + throw new SecurityException("sealing violation: " + name); + } + } else { // package not yet defined + if (man != null) { + definePackage(pkgname, man, url); + } else { + definePackage(pkgname, null, null, null, null, null, null, null); + } + } + } + + // now read the class bytes and define the class + byte[] b = h.getBytes(); + java.security.cert.Certificate[] certs = h.getCertificates(); + CodeSource cs = new CodeSource(url, certs); + return defineClass(name, b, 0, b.length, cs); + } + + /** + * returns true if the specified package name is sealed according to the + * given manifest. + */ + private boolean isSealed(String name, Manifest man) { + String path = name.replace('.', '/').concat("/"); + Attributes attr = man.getAttributes(path); + String sealed = null; + if (attr != null) { + sealed = attr.getValue(Name.SEALED); + } + if (sealed == null) { + if ((attr = man.getMainAttributes()) != null) { + sealed = attr.getValue(Name.SEALED); + } + } + return "true".equalsIgnoreCase(sealed); + } + + /** + * Finds the resource with the specified name. + * + * @param name the name of the resource + * @return a URL for the resource, or null + * if the resource could not be found. + */ + public URL findResource(final String name) { + return (URL) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.findResource(name); + } + }, acc); + } + + /** + * Returns an Enumeration of URLs representing all of the resources + * having the specified name. + * + * @param name the resource name + * @return an Enumeration of URLs + * @throws IOException if an I/O exception occurs + */ + public Enumeration findResources(final String name) throws IOException { + return (Enumeration) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.findResources(name); + } + }, acc); + } + + /** + * Returns the absolute path name of a native library. The VM + * invokes this method to locate the native libraries that belong + * to classes loaded with this class loader. If this method returns + * null, the VM searches the library along the path + * specified as the java.library.path property. + * This method invoke {@link #getLibraryHandle} method to find handle + * of this library. If the handle is found and its URL protocol is "file", + * the system-dependent absolute library file path is returned. + * Otherwise this method returns null.

+ *

+ * Subclasses can override this method to provide specific approaches + * in library searching. + * + * @param libname the library name + * @return the absolute path of the native library + * @see System#loadLibrary(String) + * @see System#mapLibraryName(String) + */ + protected String findLibrary(String libname) { + ResourceHandle md = getLibraryHandle(libname); + if (md == null) return null; + URL url = md.getURL(); + if (!"file".equals(url.getProtocol())) return null; + return new File(URI.create(url.toString())).getPath(); + } +// +// /** +// * Gets the ResourceHandle object for the specified loaded class. +// * +// * @param clazz the Class +// * @return the ResourceHandle of the Class +// */ +// protected ResourceHandle getClassHandle(Class clazz) +// { +// return null; +// } + + /** + * Finds the ResourceHandle object for the class with the specified name. + * Unlike findClass(), this method does not load the class. + * + * @param name the name of the class + * @return the ResourceHandle of the class + */ + protected ResourceHandle getClassHandle(final String name) { + String path = name.replace('.', '/').concat(".class"); + return getResourceHandle(path); + } + + /** + * Finds the ResourceHandle object for the resource with the specified name. + * + * @param name the name of the resource + * @return the ResourceHandle of the resource + */ + protected ResourceHandle getResourceHandle(final String name) { + return (ResourceHandle) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.getResource(name); + } + }, acc); + } + + /** + * Finds the ResourceHandle object for the native library with the specified + * name. + * The library name must be '/'-separated path. The last part of this + * path is substituted by its system-dependent mapping (using + * {@link System#mapLibraryName(String)} method). Next, the + * ResourceFinder is used to look for the library as it + * was ordinary resource.

+ *

+ * Subclasses can override this method to provide specific approaches + * in library searching. + * + * @param name the name of the library + * @return the ResourceHandle of the library + */ + protected ResourceHandle getLibraryHandle(final String name) { + int idx = name.lastIndexOf('/'); + String path; + String simplename; + if (idx == -1) { + path = ""; + simplename = name; + } else if (idx == name.length() - 1) { // name.endsWith('/') + throw new IllegalArgumentException(name); + } else { + path = name.substring(0, idx + 1); // including '/' + simplename = name.substring(idx + 1); + } + return getResourceHandle(path + System.mapLibraryName(simplename)); + } + + /** + * Returns an Enumeration of ResourceHandle objects representing all of the + * resources having the specified name. + * + * @param name the name of the resource + * @return the ResourceHandle of the resource + */ + protected Enumeration getResourceHandles(final String name) { + return (Enumeration) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return finder.getResources(name); + } + }, acc); + } + + protected URLStreamHandler getJarHandler() { + return finder.loader.jarHandler; + } + + private static class URIResourceFinder implements ResourceFinder { + URL[] urls; + final ResourceLoader loader; + + public URIResourceFinder(URI[] uris, URLStreamHandler jarHandler) { + try { + this.loader = (jarHandler != null) + ? new ResourceLoader(jarHandler) + : new ResourceLoader(); + URL[] urls = new URL[uris.length]; + for (int i = 0; i < uris.length; i++) { + urls[i] = uris[i].toURL(); + } + this.urls = urls; + } + catch (MalformedURLException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + public synchronized void addURI(URI uri) { + try { + URL url = uri.toURL(); + int len = this.urls.length; + URL[] urls = new URL[len + 1]; + System.arraycopy(this.urls, 0, urls, 0, len); + urls[len] = url; + this.urls = urls; + } + catch (MalformedURLException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + private synchronized final URL[] getUrls() { + return urls; + } + + public ResourceHandle getResource(String name) { + return loader.getResource(getUrls(), name); + } + + public Enumeration getResources(String name) { + return loader.getResources(getUrls(), name); + } + + public URL findResource(String name) { + return loader.findResource(getUrls(), name); + } + + public Enumeration findResources(String name) { + return loader.findResources(getUrls(), name); + } +// public URL[] getResolvedSearchPath() { +// return loader.getResolvedSearchPath(getUrls()); +// } + } +} Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jar/Handler.java URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jar/Handler.java?rev=393370&view=auto ============================================================================== --- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jar/Handler.java (added) +++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jar/Handler.java Tue Apr 11 19:48:07 2006 @@ -0,0 +1,25 @@ +/** + * + * Copyright 2005 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.geronimo.kernel.classloader.jar; + +import org.apache.geronimo.kernel.classloader.jarcache.JarUrlStreamHandler; + +/** + * @version $Rev$ $Date$ + */ +public class Handler extends JarUrlStreamHandler { +}