You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2010/01/25 13:43:23 UTC

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/

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 @@
                         </Bundle-Category>
                         <Private-Package>
                             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
                         </Private-Package>
                     </instructions>
                 </configuration>
@@ -115,12 +110,6 @@
             <artifactId>junit</artifactId>
         </dependency>
         <dependency>
-            <groupId>commons-collections</groupId>
-            <artifactId>commons-collections</artifactId>
-            <version>3.2.1</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
         </dependency>

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 <code>ClassLoaderResource</code> 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
+     * <code>null</code> 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 <code>Property</code>providing the content's of
+     *      this resource. This may be <code>null</code> 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 <code>Property</code> 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, <code>null</code> 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, <code>null</code> is returned.
+     * <p>
+     * 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 <code>InputStream</code> to read from the resource.
+     * <p>
+     * This base class implementation returns the result of calling the
+     * <code>getStream()</code> method on the resource's property or
+     * <code>null</code> 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.
+     * <p>
+     * This base class implementation returns the result of calling the
+     * <code>getLength()</code> 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.
+     * <p>
+     * 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.
+     * <p>
+     * This base class implementation returns the result of calling the
+     * {@link Util#getLastModificationTime(Property)} method on the resource's
+     * property if not <code>null</code>. In case of an error or if the
+     * property is <code>null</code>, -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 <code>null</code>,
+     * 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
+     * <code>null</code>, which is what the default implementation does.
+     */
+    public Certificate[] getCertificates() {
+        return null;
+    }
+
+    /**
+     * Returns the <code>Property</code> which is used to check whether this
+     * resource is expired or not.
+     * <p>
+     * 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 <code>true</code> 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, <code>true</code> 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
+     * <code>null</code>.
+     *
+     * @return The class loaded through this resource, which may be
+     *      <code>null</code> 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 <code>null</code> or not.
+     *
+     * @param loadedClass The class to be loaded.
+     */
+    public void setLoadedClass(Class<?> loadedClass) {
+        this.loadedClass = loadedClass;
+    }
+
+    /**
+     * Returns the <code>String</code> 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 <code>ClassPathEntry</code> 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.
+ * <p>
+ * This implementation is not currently integrated with Java security. That is
+ * protection domains and security managers are not supported yet.
+ * <p>
+ * 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 <code>ClassPathEntry</code> assigning the
+     * session and path.
+     *
+     * @param session The <code>Session</code> 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 <code>ClassPathEntry</code> setting the
+     * path and session to the same value as the base instance.
+     * <p>
+     * Note that this constructor does not duplicate the session from the base
+     * instance.
+     *
+     * @param base The <code>ClassPathEntry</code> 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 <code>ClassPathEntry</code> class. This
+     * instance will be a subclass correctly handling the type (directory or
+     * jar archive) of class path entry is to be created.
+     * <p>
+     * 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 <code>Session</code> 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 <code>ClassPathEntry</code> instance for the
+     *      path or <code>null</code> 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 <code>ClassPathEntry</code> is based.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns this <code>ClassPathEntry</code> 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, <code>null</code> 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 <code>.class</code>
+     *      extension attached.
+     *
+     * @return The {@link ClassLoaderResource} identified by the name or
+     *      <code>null</code> 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 <code>ClassPathEntry</code> with the same configuration as
+     * this <code>ClassPathEntry</code>.
+     * <p>
+     * Becase the <code>DirectoryClassPathEntry</code> 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 <code>DynamicRepositoryClassLoader</code> 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>DynamicRepositoryClassLoader</code> from a list of item
+     * path strings containing globbing pattens for the paths defining the
+     * class path.
+     *
+     * @param session The <code>Session</code> 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 <code>ClassLoader</code>, which may be
+     *      <code>null</code>.
+     *
+     * @throws NullPointerException if either the session or the handles list
+     *      is <code>null</code>.
+     */
+    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 <code>DynamicRepositoryClassLoader</code> with the same
+     * configuration as the given <code>DynamicRepositoryClassLoader</code>.
+     * This constructor is used by the {@link #reinstantiate} method.
+     * <p>
+     * Before returning from this constructor the <code>old</code> class loader
+     * is destroyed and may not be used any more.
+     *
+     * @param session The session to associate with this class loader.
+     * @param old The <code>DynamicRepositoryClassLoader</code> to copy the
+     *            cofiguration from.
+     * @param parent The parent <code>ClassLoader</code>, which may be
+     *            <code>null</code>.
+     */
+    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.
+     * <p>
+     * <em>NOTE</em>: This method just clears all internal fields and especially
+     * the class path to render this class loader unusable.
+     * <p>
+     * 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.
+     * <p>
+     * Calling this method yields the same result as calling
+     * {@link #shouldReload(String, boolean)} with the <code>force</code>
+     * argument set to <code>false</code>.
+     *
+     * @param name The name of the resource to check.
+     *
+     * @return <code>true</code> 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
+     * <code>force</code> argument. If the argument is <code>true</code>, the
+     * class loader is marked dirty and <code>true</code> 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 <code>true</code> 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 <code>true</code> if the resource is loaded and
+     *      <code>force</code> is <code>true</code> or if the resource has
+     *      expired. <code>true</code> 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 <code>true</code> 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 <code>true</code> 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 <code>true</code> or if a loaded class has been expired
+     * through the observation.
+     * <p>
+     * This method may also return <code>true</code> if the <code>Session</code>
+     * associated with this class loader is not valid anymore.
+     * <p>
+     * Finally the method always returns <code>true</code> if the class loader
+     * has already been destroyed. Note, however, that a destroyed class loader
+     * cannot be reinstantiated. See {@link #reinstantiate(Session, ClassLoader)}.
+     * <p>
+     * If the class loader is dirty, it should be reinstantiated through the
+     * {@link #reinstantiate} method.
+     *
+     * @return <code>true</code> 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.
+     * <p>
+     * When the new class loader is returned, this class loader has been
+     * destroyed and may not be used any more.
+     *
+     * @param parent The parent <code>ClassLoader</code> for the reinstantiated
+     * 	    <code>DynamicRepositoryClassLoader</code>, which may be
+     *      <code>null</code>.
+     *
+     * @return a new instance with the same configuration as this class loader.
+     *
+     * @throws IllegalStateException if <code>this</code>
+     *      {@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.
+     * <p>
+     * If this class loader already has loaded classes using the old, replaced
+     * path list, it is set dirty.
+     * <p>
+     * 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-<code>null</code>
+     * {@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
+     *      <code>null</code> 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.
+     * <p>
+     * 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 <code>ClassLoaderResource</code> 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 <code>RepositoryClassLoader</code> class extends the
+ * <code>URLClassLoader</code> and provides the functionality to load classes
+ * and resources from JCR Repository.
+ * <p>
+ * This class loader supports loading classes from the Repository hierarchy,
+ * such as a <em>classes</em> 'folder', but also from Jar and Zip files stored
+ * in the Repository.
+ * <p>
+ * 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.
+ * <p>
+ */
+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.
+     * <p>
+     * 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 <code>Session</code> grants access to the Repository to access the
+     * resources.
+     * <p>
+     * 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<String, ClassLoaderResource> cache;
+
+    /**
+     * Flag indicating whether the {@link #destroy()} method has already been
+     * called (<code>true</code>) or not (<code>false</code>)
+     */
+    private boolean destroyed;
+
+    /**
+     * Creates a <code>RepositoryClassLoader</code> from a list of item path
+     * strings containing globbing pattens for the paths defining the class
+     * path.
+     *
+     * @param session The <code>Session</code> 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 <code>ClassLoader</code>, which may be
+     *            <code>null</code>.
+     *
+     * @throws NullPointerException if either the session or the handles list is
+     *             <code>null</code>.
+     */
+    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<String, ClassLoaderResource>();
+        this.destroyed = false;
+
+        // build the class repositories list
+        buildRepository();
+
+        log.debug("RepositoryClassLoader: {} ready", this);
+    }
+
+    /**
+     * Returns <code>true</code> 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.
+     * <p>
+     * <em>NOTE</em>: This method just clears all internal fields and especially
+     * the class path to render this class loader unusable.
+     * <p>
+     * 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<ClassLoaderResource> 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 <code>URL</code> for the resource, or <code>null</code>
+     *      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 <code>Enumeration</code> of <code>URL</code>s. 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
+     * <code>JCR</code> are considered for addition. The system will find out
+     * whether the URL points to a directory or a jar archive.
+     * <p>
+     * URLs added using this method will be preserved through reconfiguration
+     * and reinstantiation.
+     * <p>
+     * If this class loader has already been destroyed this method has no
+     * effect.
+     *
+     * @param url the <code>JCR</code> 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, <code>null</code> is
+     * returned.
+     *
+     * @param name The name of the resource to retrieve from the cache.
+     *
+     * @return The named <code>ClassLoaderResource</code> or <code>null</code>
+     *      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 <code>Iterator</code> 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<ClassLoaderResource> ci = this.cache.values().iterator();
+        while (ci.hasNext()) {
+            if (ci.next() == NOT_FOUND_RESOURCE) {
+                ci.remove();
+            }
+        }
+    }
+
+    /**
+     * Returns <code>true</code>, 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 <code>null</code>
+     * 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 <code>null</code>
+     * 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.
+     * <p>
+     * If <code>list</code> is <code>null</code> a new array is returned with
+     * a single element <code>newEntry</code>. Otherwise the array returned
+     * contains all elements of <code>list</code> and <code>newEntry</code>
+     * at the last position.
+     *
+     * @param list The array of class path entries, to which a new entry is
+     *      to be appended. This may be <code>null</code>.
+     * @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<ClassPathEntry> newRepository = new ArrayList<ClassPathEntry>(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
+     * <code>PrivilegedAction</code>. Throws <code>ClassNotFoundException</code>
+     * 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 <code>name</code> or
+     * <code>null</code> 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, <code>null</code> 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 <code>null</code>
+     * is returned.
+     *
+     * @param name The name of the resource to return.
+     *
+     * @return The named <code>ClassLoaderResource</code> if found or
+     *      <code>null</code> 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
+     * <p>
+     * This code is duplicate from <code>URLClassLoader.isSealed</code> 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 <code>true</code> if the <code>url</code> is a <code>JCR</code>
+     * URL.
+     *
+     * @param url The URL to check whether it is a valid <code>JCR</code> URL.
+     *
+     * @return <code>true</code> if <code>url</code> is a valid <code>JCR</code>
+     *      URL.
+     *
+     * @throws NullPointerException if <code>url</code> is <code>null</code>.
+     */
+    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