Return-Path: Delivered-To: apmail-sling-commits-archive@www.apache.org Received: (qmail 93578 invoked from network); 25 Jan 2010 12:44:03 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 25 Jan 2010 12:44:03 -0000 Received: (qmail 73772 invoked by uid 500); 25 Jan 2010 12:44:02 -0000 Delivered-To: apmail-sling-commits-archive@sling.apache.org Received: (qmail 73716 invoked by uid 500); 25 Jan 2010 12:44:02 -0000 Mailing-List: contact commits-help@sling.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@sling.apache.org Delivered-To: mailing list commits@sling.apache.org Received: (qmail 73707 invoked by uid 99); 25 Jan 2010 12:44:02 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 25 Jan 2010 12:44:02 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 25 Jan 2010 12:43:50 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id AAC292388A1C; Mon, 25 Jan 2010 12:43:23 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r902794 [1/2] - in /sling/trunk/bundles/jcr/classloader: ./ src/main/java/org/apache/sling/jcr/classloader/internal/ src/main/java/org/apache/sling/jcr/classloader/internal/net/ Date: Mon, 25 Jan 2010 12:43:23 -0000 To: commits@sling.apache.org From: cziegeler@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20100125124323.AAC292388A1C@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: cziegeler Date: Mon Jan 25 12:43:22 2010 New Revision: 902794 URL: http://svn.apache.org/viewvc?rev=902794&view=rev Log: SLING-1316 - Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes. Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java (with props) sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java (with props) Modified: sling/trunk/bundles/jcr/classloader/pom.xml sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java Modified: sling/trunk/bundles/jcr/classloader/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/pom.xml?rev=902794&r1=902793&r2=902794&view=diff ============================================================================== --- sling/trunk/bundles/jcr/classloader/pom.xml (original) +++ sling/trunk/bundles/jcr/classloader/pom.xml Mon Jan 25 12:43:22 2010 @@ -59,11 +59,6 @@ org.apache.sling.jcr.classloader.internal.*, - org.apache.jackrabbit; - org.apache.jackrabbit.classloader; - org.apache.jackrabbit.name; - org.apache.jackrabbit.net; - org.apache.jackrabbit.util;split-package:=merge-first @@ -115,12 +110,6 @@ junit - commons-collections - commons-collections - 3.2.1 - provided - - org.osgi org.osgi.core Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java?rev=902794&view=auto ============================================================================== --- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java (added) +++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java Mon Jan 25 12:43:22 2010 @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.sling.jcr.classloader.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.cert.Certificate; +import java.util.Date; +import java.util.jar.Manifest; + +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +import org.apache.sling.jcr.classloader.internal.net.URLFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The ClassLoaderResource class represents a resource looked up + * by the {@link ClassPathEntry}s of the {@link URLRepositoryClassLoader}. + */ +class ClassLoaderResource { + + /** default log category */ + private static final Logger log = + LoggerFactory.getLogger(ClassLoaderResource.class); + + /** + * The class path entry which loaded this class loader resource + */ + private final ClassPathEntry pathEntry; + + /** + * The name of this resource. + */ + private final String name; + + /** + * The repository property providing the resource's contents. This may be + * null if the resource was loaded from a JAR/ZIP archive. + */ + private final Property resProperty; + + /** + * The class optionally loaded/defined through this resource. + * + * @see #getLoadedClass() + * @see #setLoadedClass(Class) + */ + private Class loadedClass; + + /** + * The time in milliseconds at which this resource has been loaded from + * the repository. + */ + private final long loadTime; + + /** + * Flag indicating that this resource has already been checked for expiry + * and whether it is actually expired. + * + * @see #isExpired() + */ + private boolean expired; + + /** + * Creates an instance of this class for the class path entry. + * + * @param pathEntry The {@link ClassPathEntry} of the code source of this + * class loader resource. + * @param name The path name of this resource. + * @param resProperty The Propertyproviding the content's of + * this resource. This may be null if the resource + * was loaded from an JAR or ZIP archive. + */ + /* package */ ClassLoaderResource(ClassPathEntry pathEntry, String name, + Property resProperty) { + this.pathEntry = pathEntry; + this.name = name; + this.resProperty = resProperty; + this.loadTime = System.currentTimeMillis(); + } + + /** + * Returns the {@link ClassPathEntry} which loaded this resource. + */ + protected ClassPathEntry getClassPathEntry() { + return pathEntry; + } + + /** + * Returns the name of this resource. This is the name used to find the + * resource, for example the class name or the properties file path. + */ + public String getName() { + return name; + } + + /** + * Returns the Property with which this resource is created. + */ + protected Property getProperty() { + return resProperty; + } + + /** + * Returns the time in milliseconds at which this resource has been loaded + */ + protected long getLoadTime() { + return loadTime; + } + + /** + * Returns the URL to access this resource, for example a JCR or a JAR URL. + * If the URL cannot be created from the resource data, null is + * returned. + */ + public URL getURL() { + try { + return URLFactory.createURL(getClassPathEntry().session, getPath()); + } catch (Exception e) { + log.warn("getURL: Cannot getURL for " + getPath(), e); + } + return null; + } + + /** + * Returns the URL to the code source of this entry. If there is no code + * source available, null is returned. + *

+ * This base class implementation returns the result of calling + * {@link ClassPathEntry#toURL()} on the class path entry from which this + * resource was loaded. + */ + public URL getCodeSourceURL() { + return getClassPathEntry().toURL(); + } + + /** + * Returns an InputStream to read from the resource. + *

+ * This base class implementation returns the result of calling the + * getStream() method on the resource's property or + * null if the property is not set. + */ + public InputStream getInputStream() throws RepositoryException { + return (getProperty() != null) ? getProperty().getStream() : null; + } + + /** + * Returns the size of the resource or -1 if the size cannot be found out. + *

+ * This base class implementation returns the result of calling the + * getLength() method on the resource's property or -1 if + * the property is not set. + * + * @throws RepositoryException If an error occurrs trying to find the length + * of the property. + */ + public int getContentLength() throws RepositoryException { + return (getProperty() != null) ? (int) getProperty().getLength() : -1; + } + + /** + * Returns the path of the property containing the resource. + *

+ * This base class implementation returns the absolute path of the + * resource's property. If the property is not set or if an error occurrs + * accesing the property's path, the concatentation of the class path + * entry's path and the resource's name is returned. + */ + public String getPath() { + if (getProperty() != null) { + try { + return getProperty().getPath(); + } catch (RepositoryException re) { + // fallback + log.warn("getPath: Cannot retrieve path of entry " + getName(), + re); + } + } + + // fallback if no resource property or an error accessing the path of + // the property + return getSafePath(); + } + + /** + * Returns the path of the property containing the resource by appending + * the {@link #getName() name} to the path of the class path entry to which + * this resource belongs. This path need not necessairily be the same as + * the {@link #getProperty() path of the property} but will always succeed + * as there is no repository access involved. + */ + protected String getSafePath() { + return getClassPathEntry().getPath() + getName(); + } + + /** + * Returns the time of the the last modification of the resource or -1 if + * the last modification time cannot be evaluated. + *

+ * This base class implementation returns the result of calling the + * {@link Util#getLastModificationTime(Property)} method on the resource's + * property if not null. In case of an error or if the + * property is null, -1 is returned. + */ + public long getLastModificationTime() { + if (getProperty() != null) { + try { + return Util.getLastModificationTime(getProperty()); + } catch (RepositoryException re) { + log.info("getLastModificationTime of resource property", re); + } + } + + // cannot find the resource modification time, use epoch + return -1; + } + + /** + * Returns the resource as an array of bytes + */ + public byte[] getBytes() throws IOException, RepositoryException { + InputStream in = null; + byte[] buf = null; + + log.debug("getBytes"); + + try { + in = getInputStream(); + log.debug("getInputStream() returned {}", in); + + int length = getContentLength(); + log.debug("getContentLength() returned {}", new Integer(length)); + + if (length >= 0) { + + buf = new byte[length]; + for (int read; length > 0; length -= read) { + read = in.read(buf, buf.length - length, length); + if (read == -1) { + throw new IOException("unexpected EOF"); + } + } + + } else { + + buf = new byte[1024]; + int count = 0; + int read; + + // read enlarging buffer + while ((read = in.read(buf, count, buf.length - count)) != -1) { + count += read; + if (count >= buf.length) { + byte buf1[] = new byte[count * 2]; + System.arraycopy(buf, 0, buf1, 0, count); + buf = buf1; + } + } + + // resize buffer if too big + if (count != buf.length) { + byte buf1[] = new byte[count]; + System.arraycopy(buf, 0, buf1, 0, count); + buf = buf1; + } + + } + + } finally { + + if (in != null) { + try { + in.close(); + } catch (IOException ignore) { + } + } + + } + + return buf; + } + + /** + * Returns the manifest from the jar file for this class resource. If this + * resource is not from a jar file, the method returns null, + * which is what the default implementation does. + */ + public Manifest getManifest() { + return null; + } + + /** + * Returns the certificates from the jar file for this class resource. If + * this resource is not from a jar file, the method returns + * null, which is what the default implementation does. + */ + public Certificate[] getCertificates() { + return null; + } + + /** + * Returns the Property which is used to check whether this + * resource is expired or not. + *

+ * This base class method returns the same property as returned by the + * {@link #getProperty()} method. This method may be overwritten by + * implementations as appropriate. + * + * @see #isExpired() + */ + protected Property getExpiryProperty() { + return getProperty(); + } + + /** + * Returns true if the last modification date of the expiry + * property of this resource is loaded is later than the time at which this + * resource has been loaded. If the last modification time of the expiry + * property cannot be calculated or if an error occurrs checking the expiry + * propertiy's last modification time, true is returned. + */ + public boolean isExpired() { + if (!expired) { + // creation time of version if loaded now + long currentPropTime = 0; + Property prop = getExpiryProperty(); + if (prop != null) { + try { + currentPropTime = Util.getLastModificationTime(prop); + } catch (RepositoryException re) { + // cannot get last modif time from property, use current time + log.debug("expireResource: Cannot get current version for " + + toString() + ", will expire", re); + currentPropTime = System.currentTimeMillis(); + } + } + + // creation time of version currently loaded + long loadTime = getLoadTime(); + + // expire if a new version would be loaded + expired = currentPropTime > loadTime; + if (expired && log.isDebugEnabled()) { + log.debug( + "expireResource: Resource created {} superceded by version created {}", + new Date(loadTime), new Date(currentPropTime)); + } + } + + return expired; + } + + /** + * Returns the class which was loaded through this resource. It is expected + * that the class loader sets the class which was loaded through this + * resource by calling the {@link #setLoadedClass(Class)} method. If this + * class was not used to load a class or if the class loader failed to + * set the class loaded through this resoource, this method will return + * null. + * + * @return The class loaded through this resource, which may be + * null if this resource was never used to load a class + * or if the loader failed to set class through the + * {@link #setLoadedClass(Class)} method. + * + * @see #setLoadedClass(Class) + */ + public Class getLoadedClass() { + return loadedClass; + } + + /** + * Sets the class which was loaded through this resource. This method does + * not check, whether it is plausible that this class was actually loaded + * from this resource, nor does this method check whether the class object + * is null or not. + * + * @param loadedClass The class to be loaded. + */ + public void setLoadedClass(Class loadedClass) { + this.loadedClass = loadedClass; + } + + /** + * Returns the String representation of this resource. + */ + public String toString() { + final StringBuilder buf = new StringBuilder(getClass().getName()); + buf.append(": path="); + buf.append(getSafePath()); + buf.append(", name="); + buf.append(getName()); + return buf.toString(); + } +} Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java ------------------------------------------------------------------------------ svn:keywords = author date id revision rev url Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java?rev=902794&view=auto ============================================================================== --- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java (added) +++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java Mon Jan 25 12:43:22 2010 @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.sling.jcr.classloader.internal; + +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessControlException; + +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.sling.jcr.classloader.internal.net.URLFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The ClassPathEntry class encapsulates entries in the class path + * of the {@link DynamicRepositoryClassLoader}. The main task is to retrieve + * {@link ClassLoaderResource} instances for classes or resources to load from it. + *

+ * This implementation is not currently integrated with Java security. That is + * protection domains and security managers are not supported yet. + *

+ * This class is not intended to be subclassed or instantiated by clients. + */ +public final class ClassPathEntry { + + /** default logging */ + private static final Logger log = + LoggerFactory.getLogger(ClassPathEntry.class); + + /** The session assigned to this class path entry */ + protected final Session session; + + /** The path to the item of this class path entry */ + protected final String path; + + /** The base URL for the class path entry to later construct resource URLs */ + protected URL baseURL; + + //---------- construction -------------------------------------------------- + + /** + * Creates an instance of the ClassPathEntry assigning the + * session and path. + * + * @param session The Session to access the Repository. + * @param path The path of the class path entry, this is either the + * path of a node containing a jar archive or is the path + * of the root of a hierarchy to look up resources in. + */ + protected ClassPathEntry(Session session, String path) { + this.path = path; + this.session = session; + } + + /** + * Clones this instance of the ClassPathEntry setting the + * path and session to the same value as the base instance. + *

+ * Note that this constructor does not duplicate the session from the base + * instance. + * + * @param base The ClassPathEntry from which to copy the path + * and the session. + */ + protected ClassPathEntry(ClassPathEntry base) { + this.path = base.path; + this.session = base.session; + this.baseURL = base.baseURL; + } + + /** + * Returns an instance of the ClassPathEntry class. This + * instance will be a subclass correctly handling the type (directory or + * jar archive) of class path entry is to be created. + *

+ * If the path given has a trailing slash, it is taken as a directory root + * else the path is first tested, whether it contains an archive. If not + * the path is treated as a directory. + * + * @param session The Session to access the Repository. + * @param path The path of the class path entry, this is either the + * path of a node containing a jar archive or is the path + * of the root of a hierharchy to look up resources in. + * + * @return An initialized ClassPathEntry instance for the + * path or null if an error occurred creating the + * instance. + */ + static ClassPathEntry getInstance(Session session, String path) { + + // check we can access the path, don't care about content now + try { + session.checkPermission(path, "read"); + } catch (AccessControlException ace) { + log.warn( + "getInstance: Access denied reading from {}, ignoring entry", + path); + return null; + } catch (RepositoryException re) { + log.error("getInstance: Cannot check permission to " + path, re); + } + + if (!path.endsWith("/")) { + // assume the path designates a directory + // append trailing slash now + path += "/"; + } + + // we assume a directory class path entry, but we might have to check + // whether the path refers to a node or not. On the other hande, this + // class path entry will not be usable anyway if not, user beware :-) + + return new ClassPathEntry(session, path); + } + + /** + * Returns the path on which this ClassPathEntry is based. + */ + public String getPath() { + return path; + } + + /** + * Returns this ClassPathEntry represented as an URL to be + * used in a list of URLs to further work on. If there is a problem creating + * the URL for this instance, null is returned instead. + */ + public URL toURL() { + if (baseURL == null) { + try { + baseURL = URLFactory.createURL(session, path); + } catch (MalformedURLException mue) { + log.warn("DirectoryClassPathEntry: Creating baseURl for " + + path, mue); + } + } + + return baseURL; + } + + /** + * Returns a {@link ClassLoaderResource} for the named resource if it + * can befound below this directory root identified by the path given + * at construction time. Note that if the page would exist but does + * either not contain content or is not readable by the current session, + * no resource is returned. + * + * @param name The name of the resource to return. If the resource would + * be a class the name must already be modified to denote a valid + * path, that is dots replaced by dashes and the .class + * extension attached. + * + * @return The {@link ClassLoaderResource} identified by the name or + * null if no resource is found for that name. + */ + public ClassLoaderResource getResource(final String name) { + + try { + final Property prop = Util.getProperty(session.getItem(path + name)); + if (prop != null) { + return new ClassLoaderResource(this, name, prop); + } + + log.debug("getResource: resource {} not found below {} ", name, + path); + + } catch (PathNotFoundException pnfe) { + + log.debug("getResource: Classpath entry {} does not have resource {}", + path, name); + + } catch (RepositoryException cbe) { + + log.warn("getResource: problem accessing the resource {} below {}", + new Object[] { name, path }, cbe); + + } + // invariant : no page or problem accessing the page + + return null; + } + + /** + * Returns a ClassPathEntry with the same configuration as + * this ClassPathEntry. + *

+ * Becase the DirectoryClassPathEntry class does not have + * internal state, this method returns this instance to be used as + * the "copy". + */ + ClassPathEntry copy() { + return this; + } + + /** + * @see Object#toString() + */ + public String toString() { + StringBuilder buf = new StringBuilder(super.toString()); + buf.append(": path: "); + buf.append(path); + buf.append(", user: "); + buf.append(session.getUserID()); + return buf.toString(); + } + + //----------- internal helper ---------------------------------------------- + +} Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java ------------------------------------------------------------------------------ svn:keywords = author date id revision rev url Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java?rev=902794&view=auto ============================================================================== --- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java (added) +++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java Mon Jan 25 12:43:22 2010 @@ -0,0 +1,660 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.sling.jcr.classloader.internal; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; +import javax.jcr.observation.ObservationManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The DynamicRepositoryClassLoader class extends the + * {@link org.apache.sling.jcr.classloader.internal.URLRepositoryClassLoader} and provides the + * functionality to load classes and resources from the JCR Repository. + * Additionally, this class supports the notion of getting 'dirty', which means, + * that if a resource loaded through this class loader has been modified in the + * Repository, this class loader marks itself dirty, which flag can get + * retrieved. This helps the user of this class loader to decide on whether to + * {@link #reinstantiate(Session, ClassLoader) reinstantiate} it or continue + * using this class loader. + *

+ * When a user of the class loader recognizes an instance to be dirty, it can + * easily be reinstantiated with the {@link #reinstantiate} method. This + * reinstantiation will also rebuild the internal real class path from the same + * list of path patterns as was used to create the internal class path for the + * original class loader. The resulting internal class path need not be the + * same, though. + *

+ * As an additional feature the class loaders provides the functionality for + * complete reconfiguration of the list of path patterns defined at class loader + * construction time through the {@link #reconfigure(String[])} method. This + * reconfiguration replaces the internal class path with a new one built from + * the new path list and also replaces that path list. Reinstantiating a + * reconfigured class loader gets a class loader containing the same path list + * as the original class loader had after reconfiguration. That is the original + * configuration is lost. While reconfiguration is not able to throw away + * classes already loaded, it will nevertheless mark the class loader dirty, if + * any classes have already been loaded through it. + *

+ * This class is not intended to be extended by clients. + */ +public class DynamicRepositoryClassLoader + extends URLRepositoryClassLoader + implements EventListener { + + /** default log category */ + private final Logger log = + LoggerFactory.getLogger(this.getClass().getName()); + + /** + * Cache of resources used to check class loader expiry. The map is indexed + * by the paths of the expiry properties of the cached resources. This map + * is not complete in terms of resources which have been loaded through this + * class loader. That is for resources loaded through an archive class path + * entry, only one of those resources (the last one loaded) is kept in this + * cache, while the others are ignored. + * + * @see #onEvent(EventIterator) + * @see #findClassLoaderResource(String) + */ + private Map modTimeCache; + + /** + * Flag indicating whether there are loaded classes which have later been + * expired (e.g. invalidated or modified) + */ + private boolean dirty; + + /** + * The list of repositories added through either the {@link #addURL} or the + * {@link #addHandle} method. + */ + private ClassPathEntry[] addedRepositories; + + private EventListener[] proxyListeners; + + /** + * Creates a DynamicRepositoryClassLoader from a list of item + * path strings containing globbing pattens for the paths defining the + * class path. + * + * @param session The Session to use to access the class items. + * @param classPath The list of path strings making up the (initial) class + * path of this class loader. The strings may contain globbing + * characters which will be resolved to build the actual class path. + * @param parent The parent ClassLoader, which may be + * null. + * + * @throws NullPointerException if either the session or the handles list + * is null. + */ + public DynamicRepositoryClassLoader(Session session, + String[] classPath, ClassLoader parent) { + + // initialize the super class with an empty class path + super(session, classPath, parent); + + // set fields + dirty = false; + modTimeCache = new HashMap(); + + // register with observation service and path pattern list + registerModificationListener(); + + log.debug("DynamicRepositoryClassLoader: {} ready", this); + } + + /** + * Creates a DynamicRepositoryClassLoader with the same + * configuration as the given DynamicRepositoryClassLoader. + * This constructor is used by the {@link #reinstantiate} method. + *

+ * Before returning from this constructor the old class loader + * is destroyed and may not be used any more. + * + * @param session The session to associate with this class loader. + * @param old The DynamicRepositoryClassLoader to copy the + * cofiguration from. + * @param parent The parent ClassLoader, which may be + * null. + */ + private DynamicRepositoryClassLoader(Session session, + DynamicRepositoryClassLoader old, ClassLoader parent) { + + // initialize the super class with an empty class path + super(session, old.getPaths(), parent); + + // set the configuration and fields + dirty = false; + modTimeCache = new HashMap(); + + // create a repository from the handles - might get a different one + setRepository(resetClassPathEntries(old.getRepository())); + setAddedRepositories(resetClassPathEntries(old.getAddedRepositories())); + buildRepository(); + + // register with observation service and path pattern list + registerModificationListener(); + + // finally finalize the old class loader + old.destroy(); + + log.debug( + "DynamicRepositoryClassLoader: Copied {}. Do not use that anymore", + old); + } + + /** + * Destroys this class loader. This process encompasses all steps needed + * to remove as much references to this class loader as possible. + *

+ * NOTE: This method just clears all internal fields and especially + * the class path to render this class loader unusable. + *

+ * This implementation does not throw any exceptions. + */ + public void destroy() { + // we expect to be called only once, so we stop destroyal here + if (isDestroyed()) { + log.debug("Instance is already destroyed"); + return; + } + + // remove ourselves as listeners from other places + unregisterListener(); + + addedRepositories = null; + + super.destroy(); + } + + //---------- reload support ------------------------------------------------ + + /** + * Checks whether this class loader already loaded the named resource and + * would load another version if it were instructed to do so. As a side + * effect the class loader sets itself dirty in this case. + *

+ * Calling this method yields the same result as calling + * {@link #shouldReload(String, boolean)} with the force + * argument set to false. + * + * @param name The name of the resource to check. + * + * @return true if the resource is loaded and reloading would + * take another version than currently loaded. + * + * @see #isDirty + */ + public synchronized boolean shouldReload(String name) { + return shouldReload(name, false); + } + + /** + * Checks whether this class loader already loaded the named resource and + * whether the class loader should be set dirty depending on the + * force argument. If the argument is true, the + * class loader is marked dirty and true is returned if the + * resource has been loaded, else the loaded resource is checked for expiry + * and the class loader is only set dirty if the loaded resource has + * expired. + * + * @param name The name of the resource to check. + * @param force true if the class loader should be marked dirty + * if the resource is loaded, else the class loader is only marked + * dirty if the resource is loaded and has expired. + * + * @return true if the resource is loaded and + * force is true or if the resource has + * expired. true is also returned if this class loader + * has already been destroyed. + * + * @see #isDirty + */ + public synchronized boolean shouldReload(String name, boolean force) { + if (isDestroyed()) { + log.warn("Classloader already destroyed, reload required"); + return true; + } + + ClassLoaderResource res = getCachedResource(name); + if (res != null) { + log.debug("shouldReload: Expiring cache entry {}", res); + if (force) { + log.debug("shouldReload: Forced dirty flag"); + dirty = true; + return true; + } + + return expireResource(res); + } + + return false; + } + + /** + * Returns true if any of the loaded classes need reload. Also + * sets this class loader dirty. If the class loader is already set dirty + * or if this class loader has been destroyed before calling this method, + * it returns immediately. + * + * @return true if any class loader needs to be reinstantiated. + * + * @see #isDirty + */ + public synchronized boolean shouldReload() { + + // check whether we are already dirty + if (isDirty()) { + log.debug("shouldReload: Dirty, need reload"); + return true; + } + + // Check whether any class has changed + for (Iterator iter = getCachedResources(); iter.hasNext();) { + if (expireResource((ClassLoaderResource) iter.next())) { + log.debug("shouldReload: Found expired resource, need reload"); + return true; + } + } + + // No changes, no need to reload + log.debug("shouldReload: No expired resource found, no need to reload"); + return false; + } + + /** + * Returns whether the class loader is dirty. This can be the case if any + * of the {@link #shouldReload(String)} or {@link #shouldReload()} + * methods returned true or if a loaded class has been expired + * through the observation. + *

+ * This method may also return true if the Session + * associated with this class loader is not valid anymore. + *

+ * Finally the method always returns true if the class loader + * has already been destroyed. Note, however, that a destroyed class loader + * cannot be reinstantiated. See {@link #reinstantiate(Session, ClassLoader)}. + *

+ * If the class loader is dirty, it should be reinstantiated through the + * {@link #reinstantiate} method. + * + * @return true if the class loader is dirty and needs + * reinstantiation. + */ + public boolean isDirty() { + return isDestroyed() || dirty || !getSession().isLive(); + } + + /** + * Reinstantiates this class loader. That is, a new ClassLoader with no + * loaded class is created with the same configuration as this class loader. + *

+ * When the new class loader is returned, this class loader has been + * destroyed and may not be used any more. + * + * @param parent The parent ClassLoader for the reinstantiated + * DynamicRepositoryClassLoader, which may be + * null. + * + * @return a new instance with the same configuration as this class loader. + * + * @throws IllegalStateException if this + * {@link DynamicRepositoryClassLoader} has already been destroyed + * through the {@link #destroy()} method. + */ + public DynamicRepositoryClassLoader reinstantiate(Session session, ClassLoader parent) { + log.debug("reinstantiate: Copying {} with parent {}", this, parent); + + if (isDestroyed()) { + throw new IllegalStateException("Destroyed class loader cannot be recreated"); + } + + // create the new loader + DynamicRepositoryClassLoader newLoader = + new DynamicRepositoryClassLoader(session, this, parent); + + // return the new loader + return newLoader; + } + + //---------- URLClassLoader overwrites ------------------------------------- + + /** + * Reconfigures this class loader with the pattern list. That is the new + * pattern list completely replaces the current pattern list. This new + * pattern list will also be used later to configure the reinstantiated + * class loader. + *

+ * If this class loader already has loaded classes using the old, replaced + * path list, it is set dirty. + *

+ * If this class loader has already been destroyed, this method has no + * effect. + * + * @param classPath The list of path strings making up the (initial) class + * path of this class loader. The strings may contain globbing + * characters which will be resolved to build the actual class path. + */ + public void reconfigure(String[] classPath) { + if (log.isDebugEnabled()) { + log.debug("reconfigure: Reconfiguring the with {}", + Arrays.asList(classPath)); + } + + // whether the loader is destroyed + if (isDestroyed()) { + log.warn("Cannot reconfigure this destroyed class loader"); + return; + } + + // assign new path and register + setPaths(classPath); + buildRepository(); + + dirty = !hasLoadedResources(); + log.debug("reconfigure: Class loader is dirty now: {}", (isDirty() + ? "yes" + : "no")); + } + + //---------- RepositoryClassLoader overwrites ----------------------------- + + /** + * Calls the base class implementation to actually retrieve the resource. + * If the resource could be found and provides a non-null + * {@link ClassLoaderResource#getExpiryProperty() expiry property}, the + * resource is registered with an internal cache to check with when + * a repository modification is observed in {@link #onEvent(EventIterator)}. + * + * @param name The name of the resource to be found + * + * @return the {@link ClassLoaderResource} found for the name or + * null if no such resource is available in the class + * path. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + /* package */ ClassLoaderResource findClassLoaderResource(String name) { + // call the base class implementation to actually search for it + ClassLoaderResource res = super.findClassLoaderResource(name); + + // if it could be found, we register it with the caches + if (res != null) { + // register the resource in the expiry map, if an appropriate + // property is available + Property prop = res.getExpiryProperty(); + if (prop != null) { + try { + modTimeCache.put(prop.getPath(), res); + } catch (RepositoryException re) { + log.warn("Cannot register the resource " + res + + " for expiry", re); + } + } + } + + // and finally return the resource + return res; + } + + /** + * Builds the repository list from the list of path patterns and appends + * the path entries from any added handles. This method may be used multiple + * times, each time replacing the currently defined repository list. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + protected synchronized void buildRepository() { + super.buildRepository(); + + // add added repositories + ClassPathEntry[] addedPath = getAddedRepositories(); + if (addedPath != null && addedPath.length > 0) { + ClassPathEntry[] oldClassPath = getRepository(); + ClassPathEntry[] newClassPath = + new ClassPathEntry[oldClassPath.length + addedPath.length]; + + System.arraycopy(oldClassPath, 0, newClassPath, 0, + oldClassPath.length); + System.arraycopy(addedPath, 0, newClassPath, oldClassPath.length, + addedPath.length); + + setRepository(newClassPath); + } + } + + //---------- ModificationListener interface ------------------------------- + + /** + * Handles a repository item modifcation events checking whether a class + * needs to be expired. As a side effect, this method sets the class loader + * dirty if a loaded class has been modified in the repository. + * + * @param events The iterator of repository events to be handled. + */ + public void onEvent(EventIterator events) { + while (events.hasNext()) { + Event event = events.nextEvent(); + String path; + try { + path = event.getPath(); + } catch (RepositoryException re) { + log.warn("onEvent: Cannot get path of event, ignoring", re); + continue; + } + + log.debug( + "onEvent: Item {} has been modified, checking with cache", path); + + ClassLoaderResource resource = (ClassLoaderResource) modTimeCache.get(path); + if (resource != null) { + log.debug("pageModified: Expiring cache entry {}", resource); + expireResource(resource); + } else { + // might be in not-found cache - remove from there + if (event.getType() == Event.NODE_ADDED + || event.getType() == Event.PROPERTY_ADDED) { + log.debug("pageModified: Clearing not-found cache for possible new class"); + cleanCache(); + } + } + + } + } + + //----------- Object overwrite --------------------------------------------- + + /** + * Returns a string representation of this class loader. + */ + public String toString() { + if (isDestroyed()) { + return super.toString(); + } + + StringBuilder buf = new StringBuilder(super.toString()); + buf.append(", dirty: "); + buf.append(isDirty()); + return buf.toString(); + } + + //---------- internal ------------------------------------------------------ + + /** + * Sets the list of class path entries to add to the class path after + * reconfiguration or reinstantiation. + * + * @param addedRepositories The list of class path entries to keep for + * readdition. + */ + protected void setAddedRepositories(ClassPathEntry[] addedRepositories) { + this.addedRepositories = addedRepositories; + } + + /** + * Returns the list of added class path entries to readd them to the class + * path after reconfiguring the class loader. + */ + protected ClassPathEntry[] getAddedRepositories() { + return addedRepositories; + } + + /** + * Adds the class path entry to the current class path list. If the class + * loader has already been destroyed, this method creates a single entry + * class path list with the new class path entry. + *

+ * Besides adding the entry to the current class path, it is also added to + * the list to be readded after reconfiguration and/or reinstantiation. + * + * @see #getAddedRepositories() + * @see #setAddedRepositories(ClassPathEntry[]) + */ + protected void addClassPathEntry(ClassPathEntry cpe) { + super.addClassPathEntry(cpe); + + // add the repsitory to the list of added repositories + ClassPathEntry[] oldClassPath = getAddedRepositories(); + ClassPathEntry[] newClassPath = addClassPathEntry(oldClassPath, cpe); + setAddedRepositories(newClassPath); + } + + /** + * Registers this class loader with the observation service to get + * information on page updates in the class path and to the path + * pattern list to get class path updates. + * + * @throws NullPointerException if this class loader has already been + * destroyed. + */ + private final void registerModificationListener() { + log.debug("registerModificationListener: Registering to the observation service"); + + final String[] paths = this.getPaths(); + this.proxyListeners = new EventListener[this.getPaths().length]; + for(int i=0; i < paths.length; i++ ) { + final String path = paths[i]; + try { + final EventListener listener = new ProxyEventListener(this); + final ObservationManager om = getSession().getWorkspace().getObservationManager(); + om.addEventListener(listener, 255, path, true, null, null, false); + proxyListeners[i] = listener; + } catch (RepositoryException re) { + log.error("registerModificationListener: Cannot register " + + this + " with observation manager", re); + } + } + } + + /** + * Removes this instances registrations from the observation service and + * the path pattern list. + * + * @throws NullPointerException if this class loader has already been + * destroyed. + */ + private final void unregisterListener() { + log.debug("registerModificationListener: Deregistering from the observation service"); + if ( this.proxyListeners != null ) { + for(final EventListener listener : this.proxyListeners) { + if ( listener != null ) { + try { + final ObservationManager om = getSession().getWorkspace().getObservationManager(); + om.removeEventListener(listener); + } catch (RepositoryException re) { + log.error("unregisterListener: Cannot unregister " + + this + " from observation manager", re); + } + } + } + this.proxyListeners = null; + } + } + + /** + * Checks whether the page backing the resource has been updated with a + * version, such that this new version would be used to access the resource. + * In this case the resource has expired and the class loader needs to be + * set dirty. + * + * @param resource The ClassLoaderResource to check for + * expiry. + */ + private boolean expireResource(ClassLoaderResource resource) { + + // check whether the resource is expired (only if a class has been loaded) + boolean exp = resource.getLoadedClass() != null && resource.isExpired(); + + // update dirty flag accordingly + dirty |= exp; + log.debug("expireResource: Loader dirty: {}", new Boolean(isDirty())); + + // return the expiry status + return exp; + } + + /** + * Returns the list of classpath entries after resetting each of them. + * + * @param list The list of {@link ClassPathEntry}s to reset + * + * @return The list of reset {@link ClassPathEntry}s. + */ + private ClassPathEntry[] resetClassPathEntries( + ClassPathEntry[] oldClassPath) { + if (oldClassPath != null) { + for (int i=0; i < oldClassPath.length; i++) { + ClassPathEntry entry = oldClassPath[i]; + log.debug("resetClassPathEntries: Cloning {}", entry); + oldClassPath[i] = entry.copy(); + } + } else { + log.debug("resetClassPathEntries: No list to reset"); + } + return oldClassPath; + } + + protected final static class ProxyEventListener implements EventListener { + + private final EventListener delegatee; + + public ProxyEventListener(final EventListener delegatee) { + this.delegatee = delegatee; + } + /** + * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator) + */ + public void onEvent(EventIterator events) { + this.delegatee.onEvent(events); + } + } +} Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:keywords = author date id revision rev url Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java?rev=902794&r1=902793&r2=902794&view=diff ============================================================================== --- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java (original) +++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java Mon Jan 25 12:43:22 2010 @@ -25,7 +25,6 @@ import javax.jcr.RepositoryException; -import org.apache.jackrabbit.classloader.DynamicRepositoryClassLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; Added: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java?rev=902794&view=auto ============================================================================== --- sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java (added) +++ sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java Mon Jan 25 12:43:22 2010 @@ -0,0 +1,795 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.sling.jcr.classloader.internal; + +import java.beans.Introspector; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.sling.jcr.classloader.internal.net.JCRURLConnection; +import org.apache.sling.jcr.classloader.internal.net.URLFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The RepositoryClassLoader class extends the + * URLClassLoader and provides the functionality to load classes + * and resources from JCR Repository. + *

+ * This class loader supports loading classes from the Repository hierarchy, + * such as a classes 'folder', but also from Jar and Zip files stored + * in the Repository. + *

+ * For enhanced performance, this class loader keeps a list of resources and + * classes which have already been loaded through this class loader. If later + * requests ask for already cached resources, these are returned without + * checking whether the underlying repository actually still exists. + *

+ */ +public class URLRepositoryClassLoader extends URLClassLoader { + + /** default log category */ + private final Logger log = + LoggerFactory.getLogger(this.getClass().getName()); + + /** An empty list of url paths to call superclass constructor */ + private static final URL[] NULL_PATH = {}; + + /** + * The special resource representing a resource which could not be + * found in the class path. + * + * @see #cache + * @see #findClassLoaderResource(String) + */ + /* package */ static final ClassLoaderResource NOT_FOUND_RESOURCE = + new ClassLoaderResource(null, "[sentinel]", null) { + public boolean isExpired() { + return false; + } + }; + + /** + * The classpath which this classloader searches for class definitions. + * Each element of the vector should be either a directory, a .zip + * file, or a .jar file. + *

+ * It may be empty when only system classes are controlled. + */ + private ClassPathEntry[] repository; + + /** + * The list of paths to use as a classpath. + */ + private String[] paths; + + /** + * The Session grants access to the Repository to access the + * resources. + *

+ * This field is not final such that it may be cleared when the class loader + * is destroyed. + */ + private Session session; + + /** + * Cache of resources found or not found in the class path. The map is + * indexed by resource name and contains mappings to instances of the + * {@link ClassLoaderResource} class. If a resource has been tried to be + * loaded, which could not be found, the resource is cached with the + * special mapping to {@link #NOT_FOUND_RESOURCE}. + * + * @see #NOT_FOUND_RESOURCE + * @see #findClassLoaderResource(String) + */ + private Map cache; + + /** + * Flag indicating whether the {@link #destroy()} method has already been + * called (true) or not (false) + */ + private boolean destroyed; + + /** + * Creates a RepositoryClassLoader from a list of item path + * strings containing globbing pattens for the paths defining the class + * path. + * + * @param session The Session to use to access the class items. + * @param classPath The list of path strings making up the (initial) class + * path of this class loader. The strings may contain globbing + * characters which will be resolved to build the actual class path. + * @param parent The parent ClassLoader, which may be + * null. + * + * @throws NullPointerException if either the session or the handles list is + * null. + */ + public URLRepositoryClassLoader(Session session, String[] classPath, + ClassLoader parent) { + // initialize the super class with an empty class path + super(NULL_PATH, parent); + + // check session and handles + if (session == null) { + throw new NullPointerException("session"); + } + if (classPath == null || classPath.length == 0) { + throw new NullPointerException("handles"); + } + + // set fields + this.session = session; + setPaths(classPath); + this.cache = new HashMap(); + this.destroyed = false; + + // build the class repositories list + buildRepository(); + + log.debug("RepositoryClassLoader: {} ready", this); + } + + /** + * Returns true if this class loader has already been destroyed + * by calling {@link #destroy()}. + */ + protected boolean isDestroyed() { + return destroyed; + } + + protected String[] getPaths() { + return this.paths; + } + + protected void setPaths(final String[] classPath) { + this.paths = classPath; + } + + /** + * Destroys this class loader. This process encompasses all steps needed + * to remove as much references to this class loader as possible. + *

+ * NOTE: This method just clears all internal fields and especially + * the class path to render this class loader unusable. + *

+ * This implementation does not throw any exceptions. + */ + public void destroy() { + // we expect to be called only once, so we stop destroyal here + if (isDestroyed()) { + log.debug("Instance is already destroyed"); + return; + } + + // set destroyal guard + destroyed = true; + + // clear caches and references + setRepository(null); + setPaths(null); + session = null; + + // clear the cache of loaded resources and flush cached class + // introspections of the JavaBean framework + if (cache != null) { + final Iterator ci = cache.values().iterator(); + while ( ci.hasNext() ) { + final ClassLoaderResource res = ci.next(); + if (res.getLoadedClass() != null) { + Introspector.flushFromCaches(res.getLoadedClass()); + res.setLoadedClass(null); + } + } + cache.clear(); + } + } + + //---------- URLClassLoader overwrites ------------------------------------- + + /** + * Finds and loads the class with the specified name from the class path. + * + * @param name the name of the class + * @return the resulting class + * + * @throws ClassNotFoundException If the named class could not be found or + * if this class loader has already been destroyed. + */ + protected Class findClass(final String name) throws ClassNotFoundException { + + if (isDestroyed()) { + throw new ClassNotFoundException(name + " (Classloader destroyed)"); + } + + log.debug("findClass: Try to find class {}", name); + + try { + return (Class) AccessController + .doPrivileged(new PrivilegedExceptionAction() { + + public Object run() throws ClassNotFoundException { + return findClassPrivileged(name); + } + }); + } catch (java.security.PrivilegedActionException pae) { + throw (ClassNotFoundException) pae.getException(); + } + } + + /** + * Finds the resource with the specified name on the search path. + * + * @param name the name of the resource + * + * @return a URL for the resource, or null + * if the resource could not be found or if the class loader has + * already been destroyed. + */ + public URL findResource(String name) { + + if (isDestroyed()) { + log.warn("Destroyed class loader cannot find a resource"); + return null; + } + + log.debug("findResource: Try to find resource {}", name); + + ClassLoaderResource res = findClassLoaderResource(name); + if (res != null) { + log.debug("findResource: Getting resource from {}, created {}", + res, new Date(res.getLastModificationTime())); + return res.getURL(); + } + + return null; + } + + /** + * Returns an Enumeration of URLs representing all of the resources + * on the search path having the specified name. + * + * @param name the resource name + * + * @return an Enumeration of URLs. This is an + * empty enumeration if no resources are found by this class loader + * or if this class loader has already been destroyed. + */ + public Enumeration findResources(String name) { + + if (isDestroyed()) { + log.warn("Destroyed class loader cannot find resources"); + return new Enumeration() { + public boolean hasMoreElements() { + return false; + } + public Object nextElement() { + throw new NoSuchElementException("No Entries"); + } + }; + } + + log.debug("findResources: Try to find resources for {}", name); + + List list = new LinkedList(); + for (int i=0; i < repository.length; i++) { + final ClassPathEntry cp = repository[i]; + log.debug("findResources: Trying {}", cp); + + ClassLoaderResource res = cp.getResource(name); + if (res != null) { + log.debug("findResources: Adding resource from {}, created {}", + res, new Date(res.getLastModificationTime())); + URL url = res.getURL(); + if (url != null) { + list.add(url); + } + } + + } + + // return the enumeration on the list + return Collections.enumeration(list); + } + + /** + * Returns the search path of URLs for loading classes and resources. + * This includes the original list of URLs specified to the constructor, + * along with any URLs subsequently appended by the {@link #addURL(URL)} + * and {@link #addHandle(String)} methods. + * + * @return the search path of URLs for loading classes and resources. The + * list is empty, if this class loader has already been destroyed. + * @see java.net.URLClassLoader#getURLs() + */ + public URL[] getURLs() { + if (isDestroyed()) { + log.warn("Destroyed class loader has no URLs any more"); + return new URL[0]; + } + + List urls = new ArrayList(); + for (int i=0; i < repository.length; i++) { + URL url = repository[i].toURL(); + if (url != null) { + urls.add(url); + } + } + return (URL[]) urls.toArray(new URL[urls.size()]); + } + + /** + * Appends the specified URL to the list of URLs to search for + * classes and resources. Only Repository URLs with the protocol set to + * JCR are considered for addition. The system will find out + * whether the URL points to a directory or a jar archive. + *

+ * URLs added using this method will be preserved through reconfiguration + * and reinstantiation. + *

+ * If this class loader has already been destroyed this method has no + * effect. + * + * @param url the JCR URL to be added to the search path of + * URLs. + * @see java.net.URLClassLoader#addURL(java.net.URL) + */ + protected void addURL(URL url) { + if (isDestroyed()) { + log.warn("Cannot add URL to destroyed class loader"); + + } else if (checkURL(url)) { + // Repository URL + log.debug("addURL: Adding URL {}", url); + try { + JCRURLConnection conn = (JCRURLConnection) url.openConnection(); + ClassPathEntry cp = ClassPathEntry.getInstance( + conn.getSession(), conn.getPath()); + addClassPathEntry(cp); + } catch (IOException ioe) { + log.warn("addURL: Cannot add URL " + url, ioe); + } + + } else { + log.warn("addURL: {} is not a Repository URL, ignored", url); + } + } + + //---------- Property access ---------------------------------------------- + + /** + * Returns the named {@link ClassLoaderResource} if it is contained in the + * cache. If the resource does not exist in the cache or has not been found + * in the class path at an earlier point in time, null is + * returned. + * + * @param name The name of the resource to retrieve from the cache. + * + * @return The named ClassLoaderResource or null + * if not loaded. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + /* package */ ClassLoaderResource getCachedResource(String name) { + Object res = cache.get(name); + if (res == null || res == NOT_FOUND_RESOURCE) { + log.debug("Resource {} not cached", name); + return null; + } + + return (ClassLoaderResource) res; + } + + /** + * Returns an Iterator on all resources in the cache. This + * iterator may also contain {@link #NOT_FOUND_RESOURCE sentinel} entries + * for resources, which failed to load. Callers of this method should take + * care to filter out such resources before acting on it. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + /* package */ Iterator getCachedResources() { + return cache.values().iterator(); + } + + /** + * Removes all entries from the cache of loaded resources, which mark + * resources, which have not been found as of yet. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + protected void cleanCache() { + final Iterator ci = this.cache.values().iterator(); + while (ci.hasNext()) { + if (ci.next() == NOT_FOUND_RESOURCE) { + ci.remove(); + } + } + } + + /** + * Returns true, if the cache is not empty. If the + * {@link #cleanCache()} method is not called before calling this method, a + * false positive result may be returned. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + protected boolean hasLoadedResources() { + return cache.isEmpty(); + } + + /** + * Returns the session used by this class loader to access the repository. + * If this class loader has already been destroyed, this null + * is returned. + */ + protected Session getSession() { + return session; + } + + /** + * Sets the current active class path to the list of class path entries. + */ + protected void setRepository(ClassPathEntry[] classPath) { + this.repository = classPath; + } + + /** + * Returns the current active class path entries list or null + * if this class loader has already been destroyed. + */ + protected ClassPathEntry[] getRepository() { + return repository; + } + + /** + * Adds the class path entry to the current class path list. If the class + * loader has already been destroyed, this method creates a single entry + * class path list with the new class path entry. + */ + protected void addClassPathEntry(ClassPathEntry cpe) { + log.debug("addHandle: Adding path {}", cpe.getPath()); + + // append the entry to the current class path + ClassPathEntry[] oldClassPath = getRepository(); + ClassPathEntry[] newClassPath = addClassPathEntry(oldClassPath, cpe); + setRepository(newClassPath); + } + + /** + * Helper method for class path handling to a new entry to an existing + * list and return the new list. + *

+ * If list is null a new array is returned with + * a single element newEntry. Otherwise the array returned + * contains all elements of list and newEntry + * at the last position. + * + * @param list The array of class path entries, to which a new entry is + * to be appended. This may be null. + * @param newEntry The new entry to append to the class path list. + * + * @return The extended class path list. + */ + protected ClassPathEntry[] addClassPathEntry(ClassPathEntry[] list, + ClassPathEntry newEntry) { + + // quickly define single entry array for the first entry + if (list == null) { + return new ClassPathEntry[]{ newEntry }; + } + + // create new array and copy old and new contents + ClassPathEntry[] newList = new ClassPathEntry[list.length+1]; + System.arraycopy(list, 0, newList, 0, list.length); + newList[list.length] = newEntry; + return newList; + } + + //---------- Object overwrite --------------------------------------------- + + /** + * Returns a string representation of this instance. + */ + public String toString() { + StringBuilder buf = new StringBuilder(getClass().getName()); + + if (isDestroyed()) { + buf.append(" - destroyed"); + } else { + buf.append(": parent: { "); + buf.append(getParent()); + buf.append(" }, user: "); + buf.append(session.getUserID()); + } + + return buf.toString(); + } + + //---------- internal ------------------------------------------------------ + + /** + * Builds the repository list from the list of path patterns and appends + * the path entries from any added handles. This method may be used multiple + * times, each time replacing the currently defined repository list. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + protected synchronized void buildRepository() { + List newRepository = new ArrayList(paths.length); + + // build repository from path patterns + for (int i=0; i < paths.length; i++) { + final String entry = paths[i]; + ClassPathEntry cp = null; + + // try to find repository based on this path + if (repository != null) { + for (int j=0; j < repository.length; j++) { + final ClassPathEntry tmp = repository[i]; + if (tmp.getPath().equals(entry)) { + cp = tmp; + break; + } + } + } + + // not found, creating new one + if (cp == null) { + cp = ClassPathEntry.getInstance(session, entry); + } + + if (cp != null) { + log.debug("Adding path {}", entry); + newRepository.add(cp); + } else { + log.debug("Cannot get a ClassPathEntry for {}", entry); + } + } + + // replace old repository with new one + final ClassPathEntry[] newClassPath = new ClassPathEntry[newRepository.size()]; + newRepository.toArray(newClassPath); + setRepository(newClassPath); + + // clear un-found resource cache + cleanCache(); + } + + /** + * Tries to find the class in the class path from within a + * PrivilegedAction. Throws ClassNotFoundException + * if no class can be found for the name. + * + * @param name the name of the class + * + * @return the resulting class + * + * @throws ClassNotFoundException if the class could not be found + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + private Class findClassPrivileged(String name) throws ClassNotFoundException { + + // prepare the name of the class + final String path = name.replace('.', '/').concat(".class"); + log.debug("findClassPrivileged: Try to find path {} for class {}", + path, name); + + ClassLoaderResource res = findClassLoaderResource(path); + if (res != null) { + + // try defining the class, error aborts + try { + log.debug( + "findClassPrivileged: Loading class from {}, created {}", + res, new Date(res.getLastModificationTime())); + + Class c = defineClass(name, res); + if (c == null) { + log.warn("defineClass returned null for class {}", name); + throw new ClassNotFoundException(name); + } + return c; + + } catch (IOException ioe) { + log.debug("defineClass failed", ioe); + throw new ClassNotFoundException(name, ioe); + } catch (Throwable t) { + log.debug("defineClass failed", t); + throw new ClassNotFoundException(name, t); + } + } + + throw new ClassNotFoundException(name); + } + + /** + * Returns a {@link ClassLoaderResource} for the given name or + * null if not existing. If the resource has already been + * loaded earlier, the cached instance is returned. If the resource has + * not been found in an earlier call to this method, null is + * returned. Otherwise the resource is looked up in the class path. If + * found, the resource is cached and returned. If not found, the + * {@link #NOT_FOUND_RESOURCE} is cached for the name and null + * is returned. + * + * @param name The name of the resource to return. + * + * @return The named ClassLoaderResource if found or + * null if not found. + * + * @throws NullPointerException If this class loader has already been + * destroyed. + */ + /* package */ ClassLoaderResource findClassLoaderResource(String name) { + + // check for cached resources first + ClassLoaderResource res = cache.get(name); + if (res == NOT_FOUND_RESOURCE) { + log.debug("Resource '{}' known to not exist in class path", name); + return null; + } else if (res != null) { + return res; + } + + // walk the repository list and try to find the resource + for (int i = 0; i < repository.length; i++) { + final ClassPathEntry cp = repository[i]; + log.debug("Checking {}", cp); + + res = cp.getResource(name); + if (res != null) { + log.debug("Found resource in {}, created ", res, new Date( + res.getLastModificationTime())); + cache.put(name, res); + return res; + } + + } + + log.debug("No classpath entry contains {}", name); + cache.put(name, NOT_FOUND_RESOURCE); + return null; + } + + /** + * Defines a class getting the bytes for the class from the resource + * + * @param name The fully qualified class name + * @param res The resource to obtain the class bytes from + * + * @throws RepositoryException If a problem occurrs getting at the data. + * @throws IOException If a problem occurrs reading the class bytes from + * the resource. + * @throws ClassFormatError If the class bytes read from the resource are + * not a valid class. + */ + private Class defineClass(String name, ClassLoaderResource res) + throws IOException, RepositoryException { + + log.debug("defineClass({}, {})", name, res); + + Class clazz = res.getLoadedClass(); + if (clazz == null) { + + /** + * This following code for packages is duplicate from URLClassLoader + * because it is private there. I would like to not be forced to + * do this, but I still have to find a way ... -fmeschbe + */ + + // package support + int i = name.lastIndexOf('.'); + if (i != -1) { + String pkgname = name.substring(0, i); + // Check if package already loaded. + Package pkg = getPackage(pkgname); + URL url = res.getCodeSourceURL(); + Manifest man = res.getManifest(); + if (pkg != null) { + // Package found, so check package sealing. + boolean ok; + if (pkg.isSealed()) { + // Verify that code source URL is 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"); + } + } else { + if (man != null) { + definePackage(pkgname, man, url); + } else { + definePackage(pkgname, null, null, null, null, null, null, null); + } + } + } + + byte[] data = res.getBytes(); + clazz = defineClass(name, data, 0, data.length); + res.setLoadedClass(clazz); + } + + return clazz; + } + + /** + * Returns true if the specified package name is sealed according to the + * given manifest + *

+ * This code is duplicate from URLClassLoader.isSealed because + * the latter has private access and we need the method here. + */ + 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(Attributes.Name.SEALED); + } + if (sealed == null) { + if ((attr = man.getMainAttributes()) != null) { + sealed = attr.getValue(Attributes.Name.SEALED); + } + } + return "true".equalsIgnoreCase(sealed); + } + + /** + * Returns true if the url is a JCR + * URL. + * + * @param url The URL to check whether it is a valid JCR URL. + * + * @return true if url is a valid JCR + * URL. + * + * @throws NullPointerException if url is null. + */ + private boolean checkURL(URL url) { + return URLFactory.REPOSITORY_SCHEME.equalsIgnoreCase(url.getProtocol()); + } +} Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:keywords = author date id revision rev url Propchange: sling/trunk/bundles/jcr/classloader/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java ------------------------------------------------------------------------------ svn:mime-type = text/plain