You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:46:48 UTC

[sling-org-apache-sling-jcr-classloader] annotated tag org.apache.sling.jcr.classloader-3.1.0 created (now 460f741)

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a change to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git.


      at 460f741  (tag)
 tagging 293258a419234d4f36b7abeb11859adc442bc858 (commit)
      by Carsten Ziegeler
      on Wed Feb 3 11:18:59 2010 +0000

- Log -----------------------------------------------------------------
org.apache.sling.jcr.classloader-3.1.0
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new 9609c02  Import initial Sling source
     new a0c6294  Ignore Eclipse and build artifacts
     new 04ddd72  Fix scm settings
     new ca2435c  Ignore Eclipse .settings
     new 9dfa564  Fix license headers.
     new c9cb58c  Fix license headers.
     new 09b3323  Fix license headers.
     new 2b850ab  SLING-18 scr plugin isn't enabled in jackrabbit-classloader, scripting-core and scripting-jsp
     new 9aa1c77  Remove sling module versions from parent pom to make release management easier. Add some more versions of dependencies to parent pom.
     new eafdadf  SLING-83 Move modules according to the new module structure
     new 35062bd  SLING-83 Fix SVN location, project name and java packages
     new d5ebe13  SLING-83: Fix relative paths to parent poms.
     new 81ea614  SLING-83 make artifactId fully qualified
     new c4dadf9  Add version attribute to package exports
     new 6eb3033  Ensure the Repository Classloader facade is also an URLClassLoader (just like the RepositoryClassLoader itself)
     new 019a135  Clean up code, remove unused imports etc.
     new 930a6c0  SLING-176 Include jackrabbit-jcr-commons classes in the bundle as jcr/api does not provide them any more.
     new 6a2d9a9  Use OSGi core and cmpendium jars from Felix project.
     new 23bd3b9  SLING-204 - add svn ignore patterns for intellij
     new d332a16  SLING-317 - rename /classes to /var/classes
     new 1d30440  Move licence and notice to top level dir.
     new e73f2bf  Add incubator disclaimer.
     new 19ef98b  Go back to simpler notice file format.
     new 33c124c  Eclude maven-eclipse.xml generated by the maven eclipse plugin.
     new 8f8460b  SLING-442: Clean up exports, imports, dependencies and used plugins.
     new d9ad7c5  SLING-479 Fix @scr.tags:   - normalize metadata attribute settings to "no"   - ensure proper metadata and ds attributes are set   - create metatype.properties files where required   - fix metatype.properties files
     new 5914833  SLING-483: Add excludes for javadocs (WiP)
     new 256033c  SLING-495 - NOTICE files generated using mknotice script
     new 0ba9480  SLING-495 - NOTICE files updated according to changes in revision 662927
     new ede0ef1  SLING-495 - NOTICE files regenerated with revision 663097 changes
     new 1e54dd4  Use released version of parent pom.
     new 74074ba  Use released versions (or soon to be released versions).
     new 46c8880  [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-2.0.0-incubator
     new cd4e1cc  [maven-release-plugin] prepare for next development iteration
     new d578763  Set version to next development version.
     new 9d580ae  Fix reference to parent pom.
     new 37da054  SLING-521: Restore more notice files.
     new f1aeeac  SLING-471 Apply patch by Niklas Gustavsson (slightly modified)
     new 01df96a  SLING-521: Separate between notice files for bin and src dists.
     new 4b46564  SLING-521: Separate between notice files for bin and src dists.
     new b628ff4  Change copyright year to 2008.
     new e10ce9b  Update to released versions.
     new 0606f94  [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-2.0.2-incubator
     new 69a580b  [maven-release-plugin] prepare for next development iteration
     new 5efcf8d  Use latest snapshots again to make project buildable.
     new 220cd0d  Use released versions.
     new 004b5c2  Use released parent pom where possible.
     new 554dfbe  SLING-555 : Update all poms to use the latest parent pom; update parent pom to include the incubator repo for plugins and use latest plugins.
     new dade028  SLING-769 Explicit Jackrabbit Dependency Version
     new c07b5e1  SLING-769 Revert Jackrabbit dependency to 1.4.x because the 1.4.1 classloader is the same as the 1.5 classloader (Jackrabbit loves to release new versions without changes)
     new 005f341  SLING-808 : Increase version to 5-incubator-SNAPSHOT
     new 972ae25  Update notice files to include 2009 in copyright statement.
     new ad2d9df  SLING-811 Reduce DependencyManagement in the parent pom and add explicit versions to the modules
     new c8cc0b9  SLING-829 Cosmetics to the bundle/project names
     new ce93f86  SLING-829 Ensure Apache Sling prefix on all configuration names
     new 022db7d  SLING-858 - Let the SCR plugin create correct bind/unbind implementations for us.
     new 870ab6a  SLING-865 : Move to bundles
     new c03c85d  SLING-865 : Adjust reactor pom and svn information in each module.
     new bcbf408  SLING-865 : Adjust path to parent pom and add profiles for samples and contrib
     new 6b3c079  Use release parent pom
     new df7745a  [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-2.0.4-incubator
     new dc1b1db  [maven-release-plugin] prepare for next development iteration
     new e31b679  Use next dev version of parent pom
     new e727d40  Set parent pom to released version.
     new 0a4435b  Move Sling to new TLP location
     new 468500e  SLING-1011 : Remove disclaimer file.
     new 332a7ae  SLING-1011 : Adjust svn location
     new dfa43d2  SLING-1011 : Remove disclaimer from readme's, adjust links to webite, fix versions in poms.
     new e415dca  SLING-1033 Upgrade to Sling parent POM 6 (and ensure web app has the leglize stuff, too)
     new da33ba2  SLING-1048 : Implement the dynamic class loader provider interface and deprecate the repository class loader interface.
     new 31a142c  Use provided parent class loader instead of own class loader
     new dea3efa  Experimental class loader writer for scripting engines.
     new 51c755a  Fix typo which disabled the last modified check.
     new a94152b  Fix wrong org.apache.sling.commons.classloader version number
     new 8f135ba  Update to latest Sling releases.
     new dd74c87  [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-2.0.6
     new e976428  [maven-release-plugin] prepare for next development iteration
     new 4c0188f  SLING-1150 : Session is not closed in getLastModified SLING-1146 : DynamicClassLoaderProvider and ClassLoaderWriter should not be a service factory and refactored session handling SLING-1151 : Don't export jackrabbit classes
     new 0a7b91e  SLING-1152 : Remove deprecated interface RepositoryClassLoaderProvider (and implementations)
     new 0cc78a3  Update to latest release.
     new 7c38541  SLING-1191 : Dynamic class loader providers are not reloaded when bundles are updated/installed etc. Add new release method to the dynamic class loader provider. This allows a provider to react on bundle/class loader changes.
     new 5d497cc  Prepare release.
     new 653ab3d  [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-3.0.0
     new eb88e19  [maven-release-plugin] prepare for next development iteration
     new f0080a0  Use latest versions to avoid build problems.
     new a64993e  Use latest releases.
     new c360499  SLING-1205 Upgrade all projects to parent POM 8 and use OSGi provided OSGi libraries
     new 533d6c6  SLING-1296 : Potential concurrent modification exception during path creation
     new c5e90a2  SLING-1311 : Rare exception "Servlet class not found" during compilation into the repository - improve path creation.
     new fdd62c3  Update notice to 2010 and use JCR in bundle name as this is a bundle in our JCR section.
     new c21156a  SLING-1316 -  Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes.
     new b9535a8  We don't have an api anymore (since 3.0)
     new 9c37a61  Remove spurious comma
     new 631f9f9  SLING-1316 -  Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes. Update to Java 5 code.
     new 8fdf178  SLING-1311 : Rare exception "Servlet class not found" during compilation into the repository - synchronized path creation.
     new eaea87b  SLING-1316 :  Include jackrabbit classloader code to adjust it for Sling needs: Remove unused code and combine the classloader classes into a single class.
     new 0e6d2aa  Update scripting version number to 2.1.0 as we added an interface.
     new 9836b67  Use latest jcr api to decouple modules from jcr 1.0 (contained in older jcr api releases)
     new 3f880ce  Check session before unregistering listeners
     new d803d04  [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-3.1.0
     new 293258a  [maven-scm] copy for tag org.apache.sling.jcr.classloader-3.1.0

The 101 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-jcr-classloader] 09/18: We don't have an api anymore (since 3.0)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit b9535a8193ed4b10ca7871d0c124ebd6971450ad
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 25 12:44:05 2010 +0000

    We don't have an api anymore (since 3.0)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@902795 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5da1fbc..c811477 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,9 +71,7 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
                 <configuration>
-                    <excludePackageNames>
-                        org.apache.sling.jcr.classloader.internal
-                    </excludePackageNames>
+                    <skip>true</skip>
                 </configuration>
             </plugin>
         </plugins>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 01/18: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit eb88e19fcffcd6993feed14ff6c6eb98f71c3a15
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Nov 24 16:00:50 2009 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@883742 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5fe0135..9c7694a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
     </parent>
 
     <artifactId>org.apache.sling.jcr.classloader</artifactId>
-    <version>3.0.0</version>
+    <version>3.0.1-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Repository ClassLoader</name>
@@ -37,9 +37,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.classloader-3.0.0</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.classloader-3.0.0</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.jcr.classloader-3.0.0</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 18/18: [maven-scm] copy for tag org.apache.sling.jcr.classloader-3.1.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 293258a419234d4f36b7abeb11859adc442bc858
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Feb 3 11:18:59 2010 +0000

    [maven-scm] copy for tag org.apache.sling.jcr.classloader-3.1.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.classloader-3.1.0@906001 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 14/18: Update scripting version number to 2.1.0 as we added an interface.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 0e6d2aad4650db6c9171f5d7905a544d2cdc3210
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jan 27 09:56:26 2010 +0000

    Update scripting version number to 2.1.0 as we added an interface.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@903583 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index fc06be2..e80e262 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,14 +26,16 @@
     </parent>
 
     <artifactId>org.apache.sling.jcr.classloader</artifactId>
-    <version>3.0.1-SNAPSHOT</version>
+    <version>3.1.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling JCR ClassLoader</name>
     <description>
-        Provides Support for delegate class loaders built dynamically.
-        Delegate class loaders supported are generic ClassLoader, OSGi
-        bundles and repository class loader.
+        This bundle provides a cloassloader for the Apache Sling Commons
+        dynamic classloader. The classloader provided supports loading
+        classes from a JCR repository at configured paths. This bundle
+        also implements a class writer which allows to store compiled
+        classes (like compiled JSPs etc.) in the repository.
     </description>
 
     <scm>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 07/18: Update notice to 2010 and use JCR in bundle name as this is a bundle in our JCR section.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit fdd62c3c9ec6894e0d759f1b353cbc4b8feebb74
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 25 10:24:12 2010 +0000

    Update notice to 2010 and use JCR in bundle name as this is a bundle in our JCR section.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@902764 13f79535-47bb-0310-9956-ffa450edef68
---
 NOTICE                             |  2 +-
 pom.xml                            | 14 +-------------
 src/main/resources/META-INF/NOTICE |  2 +-
 3 files changed, 3 insertions(+), 15 deletions(-)

diff --git a/NOTICE b/NOTICE
index 20f482a..76ce331 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
 Apache Sling JCR Classloader
-Copyright 2008-2009 The Apache Software Foundation
+Copyright 2008-2010 The Apache Software Foundation
 
 Apache Sling is based on source code originally developed 
 by Day Software (http://www.day.com/).
diff --git a/pom.xml b/pom.xml
index 9a2a64f..371a03f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
     <version>3.0.1-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
-    <name>Apache Sling Repository ClassLoader</name>
+    <name>Apache Sling JCR ClassLoader</name>
     <description>
         Provides Support for delegate class loaders built dynamically.
         Delegate class loaders supported are generic ClassLoader, OSGi
@@ -107,18 +107,6 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.jackrabbit</groupId>
-            <artifactId>jackrabbit-classloader</artifactId>
-            <version>1.5.0</version>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.jackrabbit</groupId>
-            <artifactId>jackrabbit-jcr-commons</artifactId>
-            <version>1.6.0</version>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
index 20f482a..76ce331 100644
--- a/src/main/resources/META-INF/NOTICE
+++ b/src/main/resources/META-INF/NOTICE
@@ -1,5 +1,5 @@
 Apache Sling JCR Classloader
-Copyright 2008-2009 The Apache Software Foundation
+Copyright 2008-2010 The Apache Software Foundation
 
 Apache Sling is based on source code originally developed 
 by Day Software (http://www.day.com/).

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 16/18: Check session before unregistering listeners

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 3f880ce57b642960c4d49011b644347c9ffa2e97
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Feb 1 15:38:27 2010 +0000

    Check session before unregistering listeners
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@905310 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/DynamicRepositoryClassLoader.java        | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
index 3b38b95..c754d63 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
@@ -837,14 +837,17 @@ public final class DynamicRepositoryClassLoader
     private final void unregisterListeners() {
         log.debug("unregisterListeners: Deregistering from the observation service");
         if ( this.proxyListeners != null ) {
-            for(final EventListener listener : this.proxyListeners) {
-                if ( listener != null ) {
-                    try {
-                        final ObservationManager om = session.getWorkspace().getObservationManager();
-                        om.removeEventListener(listener);
-                    } catch (RepositoryException re) {
-                        log.error("unregisterListener: Cannot unregister " +
-                            this + " from observation manager", re);
+            // check session first!
+            if ( session.isLive() ) {
+                for(final EventListener listener : this.proxyListeners) {
+                    if ( listener != null ) {
+                        try {
+                            final ObservationManager om = session.getWorkspace().getObservationManager();
+                            om.removeEventListener(listener);
+                        } catch (RepositoryException re) {
+                            log.error("unregisterListener: Cannot unregister " +
+                                this + " from observation manager", re);
+                        }
                     }
                 }
             }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 08/18: SLING-1316 - Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit c21156ab23710e3ea75712d43b028b779f567291
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 25 12:43:22 2010 +0000

    SLING-1316 -  Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@902794 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  11 -
 .../classloader/internal/ClassLoaderResource.java  | 414 +++++++++++
 .../jcr/classloader/internal/ClassPathEntry.java   | 227 ++++++
 .../internal/DynamicRepositoryClassLoader.java     | 660 +++++++++++++++++
 .../internal/RepositoryClassLoaderFacade.java      |   1 -
 .../internal/URLRepositoryClassLoader.java         | 795 +++++++++++++++++++++
 .../sling/jcr/classloader/internal/Util.java       | 166 +++++
 .../jcr/classloader/internal/net/FileParts.java    | 312 ++++++++
 .../internal/net/JCRJarURLConnection.java          | 289 ++++++++
 .../classloader/internal/net/JCRJarURLHandler.java | 298 ++++++++
 .../classloader/internal/net/JCRURLConnection.java | 778 ++++++++++++++++++++
 .../classloader/internal/net/JCRURLHandler.java    | 147 ++++
 .../jcr/classloader/internal/net/URLFactory.java   |  99 +++
 13 files changed, 4185 insertions(+), 12 deletions(-)

diff --git a/pom.xml b/pom.xml
index 371a03f..5da1fbc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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>
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java b/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java
new file mode 100644
index 0000000..73671bf
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/ClassLoaderResource.java
@@ -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();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java b/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java
new file mode 100644
index 0000000..3f79345
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java
@@ -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 ----------------------------------------------
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
new file mode 100644
index 0000000..35df60a
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
@@ -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);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java b/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java
index f33a834..d52ee8f 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/RepositoryClassLoaderFacade.java
@@ -25,7 +25,6 @@ import java.util.Enumeration;
 
 import javax.jcr.RepositoryException;
 
-import org.apache.jackrabbit.classloader.DynamicRepositoryClassLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
new file mode 100644
index 0000000..7755fd9
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
@@ -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());
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java b/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java
new file mode 100644
index 0000000..01e41f8
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/Util.java
@@ -0,0 +1,166 @@
+/*
+ * 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 javax.jcr.AccessDeniedException;
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFormatException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>Util</code> provides helper methods for the repository
+ * classloader and its class path entry and resource classes.
+ * <p>
+ * This class may not be extended or instantiated, it just contains static
+ * utility methods.
+ */
+public class Util {
+
+    /** default logging */
+    private static final Logger log = LoggerFactory.getLogger(Util.class);
+
+    /** Private constructor to not instantiate */
+    private Util() {
+    }
+
+    /**
+     * Resolves the given <code>item</code> to a <code>Property</code> from
+     * which contents can be read.
+     * <p>
+     * The following mechanism is used to derive the contents:
+     * <ol>
+     * <li>If the <code>item</code> is a property, this property is used</li>
+     * <li>If the <code>item</code> is a node, three steps are tested:
+     * <ol>
+     * <li>If the node has a <code>jcr:content</code> child node, use that
+     * child node in the next steps. Otherwise continue with the node.</li>
+     * <li>Check for a <code>jcr:data</code> property and use that property
+     * if existing.</li>
+     * <li>Otherwise call <code>getPrimaryItem</code> method repeatedly until
+     * a property is returned or until no more primary item is available.</li>
+     * </ol>
+     * </ol>
+     * If no property can be resolved using the above algorithm or if the
+     * resulting property is a multivalue property, <code>null</code> is
+     * returned. Otherwise if the resulting property is a <code>REFERENCE</code>
+     * property, the node referred to is retrieved and this method is called
+     * recursively with the node. Otherwise, the resulting property is returned.
+     * 
+     * @param item The <code>Item</code> to resolve to a <code>Property</code>.
+     * @return The resolved <code>Property</code> or <code>null</code> if
+     *         the resolved property is a multi-valued property or the
+     *         <code>item</code> is a node which cannot be resolved to a data
+     *         property.
+     * @throws ValueFormatException If the <code>item</code> resolves to a
+     *             single-valued <code>REFERENCE</code> type property which
+     *             cannot be resolved to the node referred to.
+     * @throws RepositoryException if another error occurrs accessing the
+     *             repository.
+     */
+    public static Property getProperty(Item item) throws ValueFormatException,
+            RepositoryException {
+
+        Property prop;
+        if (item.isNode()) {
+
+            // check whether the node has a jcr:content node (e.g. nt:file)
+            Node node = (Node) item;
+            if (node.hasNode("jcr:content")) {
+                node = node.getNode("jcr:content");
+            }
+
+            // if the node has a jcr:data property, use that property
+            if (node.hasProperty("jcr:data")) {
+                
+                prop = node.getProperty("jcr:data");
+
+            } else {
+
+                // otherwise try to follow default item trail
+                try {
+                    item = node.getPrimaryItem();
+                    while (item.isNode()) {
+                        item = ((Node) item).getPrimaryItem();
+                    }
+                    prop = (Property) item;
+                } catch (ItemNotFoundException infe) {
+                    // we don't actually care, but log for completeness
+                    log.debug("getProperty: No primary items for "
+                        + node.getPath(), infe);
+                    return null;
+                }
+            }
+
+        } else {
+
+            prop = (Property) item;
+
+        }
+
+        // we get here with a property - otherwise an exception has already
+        // been thrown
+        if (prop.getDefinition().isMultiple()) {
+            log.error("{} is a multivalue property", prop.getPath());
+            return null;
+        } else if (prop.getType() == PropertyType.REFERENCE) {
+            Node node = prop.getNode();
+            log.info("Property {} refers to node {}; finding primary item",
+                prop.getPath(), node.getPath());
+            return getProperty(node);
+        }
+
+        return prop;
+    }
+
+    /**
+     * Returns the last modification time of the property, which is the long
+     * value of the <code>jcr:lastModified</code> property of the parent node
+     * of <code>prop</code>. If the parent node does not have a
+     * <code>jcr:lastModified</code> property the current system time is
+     * returned.
+     * 
+     * @param prop The property for which to return the last modification time.
+     * @return The last modification time of the resource or the current time if
+     *         the parent node of the property does not have a
+     *         <code>jcr:lastModified</code> property.
+     * @throws ItemNotFoundException If the parent node of the property cannot
+     *             be retrieved.
+     * @throws AccessDeniedException If (read) access to the parent node is
+     *             denied.
+     * @throws RepositoryException If any other error occurrs accessing the
+     *             repository to retrieve the last modification time.
+     */
+    public static long getLastModificationTime(Property prop)
+            throws ItemNotFoundException, PathNotFoundException,
+            AccessDeniedException, RepositoryException {
+
+        Node parent = prop.getParent();
+        if (parent.hasProperty("jcr:lastModified")) {
+            return parent.getProperty("jcr:lastModified").getLong();
+        }
+
+        return System.currentTimeMillis();
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java
new file mode 100644
index 0000000..a5057f3
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/FileParts.java
@@ -0,0 +1,312 @@
+/*
+ * 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.net;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>FileParts</code> class provides composing and parsing functionality
+ * to create and analize JCR Repository URL file components.
+ * <p>
+ * The file component of a JCR Repository URL has the format
+ * <pre>
+ *      file = [ "jcr:" [ "//" authority ] ] "/" repository "/" workspace jarpath .
+ *      authority = // URL authority specification
+ *      repository = // URL encoded repository name
+ *      workspace = // URL encoded workspace name
+ *      jarpath = path [ "!/" [ entry ] ] .
+ *      path = // The absolute item path (with a leading slash)
+ *      entry = // The (relative) path to the entry in an archive
+ * </pre>
+ * <p>
+ * To facitility use of this class with JCRJar URLs, the
+ * {@link #FileParts(String)} supports file specifications which contains
+ * the JCR Repository URL scheme name and an optional URL authority
+ * specification. This prefix in front of the real file specification is
+ * silently discarded. It is not included in the string representation returned
+ * by the {@link #toString()} method.
+ * <p>
+ * To make sure parsing is not complicated by implementation and use case
+ * specific repository and workspace names, those names are URL encoded using
+ * the <code>URLEncoder</code> class and <i>UTF-8</i> character encoding.
+ *
+ * @author Felix Meschberger
+ */
+class FileParts {
+
+    /** The decoded name of the repository */
+    private final String repository;
+
+    /** The decoded name of the workspace */
+    private final String workspace;
+
+    /** The repository item path part of the URL path */
+    private final String path;
+
+    /**
+     * The path to the entry in the archive, if the file spec contains the
+     * jar entry separator <i>!/</i>. If no entry path is specified, this is
+     * <code>null</code>. If no path is specified after the <i>!/</i> this
+     * is an empty string.
+     */
+    private final String entryPath;
+
+    /**
+     * Creates a new instance for the root node of the given session. The
+     * repository name is currently set to the fixed string "_" as there has not
+     * been established a repository naming convention yet. The name of the
+     * workspace is set to the name of the workspace to which the session is
+     * attached. The path is set to <code>"/"</code> to indicate the root node
+     * if the <code>path</code> argument is <code>null</code>.
+     *
+     * @param session The session for which to create this instance.
+     * @param path The absolute item path to initialize this instance with. If
+     *      <code>null</code> the item path is set to the <code>/</code>.
+     * @param entryPath The path to the archive entry to set on this instance.
+     *      This is expected to be a relative path without a leading slash and
+     *      may be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>session</code> is
+     *      <code>null</code>.
+     */
+    FileParts(Session session, String path, String entryPath) {
+        this.repository = "_";
+        this.workspace = session.getWorkspace().getName();
+        this.path = (path == null) ? "/" : path;
+        this.entryPath = entryPath;
+    }
+
+    /**
+     * Creates an instance of this class setting the repository, workspace and
+     * path fields from the given <code>file</code> specification.
+     *
+     * @param file The specification providing the repository, workspace and
+     *      path values.
+     *
+     * @throws NullPointerException if <code>file</code> is
+     *      <code>null</code>.
+     * @throws IllegalArgumentException if <code>file</code> is not the
+     *      correct format.
+     */
+    FileParts(String file) {
+        if (!file.startsWith("/")) {
+            if (file.startsWith(URLFactory.REPOSITORY_SCHEME+":")) {
+                file = strip(file);
+            } else {
+                throw failure("Not an absolute file", file);
+            }
+        }
+
+        // find the repository name
+        int slash0 = 1;
+        int slash1 = file.indexOf('/', slash0);
+        if (slash1 < 0 || slash1-slash0 == 0) {
+            throw failure("Missing repository name", file);
+        }
+        this.repository = decode(file.substring(slash0, slash1));
+
+        // find the workspace name
+        slash0 = slash1 + 1;
+        slash1 = file.indexOf('/', slash0);
+        if (slash1 < 0 || slash1-slash0 == 0) {
+            throw failure("Missing workspace name", file);
+        }
+        this.workspace = decode(file.substring(slash0, slash1));
+
+        String fullPath = file.substring(slash1);
+        int bangSlash = JCRJarURLHandler.indexOfBangSlash(fullPath);
+        if (bangSlash < 0) {
+            this.path = fullPath;
+            this.entryPath = null;
+        } else {
+            this.path = fullPath.substring(0, bangSlash-1);
+            this.entryPath = fullPath.substring(bangSlash+1);
+        }
+    }
+
+    /**
+     * Returns the plain name of the repository.
+     */
+    String getRepository() {
+        return repository;
+    }
+
+    /**
+     * Returns the plain name of the workspace.
+     */
+    String getWorkspace() {
+        return workspace;
+    }
+
+    /**
+     * Returns the absolute repository path of the item.
+     */
+    String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns the entry path of <code>null</code> if no entry exists.
+     */
+    String getEntryPath() {
+        return entryPath;
+    }
+
+    //---------- Object overwrites --------------------------------------------
+
+    /**
+     * Returns a hash code for this instance composed of the hash codes of the
+     * repository, workspace and path names.
+     */
+    public int hashCode() {
+        return getRepository().hashCode() +
+            17 * getWorkspace().hashCode() +
+            33 * getPath().hashCode();
+    }
+
+    /**
+     * Returns <code>true</code> if <code>obj</code> is the same as this or
+     * if other is a <code>FileParts</code> with the same path, workspace and
+     * repository. Otherwise <code>false</code> is returned.
+     */
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        } else if (obj instanceof FileParts) {
+            FileParts other = (FileParts) obj;
+
+            // catch null entry path, fail if other has a defined entry path
+            if (getEntryPath() == null) {
+                if (other.getEntryPath() != null) {
+                    return false;
+                }
+            }
+
+            return getPath().equals(other.getPath()) &&
+                getWorkspace().equals(other.getWorkspace()) &&
+                getRepository().equals(other.getRepository()) &&
+                getEntryPath().equals(other.getEntryPath());
+        }
+
+        // fall back on null or other class
+        return false;
+    }
+
+    /**
+     * Returns the encoded string representation of this instance, which may
+     * later be fed to the {@link #FileParts(String)} constructor to recreate
+     * an equivalent instance.
+     */
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append('/').append(encode(getRepository()));
+        buf.append('/').append(encode(getWorkspace()));
+        buf.append(getPath());
+
+        if (getEntryPath() != null) {
+            buf.append("!/").append(getEntryPath());
+        }
+
+        return buf.toString();
+    }
+
+    //---------- internal -----------------------------------------------------
+
+    /**
+     * @throws IllegalArgumentException If there is no path element after the
+     *      authority.
+     */
+    private String strip(String file) {
+        // cut off jcr: prefix - any other prefix, incl. double slash
+        // would cause an exception to be thrown in the constructor
+        int start = 4;
+
+        // check whether the remainder contains an authority specification
+        if (file.length() >= start+2 && file.charAt(start) == '/' &&
+                file.charAt(start+1) == '/') {
+
+            // find the slash after the authority, fail if missing
+            start = file.indexOf('/', start + 2);
+            if (start < 0) {
+                throw failure("Missing path after authority", file);
+            }
+        }
+
+        // return the file now
+        return file.substring(start);
+    }
+
+    /**
+     * Encodes the given string value using the <code>URLEncoder</code> and
+     * <i>UTF-8</i> character encoding.
+     *
+     * @param value The string value to encode.
+     *
+     * @return The encoded string value.
+     *
+     * @throws InternalError If <code>UTF-8</code> character set encoding is
+     *      not supported. As <code>UTF-8</code> is required to be implemented
+     *      on any Java platform, this error is not expected.
+     */
+    private String encode(String value) {
+        try {
+            return URLEncoder.encode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // not expected, throw an InternalError
+            throw new InternalError("UTF-8 not supported");
+        }
+    }
+
+    /**
+     * Decodes the given string value using the <code>URLDecoder</code> and
+     * <i>UTF-8</i> character encoding.
+     *
+     * @param value The string value to decode.
+     *
+     * @return The decoded string value.
+     *
+     * @throws InternalError If <code>UTF-8</code> character set encoding is
+     *      not supported. As <code>UTF-8</code> is required to be implemented
+     *      on any Java platform, this error is not expected.
+     */
+    private String decode(String value) {
+        try {
+            return URLDecoder.decode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            // not expected, throw an InternalError
+            throw new InternalError("UTF-8 not supported");
+        }
+    }
+
+    /**
+     * Returns a <code>IllegalArgumentException</code> formatted with the
+     * given reason and causing file specification.
+     *
+     * @param reason The failure reason.
+     * @param file The original file specification leading to failure.
+     *
+     * @return A <code>IllegalArgumentException</code> with the given
+     *      reason and causing file specification.
+     */
+    private IllegalArgumentException failure(String reason, String file) {
+        return new IllegalArgumentException(reason + ": '" + file + "'");
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java
new file mode 100644
index 0000000..29d4a20
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLConnection.java
@@ -0,0 +1,289 @@
+/*
+ * 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.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>JCRJarURLConnection</code> extends the
+ * {@link org.apache.sling.jcr.classloader.internal.net.JCRURLConnection} class to support accessing
+ * archive files stored in a JCR Repository.
+ * <p>
+ * Just like the base class, this class requires the URL to resolve, either
+ * directly or through primary item chain, to a repository <code>Property</code>.
+ * <p>
+ * Access to this connections property and archive entry content is perpared
+ * with the {@link #connect()}, which after calling the base class implementation
+ * to find the property tries to find the archive entry and set the connection's
+ * fields according to the entry. This implementation's {@link #connect()}
+ * method fails if the named entry does not exist in the archive.
+ * <p>
+ * The {@link #getInputStream()} method either returns an stream on the archive
+ * entry or on the archive depending on whether an entry path is specified
+ * in the URL or not. Like the base class implementation, this implementation
+ * returns a new <code>InputStream</code> on each invocation.
+ * <p>
+ * If an entry path is defined on the URL, the header fields are set from the
+ * archive entry:
+ * <table border="0" cellspacing="0" cellpadding="3">
+ *  <tr><td><code>Content-Type</code><td>Guessed from the entry name or
+ *      <code>application/octet-stream</code> if the type cannot be guessed
+ *      from the name</tr>
+ *  <tr><td><code>Content-Encoding</code><td><code>null</code></tr>
+ *  <tr><td><code>Content-Length</code><td>The size of the entry</tr>
+ *  <tr><td><code>Last-Modified</code><td>The last modification time of the
+ *      entry</tr>
+ * </table>
+ * <p>
+ * If no entry path is defined on the URL, the header fields are set from the
+ * property by the base class implementation with the exception of the
+ * content type, which is set to <code>application/java-archive</code> by
+ * the {@link #connect()} method.
+ * <p>
+ * <em>Note that this implementation does only support archives stored in the
+ * JCR Repository, no other contained storage such as </em>file<em> or
+ * </em>http<em> is supported.</em>
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ */
+public class JCRJarURLConnection extends JCRURLConnection {
+
+    /** default log category */
+    private static final Logger log =
+        LoggerFactory.getLogger(JCRJarURLConnection.class);
+
+    /**
+     * The name of the MIME content type for this connection's content if
+     * no entry path is defined on the URL (value is "application/java-archive").
+     */
+    protected static final String APPLICATION_JAR = "application/java-archive";
+
+    /**
+     * Creates an instance of this class for the given <code>url</code>
+     * supported by the <code>handler</code>.
+     *
+     * @param url The URL to base the connection on.
+     * @param handler The URL handler supporting the given URL.
+     */
+    JCRJarURLConnection(URL url, JCRJarURLHandler handler) {
+        super(url, handler);
+    }
+
+    /**
+     * Returns the path to the entry contained in the archive or
+     * <code>null</code> if the URL contains no entry specification in the
+     * path.
+     */
+    String getEntryPath() {
+        return getFileParts().getEntryPath();
+    }
+
+    /**
+     * Connects to the URL setting the header fields and preparing for the
+     * {@link #getProperty()} and {@link #getInputStream()} methods.
+     * <p>
+     * After calling the base class implemenation to get the basic connection,
+     * the entry is looked for in the archive to set the content type, content
+     * length and last modification time header fields according to the named
+     * entry. If no entry is defined on the URL, only the content type header
+     * field is set to <code>application/java-archive</code>.
+     * <p>
+     * When this method successfully returns, this connection is considered
+     * connected. In case of an exception thrown, the connection is not
+     * connected.
+     *
+     * @throws IOException if an error occurrs retrieving the data property or
+     *      any of the header field value properties or if any other errors
+     *      occurrs. Any cuasing exception is set as the cause of this
+     *      exception.
+     */
+    public synchronized void connect() throws IOException {
+
+        if (!connected) {
+
+            // have the base class connect to get the jar property
+            super.connect();
+
+            // we assume the connection is now (temporarily) connected,
+            // thus calling the getters will not result in a recursive loop
+            Property property = getProperty();
+            String contentType = getContentType();
+            String contentEncoding = getContentEncoding();
+            int contentLength = getContentLength();
+            long lastModified = getLastModified();
+
+            // mark as not connected to not get false positives if the
+            // following code fails
+            connected = false;
+
+            // Get hold of the data
+            try {
+
+                JarInputStream jins = null;
+                try {
+
+                    // try to get the jar input stream, fail if no jar
+                    jins = new JarInputStream(property.getStream());
+
+                    String entryPath = getEntryPath();
+                    if (entryPath != null) {
+
+                        JarEntry entry = findEntry(jins, entryPath);
+
+                        if (entry != null) {
+
+                            contentType = guessContentTypeFromName(entryPath);
+                            if (contentType == null) {
+                                contentType = APPLICATION_OCTET;
+                            }
+
+                            contentLength = (int) entry.getSize();
+                            lastModified = entry.getTime();
+
+                        } else {
+
+                            throw failure("connect", entryPath +
+                                " not contained in jar archive", null);
+
+                        }
+
+                    } else {
+
+                        // replaces the base class defined content type
+                        contentType = APPLICATION_JAR;
+
+                    }
+
+                } finally {
+                    if (jins != null) {
+                        try {
+                            jins.close();
+                        } catch (IOException ignore) {
+                        }
+                    }
+                }
+
+                log.debug("connect: Using atom '" + property.getPath()
+                    + "' with content type '" + contentType + "' for "
+                    + String.valueOf(contentLength) + " bytes");
+
+                // set the fields
+                setContentType(contentType);
+                setContentEncoding(contentEncoding);
+                setContentLength(contentLength);
+                setLastModified(lastModified);
+
+                // mark connection open
+                connected = true;
+
+            } catch (RepositoryException re) {
+
+                throw failure("connect", re.toString(), re);
+
+            }
+        }
+    }
+
+    /**
+     * Returns an input stream that reads from this open connection. If not
+     * entry path is specified in the URL, this method returns the input stream
+     * providing access to the archive as a whole. Otherwise the input stream
+     * returned is a <code>JarInputStream</code> positioned at the start of
+     * the named entry.
+     * <p>
+     * <b>NOTES:</b>
+     * <ul>
+     * <li>Each call to this method returns a new <code>InputStream</code>.
+     * <li>Do not forget to close the return stream when not used anymore for
+     *      the system to be able to free resources.
+     * </ul>
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The <code>InputStream</code> on the archive or the entry if
+     *      specified.
+     *
+     * @throws IOException if an error occurrs opening the connection through
+     *      {@link #connect()} or creating the <code>InputStream</code> on the
+     *      repository <code>Property</code>.
+     */
+    public InputStream getInputStream() throws IOException {
+
+        // get the input stream on the archive itself - also enforces connect()
+        InputStream ins = super.getInputStream();
+
+        // access the entry in the archive if defined
+        String entryPath = getEntryPath();
+        if (entryPath != null) {
+            // open the jar input stream
+            JarInputStream jins = new JarInputStream(ins);
+
+            // position at the correct entry
+            findEntry(jins, entryPath);
+
+            // return the input stream
+            return jins;
+        }
+
+        // otherwise just return the stream on the archive
+        return ins;
+    }
+
+    //----------- internal helper to find the entry ------------------------
+
+    /**
+     * Returns the <code>JarEntry</code> for the path from the
+     * <code>JarInputStream</code> or <code>null</code> if the path cannot
+     * be found in the archive.
+     *
+     * @param zins The <code>JarInputStream</code> to search in.
+     * @param path The path of the <code>JarEntry</code> to return.
+     *
+     * @return The <code>JarEntry</code> for the path or <code>null</code>
+     *      if no such entry can be found.
+     *
+     * @throws IOException if a problem occurrs reading from the stream.
+     */
+    static JarEntry findEntry(JarInputStream zins, String path)
+        throws IOException {
+
+        JarEntry entry = zins.getNextJarEntry();
+        while (entry != null) {
+            if (path.equals(entry.getName())) {
+                return entry;
+            }
+
+            entry = zins.getNextJarEntry();
+        }
+        // invariant : nothing found in the zip matching the path
+
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java
new file mode 100644
index 0000000..39b7f26
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRJarURLHandler.java
@@ -0,0 +1,298 @@
+/*
+ * 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.net;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>JCRJarURLHandler</code> is the <code>URLStreamHandler</code> for
+ * Java Archive URLs for archives from a JCR Repository URLs (JCRJar URL). The
+ * scheme for such ULRs will be <code>jar</code> while the file part of the URL
+ * has the scheme <code>jcr</code>.
+ * <p>
+ * JCRJar URLs have not been standardized yet and may only be created in the
+ * context of an existing <code>Session</code>. Therefore this handler is not
+ * globally available and JCR Repository URLs may only be created through the
+ * factory methods in the {@link org.apache.sling.jcr.classloader.internal.net.URLFactory} class.
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ *
+ * @see org.apache.sling.jcr.classloader.internal.net.JCRJarURLConnection
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory#createJarURL(Session, String, String)
+ */
+class JCRJarURLHandler extends JCRURLHandler {
+
+    /**
+     * Creates an instance of this handler class.
+     *
+     * @param session The <code>Session</code> supporting this handler. This
+     *      must not be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>session</code> is <code>null</code>.
+     */
+    JCRJarURLHandler(Session session) {
+        super(session);
+    }
+
+    //---------- URLStreamHandler abstracts ------------------------------------
+
+    /**
+     * Gets a connection object to connect to an JCRJar URL.
+     *
+     * @param url The JCRJar URL to connect to.
+     *
+     * @return An instance of the {@link JCRJarURLConnection} class.
+     *
+     * @see JCRJarURLConnection
+     */
+    protected URLConnection openConnection(URL url) {
+        return new JCRJarURLConnection(url, this);
+    }
+
+    /**
+     * Parses the string representation of a <code>URL</code> into a
+     * <code>URL</code> object.
+     * <p>
+     * If there is any inherited context, then it has already been copied into
+     * the <code>URL</code> argument.
+     * <p>
+     * The <code>parseURL</code> method of <code>URLStreamHandler</code>
+     * parses the string representation as if it were an <code>http</code>
+     * specification. Most URL protocol families have a similar parsing. A
+     * stream protocol handler for a protocol that has a different syntax must
+     * override this routine.
+     *
+     * @param url the <code>URL</code> to receive the result of parsing the
+     *            spec.
+     * @param spec the <code>String</code> representing the URL that must be
+     *            parsed.
+     * @param start the character index at which to begin parsing. This is just
+     *            past the '<code>:</code>' (if there is one) that specifies
+     *            the determination of the protocol name.
+     * @param limit the character position to stop parsing at. This is the end
+     *            of the string or the position of the "<code>#</code>"
+     *            character, if present. All information after the sharp sign
+     *            indicates an anchor.
+     */
+    protected void parseURL(URL url, String spec, int start, int limit) {
+        // protected void parseURL(URL url, String s, int i, int j)
+
+        String file = null;
+        String ref = null;
+
+        // split the reference and file part
+        int hash = spec.indexOf('#', limit);
+        boolean emptyFile = hash == start;
+        if (hash > -1) {
+            ref = spec.substring(hash + 1, spec.length());
+            if (emptyFile) {
+                file = url.getFile();
+            }
+        }
+
+        boolean isSpecAbsolute = spec.substring(0, 4).equalsIgnoreCase("jar:");
+        spec = spec.substring(start, limit);
+
+        if (isSpecAbsolute) {
+
+            // get the file part from the absolute spec
+            file = parseAbsoluteSpec(spec);
+
+        } else if (!emptyFile) {
+
+            // build the file part from the url and relative spec
+            file = parseContextSpec(url, spec);
+
+            // split archive and entry names
+            int bangSlash = indexOfBangSlash(file);
+            String archive = file.substring(0, bangSlash);
+            String entry = file.substring(bangSlash);
+
+            // collapse /../, /./ and //
+            entry = canonizeString(entry);
+
+            file = archive + entry;
+
+        }
+
+        setURL(url, "jar", "", -1, null, null, file, null, ref);
+    }
+
+    //---------- internal -----------------------------------------------------
+
+    /**
+     * Finds the position of the bang slash (!/) in the file part of the URL.
+     */
+    static int indexOfBangSlash(String file) {
+
+        for (int i = file.length(); (i = file.lastIndexOf('!', i)) != -1; i--) {
+            if (i != file.length() - 1 && file.charAt(i + 1) == '/') {
+                return i + 1;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Parses the URL spec and checks whether it contains a bang slash and
+     * whether it would get a valid URL. Returns the same value if everything is
+     * fine else a <code>NullPointerException</code> is thrown.
+     *
+     * @param spec The URL specification to check.
+     * @return The <code>spec</code> if everything is ok.
+     * @throws NullPointerException if either no bang slash is contained in the
+     *             spec or if the spec without the bang slash part would not be
+     *             a valid URL.
+     */
+    private String parseAbsoluteSpec(String spec) {
+
+        // find and check bang slash
+        int bangSlash = indexOfBangSlash(spec);
+        if (bangSlash == -1) {
+            throw new NullPointerException("no !/ in spec");
+        }
+
+        try {
+
+            String testSpec = spec.substring(0, bangSlash - 1);
+            URI uri = new URI(testSpec);
+
+            // verify the scheme is the JCR Repository Scheme
+            if (!URLFactory.REPOSITORY_SCHEME.equals(uri.getScheme())) {
+                throw new URISyntaxException(testSpec,
+                    "Unsupported Scheme " + uri.getScheme(), 0);
+            }
+
+        } catch (URISyntaxException use) {
+
+            throw new NullPointerException("invalid url: " + spec + " (" + use
+                + ")");
+
+        }
+
+        return spec;
+    }
+
+    /**
+     * Merges the specification and the file part of the URL respecting the bang
+     * slashes. If the specification starts with a slash, it is regarded as a
+     * complete path of a archive entry and replaces an existing archive entry
+     * specification in the url. Examples :<br>
+     * <table>
+     * <tr>
+     * <th align="left">file
+     * <th align="left">spec
+     * <th align="left">result
+     * <tr>
+     * <td>/some/file/path.jar!/
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path
+     * <tr>
+     * <td>/some/file/path.jar!/some/default
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path </table>
+     * <p>
+     * If the specification is not absolutes it replaces the last file name part
+     * if the file name does not end with a slash. Examples :<br>
+     * <table>
+     * <tr>
+     * <th align="left">file
+     * <th align="left">spec
+     * <th align="left">result
+     * <tr>
+     * <td>/some/file/path.jar!/
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path
+     * <tr>
+     * <td>/some/file/path.jar!/some/default
+     * <td>/some/entry/path
+     * <td>/some/file/path.jar!/some/entry/path </table>
+     *
+     * @param url The <code>URL</code> whose file part is used
+     * @param spec The specification to merge with the file part
+     * @throws NullPointerException If the specification starts with a slash and
+     *             the URL does not contain a slash bang or if the specification
+     *             does not start with a slash and the file part of the URL does
+     *             is not an absolute file path.
+     */
+    private String parseContextSpec(URL url, String spec) {
+
+        // spec is relative to this file
+        String file = url.getFile();
+
+        // if the spec is absolute path, it is an absolute entry spec
+        if (spec.startsWith("/")) {
+
+            // assert the bang slash in the original URL
+            int bangSlash = indexOfBangSlash(file);
+            if (bangSlash == -1) {
+                throw new NullPointerException("malformed context url:" + url
+                    + ": no !/");
+            }
+
+            // remove bang slash part from the original file
+            file = file.substring(0, bangSlash);
+        }
+
+        // if the file is not a directory and spec is a relative file path
+        if (!file.endsWith("/") && !spec.startsWith("/")) {
+
+            // find the start of the file name in the url file path
+            int lastSlash = file.lastIndexOf('/');
+            if (lastSlash == -1) {
+                throw new NullPointerException("malformed context url:" + url);
+            }
+
+            // cut off the file name from the URL file path
+            file = file.substring(0, lastSlash + 1);
+        }
+
+        // concat file part and the spec now
+        return file + spec;
+    }
+
+    public String canonizeString(String s) {
+        int i = 0;
+        int k = s.length();
+        while ((i = s.indexOf("/../")) >= 0)
+            if ((k = s.lastIndexOf('/', i - 1)) >= 0)
+                s = s.substring(0, k) + s.substring(i + 3);
+            else
+                s = s.substring(i + 3);
+        while ((i = s.indexOf("/./")) >= 0)
+            s = s.substring(0, i) + s.substring(i + 2);
+        while (s.endsWith("/..")) {
+            int j = s.indexOf("/..");
+            int l;
+            if ((l = s.lastIndexOf('/', j - 1)) >= 0)
+                s = s.substring(0, l + 1);
+            else
+                s = s.substring(0, j);
+        }
+        if (s.endsWith("/.")) s = s.substring(0, s.length() - 1);
+        return s;
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
new file mode 100644
index 0000000..9e1471a
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
@@ -0,0 +1,778 @@
+/*
+ * 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.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.classloader.internal.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * The <code>JCRURLConnection</code> is the <code>URLConnection</code>
+ * implementation to access the data addressed by a JCR Repository URL.
+ * <p>
+ * As the primary use of a <code>URLConnection</code> and thus the
+ * <code>JCRURLConnection</code> is to provide access to the content of a
+ * resource identified by the URL, it is the primary task of this class to
+ * identify and access a repository <code>Property</code> based on the URL. This
+ * main task is executed in the {@link #connect()} method.
+ * <p>
+ * Basically the guideposts to access content from a JCR Repository URL are
+ * the following:
+ * <ul>
+ * <li>The URL must ultimately resolve to a repository property to provide
+ *      content.
+ * <li>If the URL itself is the path to a property, that property is used to
+ *      provide the content.
+ * <li>If the URL is a path to a node, either the
+ *      <code>jcr:content/jcr:data</code> or <code>jcr:data</code> property is
+ *      used or the primary item chain starting with this node is followed until
+ *      no further primary items exist. If the final item is a property, that
+ *      property is used to provide the content.
+ * <li>If neither of the above methods resolve to a property, the
+ *      {@link #connect()} fails and access to the content is not possible.
+ * </ul>
+ * <p>
+ * After having connected the property is available through the
+ * {@link #getProperty()} method. Other methods exist to retrieve repository
+ * related information defined when creating the URL: {@link #getSession()} to
+ * retrieve the session of the URL, {@link #getPath()} to retrieve the path
+ * with which the URL was created and {@link #getItem()} to retrieve the item
+ * with which the URL was created. The results of calling {@link #getProperty()}
+ * and {@link #getItem()} will be the same if the URL directly addressed the
+ * property. If the URL addressed the node whose primary item chain ultimately
+ * resolved to the property, the {@link #getItem()} will return the node and
+ * {@link #getProperty()} will return the resolved property.
+ * <p>
+ * A note on the <code>InputStream</code> available from
+ * {@link #getInputStream()}: Unlike other implementations - for example
+ * for <code>file:</code> or <code>http:</code> URLs - which return the same
+ * stream on each call, this implementation returns a new stream on each
+ * invocation.
+ * <p>
+ * The following header fields are implemented by this class:
+ * <dl>
+ * <dt><code>Content-Length</code>
+ * <dd>The size of the content is filled from the <code>Property.getLength()</code>
+ *      method, which returns the size in bytes of the property's value for
+ *      binary values and the number of characters used for the string
+ *      representation of the value for all other value types.
+ *
+ * <dt><code>Content-Type</code>
+ * <dd>The content type is retrieved from the <code>jcr:mimeType</code>
+ *      property of the property's parent node if existing. Otherwise the
+ *      <code>guessContentTypeFromName</code> method is called on the
+ *      {@link #getPath() path}. If this does not yield a content type, it is
+ *      set to <code>application/octet-stream</code> for binary properties and
+ *      to <code>text/plain</code> for other types.
+ *
+ * <dt><code>Content-Enconding</code>
+ * <dd>The content encoding is retrieved from the <code>jcr:econding</code>
+ *      property of the property's parent node if existing. Otherwise this
+ *      header field remains undefined (aka <code>null</code>).
+ *
+ * <dt><code>Last-Modified</code>
+ * <dd>The last modified type is retrieved from the <code>jcr:lastModified</code>
+ *      property of the property's parent node if existing. Otherwise the last
+ *      modification time is set to zero.
+ * </dl>
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ */
+public class JCRURLConnection extends URLConnection {
+
+    /** Default logging */
+    private static final Logger log =
+        LoggerFactory.getLogger(JCRURLConnection.class);
+
+    /**
+     * The name of the header containing the content size (value is
+     * "content-length").
+     */
+    protected static final String CONTENT_LENGTH = "content-length";
+
+    /**
+     * The name of the header containing the MIME type of the content (value is
+     * "content-type").
+     */
+    protected static final String CONTENT_TYPE = "content-type";
+
+    /**
+     * The name of the header containing the content encoding (value is
+     * "content-encoding").
+     */
+    protected static final String CONTENT_ENCODING = "content-encoding";
+
+    /**
+     * The name of the header containing the last modification time stamp of
+     * the content (value is "last-modified").
+     */
+    protected static final String LAST_MODIFIED = "last-modified";
+
+    /**
+     * The default content type name for binary properties accessed by this
+     * connection (value is "application/octet-stream").
+     * @see #connect()
+     */
+    protected static final String APPLICATION_OCTET = "application/octet-stream";
+
+    /**
+     * The default content type name for non-binary properties accessed by this
+     * connection (value is "text/plain").
+     * @see #connect()
+     */
+    protected static final String TEXT_PLAIN = "text/plain";
+
+    /**
+     * The handler associated with the URL of this connection. This handler
+     * provides the connection with access to the repository and the item
+     * underlying the URL.
+     */
+    private final JCRURLHandler handler;
+
+    /**
+     * The {@link FileParts} encapsulating the repository name, workspace name,
+     * item path and optional archive entry path contained in the file part
+     * of the URL. This field is set on-demand by the {@link #getFileParts()}
+     * method.
+     *
+     * @see #getFileParts()
+     */
+    private FileParts fileParts;
+
+    /**
+     * The <code>Item</code> addressed by the path of this connection's URL.
+     * This field is set on-demand by the {@link #getItem()} method.
+     *
+     * @see #getItem()
+     */
+    private Item item;
+
+    /**
+     * The <code>Property</code> associated with the URLConnection. The field
+     * is only set after the connection has been successfully opened.
+     *
+     * @see #getProperty()
+     * @see #connect()
+     */
+    private Property property;
+
+    /**
+     * The (guessed) content type of the data. Currently the content type is
+     * guessed based on the path name of the page or the binary attribute of the
+     * atom.
+     * <p>
+     * Implementations are free to decide, how to define the content type. But
+     * they are required to set the type in the {@link #connect(Ticket)}method.
+     *
+     * @see #getContentType()
+     * @see #connect()
+     */
+    private String contentType;
+
+    /**
+     * The (guessed) content encoding of the data. Currently the content type is
+     * guessed based on the path name of the page or the binary attribute of the
+     * atom.
+     * <p>
+     * Implementations are free to decide, how to define the content type. But
+     * they are required to set the type in the {@link #connect(Ticket)}method.
+     *
+     * @see #getContentEncoding()
+     * @see #connect()
+     */
+    private String contentEncoding;
+
+    /**
+     * The content lentgh of the data, which is the size field of the atom
+     * status information of the base atom.
+     * <p>
+     * Implementations are free to decide, how to define the content length. But
+     * they are required to set the type in the {@link #connect(Ticket)}method.
+     *
+     * @see #getContentLength()
+     * @see #connect()
+     */
+    private int contentLength;
+
+    /**
+     * The last modification time in milliseconds since the epoch (1970/01/01)
+     * <p>
+     * Implementations are free to decide, how to define the last modification
+     * time. But they are required to set the type in the
+     * {@link #connect(Ticket)}method.
+     *
+     * @see #getLastModified()
+     * @see #connect()
+     */
+    private long lastModified;
+
+    /**
+     * Creates an instance of this class for the given <code>url</code>
+     * supported by the <code>handler</code>.
+     *
+     * @param url The URL to base the connection on.
+     * @param handler The URL handler supporting the given URL.
+     */
+    JCRURLConnection(URL url, JCRURLHandler handler) {
+        super(url);
+        this.handler = handler;
+    }
+
+    /**
+     * Returns the current session of URL.
+     * <p>
+     * Calling this method does not require this connection being connected.
+     */
+    public Session getSession() {
+        return handler.getSession();
+    }
+
+    /**
+     * Returns the path to the repository item underlying the URL of this
+     * connection.
+     * <p>
+     * Calling this method does not require this connection being connected.
+     */
+    public String getPath() {
+        return getFileParts().getPath();
+    }
+
+    /**
+     * Returns the repository item underlying the URL of this connection
+     * retrieved through the path set on the URL.
+     * <p>
+     * Calling this method does not require this connection being connected.
+     *
+     * @throws IOException If the item has to be retrieved from the repository
+     *      <code>Session</code> of this connection and an error occurrs. The
+     *      cause of the exception will refer to the exception thrown from the
+     *      repository. If the path addresses a non-existing item, the cause
+     *      will be a <code>PathNotFoundException</code>.
+     */
+    public Item getItem() throws IOException {
+        if (item == null) {
+            try {
+                item = getSession().getItem(getPath());
+            } catch (RepositoryException re) {
+                throw failure("getItem", re.toString(), re);
+            }
+        }
+
+        return item;
+    }
+
+    /**
+     * Returns the repository <code>Property</code> providing the contents of
+     * this connection.
+     * <p>
+     * Calling this method forces the connection to be opened by calling the
+     * {@link #connect()} method.
+     *
+     * @throws IOException May be thrown by the {@link #connect()} method called
+     *      by this method.
+     *
+     * @see #connect()
+     */
+    public Property getProperty() throws IOException {
+        // connect to set the property value
+        connect();
+
+        return property;
+    }
+
+    //---------- URLConnection overwrites -------------------------------------
+
+    /**
+     * Connects to the URL setting the header fields and preparing for the
+     * {@link #getProperty()} and {@link #getInputStream()} methods.
+     * <p>
+     * The following algorithm is applied:
+     * <ol>
+     * <li>The repository item is retrieved from the URL's
+     *      <code>URLHandler</code>.
+     * <li>If the item is a node, the <code>getPrimaryItem</code> method is
+     *      called on that node. If the node has no primary item, the connection
+     *      fails.
+     * <li>If the item - either from the handler or after calling
+     *      <code>getPrimaryItem</code> is still a node, this method fails
+     *      because a <code>Property</code> is required for a successfull
+     *      connection.
+     * <li>If the property found above is a multi-valued property, connection
+     *      fails, because multi-valued properties are not currently supported.
+     * <li>The content length header field is set from the property length
+     *      (<code>Property.getLength())</code>).
+     * <li>The header fields for the content type, content encoding and last
+     *      modification time are set from the <code>jcr:mimeType</code>,
+     *      <code>jcr:encoding</code>, and <code>jcr:lastModification</code>
+     *      properties of the property's parent node if existing. Otherwise the
+     *      content encoding field is set to <code>null</code> and the last
+     *      modification time is set to zero. The content type field is guessed
+     *      from the name of the URL item. If the content type cannot be
+     *      guessed, it is set to <code>application/octet-stream</code> if the
+     *      property is of binary type or <code>text/plain</code> otherwise.
+     * </ol>
+     * <p>
+     * When this method successfully returns, this connection is considered
+     * connected. In case of an exception thrown, the connection is not
+     * connected.
+     *
+     * @throws IOException if an error occurrs retrieving the data property or
+     *      any of the header field value properties or if any other errors
+     *      occurrs. Any cuasing exception is set as the cause of this
+     *      exception.
+     */
+    public synchronized void connect() throws IOException {
+        // todo: The ContentBus URL must also contain version information on
+        if (!connected) {
+
+            // Get hold of the data
+            try {
+                // resolve the URLs item to a property
+                Property property = Util.getProperty(getItem());
+                if (property == null) {
+                    throw failure("connect",
+                        "Multivalue property not supported", null);
+                }
+
+                // values to set later
+                String contentType;
+                String contentEncoding = null; // no defined content encoding
+                int contentLength = (int) property.getLength();
+                long lastModified;
+
+                Node parent = property.getParent();
+                if (parent.hasProperty("jcr:lastModified")) {
+                    lastModified = parent.getProperty("jcr:lastModified").getLong();
+                } else {
+                    lastModified = 0;
+                }
+                
+                if (parent.hasProperty("jcr:mimeType")) {
+                    contentType = parent.getProperty("jcr:mimeType").getString();
+                } else {
+                    contentType = guessContentTypeFromName(getItem().getName());
+                    if (contentType == null) {
+                        contentType = (property.getType() == PropertyType.BINARY)
+                                ? APPLICATION_OCTET
+                                : TEXT_PLAIN;
+                    }
+                }
+                
+                if (parent.hasProperty("jcr:encoding")) {
+                    contentEncoding = parent.getProperty("jcr:encoding").getString();
+                } else {
+                    contentEncoding = null;
+                }
+
+                log.debug(
+                    "connect: Using property '{}' with content type '{}' for {} bytes",
+                    new Object[] { property.getPath(), contentType,
+                        new Integer(contentLength) });
+
+                // set the fields
+                setProperty(property);
+                setContentType(contentType);
+                setContentEncoding(contentEncoding);
+                setContentLength(contentLength);
+                setLastModified(lastModified);
+
+                // mark connection open
+                connected = true;
+
+            } catch (RepositoryException re) {
+                throw failure("connect", re.toString(), re);
+            }
+        }
+    }
+
+    /**
+     * Returns an input stream that reads from this open connection.
+     * <p>
+     * <b>NOTES:</b>
+     * <ul>
+     * <li>Each call to this method returns a new <code>InputStream</code>.
+     * <li>Do not forget to close the return stream when not used anymore for
+     *      the system to be able to free resources.
+     * </ul>
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @throws IOException if an error occurrs opening the connection through
+     *      {@link #connect()} or creating the <code>InputStream</code> on the
+     *      repository <code>Property</code>.
+     *
+     * @see #connect()
+     */
+    public InputStream getInputStream() throws IOException {
+        try {
+            return getProperty().getStream();
+        } catch (RepositoryException re) {
+            throw failure("getInputStream", re.toString(), re);
+        }
+    }
+
+    /**
+     * Gets the named header field. This implementation only supports the
+     * Content-Type, Content-Encoding, Content-Length and Last-Modified header
+     * fields. All other names return <code>null</code>.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @param s The name of the header field value to return.
+     *
+     * @return The corresponding value or <code>null</code> if not one of the
+     *      supported fields or the named field's value cannot be retrieved
+     *      from the data source.
+     *
+     * @see #connect()
+     */
+    public String getHeaderField(String s) {
+        try {
+            connect();
+            if (CONTENT_LENGTH.equalsIgnoreCase(s)) {
+                return String.valueOf(contentLength);
+            } else if (CONTENT_TYPE.equalsIgnoreCase(s)) {
+                return contentType;
+            } else if (LAST_MODIFIED.equalsIgnoreCase(s)) {
+                return String.valueOf(lastModified);
+            } else if (CONTENT_ENCODING.equalsIgnoreCase(s)) {
+                return contentEncoding;
+            }
+        } catch (IOException ioe) {
+            log.info("getHeaderField: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the header field with the given index. As with
+     * {@link #getHeaderField(String)} only Content-Length, Content-Type,
+     * Content-Encoding, and Last-Modified are supported. All indexes other
+     * than 0, 1, 2 or 3 will return <code>null</code>.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @param i The index of the header field value to return.
+     *
+     * @return The corresponding value or <code>null</code> if not one of the
+     *      supported fields or the known field's value cannot be retrieved
+     *      from the data source.
+     *
+     * @see #connect()
+     */
+    public String getHeaderField(int i) {
+        try {
+            connect();
+            if (i == 0) {
+                return String.valueOf(contentLength);
+            } else if (i == 1) {
+                return contentType;
+            } else if (i == 2) {
+                return String.valueOf(lastModified);
+            } else if (i == 3) {
+                return contentEncoding;
+            }
+        } catch (IOException ioe) {
+            log.info("getHeaderField: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the name of the header field with the given index. As with
+     * {@link #getHeaderField(String)} only Content-Length, Content-Type,
+     * Content-Encoding and Last-Modified are supported. All indexes other than
+     * 0, 1, 2 or 3 will return <code>null</code>.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @param i The index of the header field name to return.
+     * @return The corresponding name or <code>null</code> if not one of the
+     *         supported fields.
+     *
+     * @see #connect()
+     */
+    public String getHeaderFieldKey(int i) {
+        try {
+            connect();
+            if (i == 0) {
+                return CONTENT_LENGTH;
+            } else if (i == 1) {
+                return CONTENT_TYPE;
+            } else if (i == 2) {
+                return LAST_MODIFIED;
+            } else if (i == 3) {
+                return CONTENT_ENCODING;
+            }
+        } catch (IOException ioe) {
+            log
+                .info("getHeaderFieldKey: Problem connecting: "
+                    + ioe.toString());
+            log.debug("dump", ioe);
+        }
+        return null;
+    }
+
+    /**
+     * Returns an unmodifiable map of all header fields. Each entry is indexed
+     * with a string key naming the field. The entry's value is an unmodifiable
+     * list of the string values of the respective header field.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return An unmodifiable map of header fields and their values. The map
+     *      will be empty if an error occurrs connecting through
+     *      {@link #connect()}.
+     *
+     * @see #connect()
+     */
+    public Map getHeaderFields() {
+        Map fieldMap = new HashMap();
+
+        try {
+            connect();
+            fieldMap.put(CONTENT_LENGTH, toList(String.valueOf(contentLength)));
+            fieldMap.put(CONTENT_TYPE, toList(contentType));
+            fieldMap.put(LAST_MODIFIED, toList(String.valueOf(lastModified)));
+
+            // only include if not null))
+            if (contentEncoding != null) {
+                fieldMap.put(CONTENT_ENCODING, toList(contentEncoding));
+            }
+        } catch (IOException ioe) {
+            log.info("getHeaderFields: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return Collections.unmodifiableMap(fieldMap);
+    }
+
+    /**
+     * Returns the content type of the data as a string. This is just a
+     * perfomance convenience overwrite of the base class implementation.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The content length of the data or <code>null</code> if the
+     *      content type cannot be derived from the data source.
+     *
+     * @see #connect()
+     */
+    public String getContentType() {
+        try {
+            connect();
+            return contentType;
+        } catch (IOException ioe) {
+            log.info("getContentType: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the content encoding of the data as a string. This is just a
+     * perfomance convenience overwrite of the base class implementation.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The content encoding of the data or <code>null</code> if the
+     *      content encoding cannot be derived from the data source.
+     *
+     * @see #connect()
+     */
+    public String getContentEncoding() {
+        try {
+            connect();
+            return contentEncoding;
+        } catch (IOException ioe) {
+            log.info("getContentEncoding: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the content length of the data as an number. This is just a
+     * perfomance convenience overwrite of the base class implementation.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return The content length of the data or -1 if the content length cannot
+     *         be derived from the data source.
+     *
+     * @see #connect()
+     */
+    public int getContentLength() {
+        try {
+            connect();
+            return contentLength;
+        } catch (IOException ioe) {
+            log.info("getContentLength: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the value of the <code>last-modified</code> header field. The
+     * result is the number of milliseconds since January 1, 1970 GMT.
+     * <p>
+     * Calling this method implicitly calls {@link #connect()} to ensure the
+     * connection is open.
+     *
+     * @return the date the resource referenced by this
+     *         <code>URLConnection</code> was last modified, or -1 if not
+     *         known.
+     *
+     * @see #connect()
+     */
+    public long getLastModified() {
+        try {
+            connect();
+            return lastModified;
+        } catch (IOException ioe) {
+            log.info("getLastModified: Problem connecting: " + ioe.toString());
+            log.debug("dump", ioe);
+        }
+        return -1;
+    }
+
+    //---------- implementation helpers ----------------------------------------
+
+    /**
+     * Returns the URL handler of the URL of this connection.
+     */
+    protected JCRURLHandler getHandler() {
+        return handler;
+    }
+
+    /**
+     * Returns the {@link FileParts} object which contains the decomposed file
+     * part of this connection's URL.
+     */
+    FileParts getFileParts() {
+        if (fileParts == null) {
+            fileParts = new FileParts(getURL().getFile());
+        }
+
+        return fileParts;
+    }
+
+    /**
+     * @param contentEncoding The contentEncoding to set.
+     */
+    protected void setContentEncoding(String contentEncoding) {
+        this.contentEncoding = contentEncoding;
+    }
+
+    /**
+     * @param contentLength The contentLength to set.
+     */
+    protected void setContentLength(int contentLength) {
+        this.contentLength = contentLength;
+    }
+
+    /**
+     * @param contentType The contentType to set.
+     */
+    protected void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
+
+    /**
+     * @param lastModified The lastModified to set.
+     */
+    protected void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * @param property The property to set.
+     */
+    protected void setProperty(Property property) {
+        this.property = property;
+    }
+
+    //---------- internal -----------------------------------------------------
+
+    /**
+     * Logs the message and returns an IOException to be thrown by the caller.
+     * The log message contains the caller name, the external URL form and the
+     * message while the IOException is only based on the external URL form and
+     * the message given.
+     *
+     * @param method The method in which the error occurred. This is used for
+     *            logging.
+     * @param message The message to log and set in the exception
+     * @param cause The cause of failure. May be <code>null</code>.
+     *
+     * @return The IOException the caller may throw.
+     */
+    protected IOException failure(String method, String message, Throwable cause) {
+        log.info(method + ": URL: " + url.toExternalForm() + ", Reason: "
+            + message);
+
+        if (cause != null) {
+            log.debug("dump", cause);
+        }
+
+        IOException ioe = new IOException(url.toExternalForm() + ": " + message);
+        ioe.initCause(cause);
+        return ioe;
+    }
+
+    /**
+     * Returns an unmodifiable list containing just the given string value.
+     */
+    private List toList(String value) {
+        String[] values = { value };
+        List valueList = Arrays.asList(values);
+        return Collections.unmodifiableList(valueList);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java
new file mode 100644
index 0000000..1fa72c6
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLHandler.java
@@ -0,0 +1,147 @@
+/*
+ * 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.net;
+
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>JCRURLHandler</code> is the <code>URLStreamHandler</code> for
+ * JCR Repository URLs identified by the scheme <code>jcr</code>.
+ * <p>
+ * JCR Repository URLs have not been standardized yet and may only be created
+ * in the context of an existing <code>Session</code>. Therefore this handler
+ * is not globally available and JCR Repository URLs may only be created through
+ * the factory methods in the {@link org.apache.sling.jcr.classloader.internal.net.URLFactory}
+ * class.
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ *
+ * @see org.apache.sling.jcr.classloader.internal.net.JCRURLConnection
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory
+ * @see org.apache.sling.jcr.classloader.internal.net.URLFactory#createURL(Session, String)
+ */
+class JCRURLHandler extends URLStreamHandler {
+
+    /**
+     * The session used to create this handler, which is also used to open
+     * the connection object.
+     *
+     * @see #getSession()
+     */
+    private final Session session;
+
+    /**
+     * Creates a new instance of the <code>JCRURLHandler</code> with the
+     * given session.
+     *
+     * @param session The <code>Session</code> supporting this handler. This
+     *      must not be <code>null</code>.
+     *
+     * @throws NullPointerException if <code>session</code> is <code>null</code>.
+     */
+    JCRURLHandler(Session session) {
+        if (session == null) {
+            throw new NullPointerException("session");
+        }
+
+        this.session = session;
+    }
+
+    /**
+     * Returns the session supporting this handler.
+     */
+    Session getSession() {
+        return session;
+    }
+
+    //---------- URLStreamHandler abstracts ------------------------------------
+
+    /**
+     * Gets a connection object to connect to an JCR Repository URL.
+     *
+     * @param url The JCR Repository URL to connect to.
+     *
+     * @return An instance of the {@link JCRURLConnection} class.
+     *
+     * @see JCRURLConnection
+     */
+    protected URLConnection openConnection(URL url) {
+        return new JCRURLConnection(url, this);
+    }
+
+    /**
+     * Checks the new <code>authority</code> and <code>path</code> before
+     * actually setting the values on the url calling the base class
+     * implementation.
+     * <p>
+     * We check the authority to not have been modified from the original URL,
+     * as the authority is dependent on the repository <code>Session</code> on
+     * which this handler is based and which was used to create the original
+     * URL. Likewise the repository and workspace name parts of the path must
+     * not have changed.
+     *
+     * @param u the URL to modify.
+     * @param protocol the protocol name.
+     * @param host the remote host value for the URL.
+     * @param port the port on the remote machine.
+     * @param authority the authority part for the URL.
+     * @param userInfo the userInfo part of the URL.
+     * @param path the path component of the URL.
+     * @param query the query part for the URL.
+     * @param ref the reference.
+     *
+     * @throws IllegalArgumentException if the authority or the repository name
+     *             or workspace name parts of the path has changed.
+     */
+    protected void setURL(URL u, String protocol, String host, int port,
+        String authority, String userInfo, String path, String query, String ref) {
+
+        // check for authority
+        if (u.getAuthority() != authority) {
+            if (u.getAuthority() == null) {
+                if (authority != null) {
+                    throw new IllegalArgumentException("Authority " +
+                        authority + " not supported by this handler");
+                }
+            } else if (!u.getAuthority().equals(authority)) {
+                throw new IllegalArgumentException("Authority " +
+                    authority + " not supported by this handler");
+            }
+        }
+
+        // check for repository and/or workspace modifications
+        FileParts newParts = new FileParts(path);
+        if (!"_".equals(newParts.getRepository())) {
+            throw new IllegalArgumentException("Repository " +
+                newParts.getRepository() + " not supported by this handler");
+        }
+        if (!session.getWorkspace().getName().equals(newParts.getWorkspace())) {
+            throw new IllegalArgumentException("Workspace " +
+                newParts.getWorkspace() + " not supported by this handler");
+        }
+
+        // finally set the new values on the URL
+        super.setURL(u, protocol, host, port, authority, userInfo, path, query,
+            ref);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java
new file mode 100644
index 0000000..834480a
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/URLFactory.java
@@ -0,0 +1,99 @@
+/*
+ * 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.net;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.jcr.Session;
+
+/**
+ * The <code>URLFactory</code> class provides factory methods for creating
+ * JCR Repository and JCRJar URLs.
+ * <p>
+ * This class is not intended to be subclassed or instantiated by clients.
+ *
+ * @author Felix Meschberger
+ */
+public final class URLFactory {
+
+    /**
+     * The scheme for JCR Repository URLs (value is "jcr").
+     */
+    public static final String REPOSITORY_SCHEME = "jcr";
+
+    /**
+     * The scheme for JCRJar URLs (value is "jar").
+     */
+    public static final String REPOSITORY_JAR_SCHEME = "jar";
+
+    /** Private default constructor, not to be instantiated */
+    private URLFactory() {
+    }
+
+    /**
+     * Creates a new JCR Repository URL for the given session and item path.
+     *
+     * @param session The repository session providing access to the item.
+     * @param path The absolute path to the item. This must be an absolute
+     *      path with a leading slash character. If this is <code>null</code>
+     *      the root node path - <code>/</code> - is assumed.
+     *
+     * @return The JCR Repository URL
+     *
+     * @throws MalformedURLException If an error occurrs creating the
+     *      <code>URL</code> instance.
+     */
+    public static URL createURL(Session session, String path)
+        throws MalformedURLException {
+
+        return new URL(REPOSITORY_SCHEME, "", -1,
+            new FileParts(session, path, null).toString(),
+            new JCRURLHandler(session));
+    }
+
+    /**
+     * Creates a new JCRJar URL for the given session, archive and entry.
+     *
+     * @param session The repository session providing access to the archive.
+     * @param path The absolute path to the archive. This must either be the
+     *      property containing the archive or an item which resolves to such
+     *      a property through its primary item chain. This must be an absolute
+     *      path with a leading slash character. If this is <code>null</code>
+     *      the root node path - <code>/</code> - is assumed.
+     * @param entry The entry within the archive. If <code>null</code>, the URL
+     *      provides access to the archive itself.
+     *
+     * @return The JCRJar URL
+     *
+     * @throws MalformedURLException If an error occurrs creating the
+     *      <code>URL</code> instance.
+     */
+    public static URL createJarURL(Session session, String path, String entry)
+        throws MalformedURLException {
+
+        JCRJarURLHandler handler = new JCRJarURLHandler(session);
+        String file = createURL(session, path).toExternalForm();
+
+        // append entry spec if not null
+        if (entry != null) {
+            file += "!/" + entry;
+        }
+
+        return new URL(REPOSITORY_JAR_SCHEME, "", -1, file, handler);
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 12/18: SLING-1311 : Rare exception "Servlet class not found" during compilation into the repository - synchronized path creation.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 8fdf17890eef016ec85759f8e7c83efed8ecec73
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jan 26 10:47:50 2010 +0000

    SLING-1311 : Rare exception "Servlet class not found" during compilation into the repository - synchronized path creation.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@903164 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/DynamicClassLoaderProviderImpl.java   | 29 ++++++++++++++++++----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
index a614826..20c346f 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
@@ -69,6 +69,9 @@ public class DynamicClassLoaderProviderImpl
 
     public static final String CLASS_PATH_DEFAULT = "/var/classes";
 
+    /** Node type for packages/folders. */
+    private static final String NT_FOLDER = "nt:folder";
+
     /**
      * @scr.property valueRef="OWNER_DEFAULT"
      */
@@ -89,6 +92,7 @@ public class DynamicClassLoaderProviderImpl
      */
     private SlingRepository repository;
 
+    /** The configured class paths. */
     private String[] classPath;
 
     /** @scr.reference policy="dynamic" */
@@ -97,6 +101,9 @@ public class DynamicClassLoaderProviderImpl
     /** The read session. */
     private Session readSession;
 
+    /**
+     * Return a new session (for writing).
+     */
     Session getSession() throws RepositoryException {
         // get an administrative session for potentiall impersonation
         final Session admin = this.repository.loginAdministrative(null);
@@ -192,12 +199,16 @@ public class DynamicClassLoaderProviderImpl
         return false;
     }
 
-    private static final String NT_FOLDER = "nt:folder";
-
     /**
      * Creates a folder hierarchy in the repository.
+     * We synchronize this method to reduce potential conflics.
+     * Although each write uses its own session it might occur
+     * that more than one session tries to create the same path
+     * (or parent path) at the same time. By synchronizing this
+     * we avoid this situation - however this method is written
+     * in a failsafe manner anyway.
      */
-    private boolean mkdirs(final Session session, String path) {
+    private synchronized boolean mkdirs(final Session session, String path) {
         try {
             // quick test
             if (session.itemExists(path) && session.getItem(path).isNode()) {
@@ -253,6 +264,11 @@ public class DynamicClassLoaderProviderImpl
         return false;
     }
 
+    /**
+     * Helper method to clean the path.
+     * It replaces backslashes with slashes and cuts off trailing spaces.
+     * It uses the first configured class path to access the path.
+     */
     private String cleanPath(String path) {
         // replace backslash by slash
         path = path.replace('\\', '/');
@@ -409,7 +425,7 @@ public class DynamicClassLoaderProviderImpl
 
     /**
      * Activate this component.
-     * @param componentContext
+     * @param componentContext The component context.
      */
     protected void activate(final ComponentContext componentContext) {
         @SuppressWarnings("unchecked")
@@ -424,7 +440,7 @@ public class DynamicClassLoaderProviderImpl
 
     /**
      * Deactivate this component
-     * @param componentContext
+     * @param componentContext The component context.
      */
     protected void deactivate(final ComponentContext componentContext) {
         if ( this.readSession != null ) {
@@ -447,6 +463,9 @@ public class DynamicClassLoaderProviderImpl
         return this.classPath;
     }
 
+    /**
+     * Return the read session.
+     */
     public synchronized Session getReadSession() throws RepositoryException {
         // check current session
         if (this.readSession != null) {

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 06/18: SLING-1311 : Rare exception "Servlet class not found" during compilation into the repository - improve path creation.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit c5e90a28592fe9e5d03aab7441c311b455fabe1d
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 25 07:49:31 2010 +0000

    SLING-1311 : Rare exception "Servlet class not found" during compilation into the repository - improve path creation.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@902724 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/DynamicClassLoaderProviderImpl.java   | 102 +++++++++++----------
 1 file changed, 52 insertions(+), 50 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
index 6b89596..a614826 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
@@ -42,7 +42,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The <code>DynamicClassLoaderProviderImpl</code> TODO
+ * The <code>DynamicClassLoaderProviderImpl</code> provides
+ * a class loader which loads classes from configured paths
+ * in the repository.
+ * In addition it implements {@link ClassLoaderWriter} and
+ * supports writing class files to the repository.
  *
  * @scr.component label="%loader.name"
  *      description="%loader.description"
@@ -147,7 +151,6 @@ public class DynamicClassLoaderProviderImpl
         } catch (RepositoryException re) {
             log.error("Cannot remove " + name, re);
         } finally {
-            checkNode(parentNode, name);
             if ( session != null ) {
                 session.logout();
             }
@@ -189,11 +192,12 @@ public class DynamicClassLoaderProviderImpl
         return false;
     }
 
+    private static final String NT_FOLDER = "nt:folder";
+
     /**
      * Creates a folder hierarchy in the repository.
      */
     private boolean mkdirs(final Session session, String path) {
-        Node parentNode = null;
         try {
             // quick test
             if (session.itemExists(path) && session.getItem(path).isNode()) {
@@ -209,46 +213,46 @@ public class DynamicClassLoaderProviderImpl
                 } else if (current.hasNode(names[i])) {
                     current = current.getNode(names[i]);
                 } else {
-                    if (parentNode == null) {
-                        parentNode = current;
-                    }
+                    final Node parentNode = current;
                     try {
-                        current.addNode(names[i], "nt:folder");
+                        // adding the node could cause an exception
+                        // for example if another thread tries to
+                        // create the node "at the same time"
+                        current = parentNode.addNode(names[i], NT_FOLDER);
                         session.save();
                     } catch (RepositoryException re) {
-                        // we ignore this as this might be a concurrent modification!
+                        // let's first refresh the session
+                        // we don't catch an exception here, because if
+                        // session refresh fails, we might have a serious problem!
                         session.refresh(false);
+                        // let's check if the node is available now
+                        if ( parentNode.hasNode(names[i]) ) {
+                            current = parentNode.getNode(names[i]);
+                        } else {
+                            // we try it one more time to create the node - and fail otherwise
+                            current = parentNode.addNode(names[i], NT_FOLDER);
+                            session.save();
+                        }
                     }
-                    current = current.getNode(names[i]);
                 }
             }
 
-            if (parentNode != null) {
-                session.save();
-                return true;
-            }
+            return true;
 
         } catch (RepositoryException re) {
-            log.error("Cannot create folder path " + path, re);
-        } finally {
-            checkNode(parentNode, path);
+            log.error("Cannot create folder path:" + path, re);
+            // discard changes
+            try {
+                session.refresh(false);
+            } catch (RepositoryException e) {
+                // we simply ignore this
+            }
         }
 
         // false in case of error or no need to create
         return false;
     }
 
-    private void checkNode(Node node, String path) {
-        if (node != null && node.isModified()) {
-            try {
-                node.refresh(false);
-            } catch (RepositoryException re) {
-                log.error("Cannot refresh node for " + path
-                    + " after failed save", re);
-            }
-        }
-    }
-
     private String cleanPath(String path) {
         // replace backslash by slash
         path = path.replace('\\', '/');
@@ -276,25 +280,31 @@ public class DynamicClassLoaderProviderImpl
             this.fileName = fileName;
         }
 
+        /**
+         * @see java.io.ByteArrayOutputStream#close()
+         */
         public void close() throws IOException {
             super.close();
 
-            Node parentNode = null;
             Session session = null;
             try {
+                // get an own session for writing
                 session = repositoryOutputProvider.getSession();
                 final int lastPos = fileName.lastIndexOf('/');
+                final String path = (lastPos == -1 ? null : fileName.substring(0, lastPos));
+                final String name = (lastPos == -1 ? fileName : fileName.substring(lastPos + 1));
                 if ( lastPos != -1 ) {
-                    repositoryOutputProvider.mkdirs(session, fileName.substring(0, lastPos));
+                    if ( !repositoryOutputProvider.mkdirs(session, path) ) {
+                        throw new IOException("Unable to create path for " + path);
+                    }
                 }
                 Node fileNode = null;
                 Node contentNode = null;
+                Node parentNode = null;
                 if (session.itemExists(fileName)) {
-                    Item item = session.getItem(fileName);
+                    final Item item = session.getItem(fileName);
                     if (item.isNode()) {
-                        Node node = item.isNode()
-                                ? (Node) item
-                                : item.getParent();
+                        final Node node = item.isNode() ? (Node) item : item.getParent();
                         if ("jcr:content".equals(node.getName())) {
                             // replace the content properties of the jcr:content
                             // node
@@ -313,23 +323,20 @@ public class DynamicClassLoaderProviderImpl
                     } else {
                         // replace property with an nt:file node (if possible)
                         parentNode = item.getParent();
-                        String name = item.getName();
-                        fileNode = parentNode.addNode(name, "nt:file");
                         item.remove();
+                        session.save();
+                        fileNode = parentNode.addNode(name, "nt:file");
                     }
                 } else {
-                    int lastSlash = fileName.lastIndexOf('/');
-                    if (lastSlash <= 0) {
+                    if (lastPos <= 0) {
                         parentNode = session.getRootNode();
                     } else {
-                        Item parent = session.getItem(fileName.substring(0,
-                            lastSlash));
+                        Item parent = session.getItem(path);
                         if (!parent.isNode()) {
-                            // TODO: fail
+                            throw new IOException("Parent at " + path + " is not a node.");
                         }
                         parentNode = (Node) parent;
                     }
-                    String name = fileName.substring(lastSlash + 1);
                     fileNode = parentNode.addNode(name, "nt:file");
                 }
 
@@ -345,19 +352,14 @@ public class DynamicClassLoaderProviderImpl
                     mimeType = "application/octet-stream";
                 }
 
-                contentNode.setProperty("jcr:lastModified",
-                    System.currentTimeMillis());
-                contentNode.setProperty("jcr:data", new ByteArrayInputStream(
-                    buf, 0, size()));
+                contentNode.setProperty("jcr:lastModified", System.currentTimeMillis());
+                contentNode.setProperty("jcr:data", new ByteArrayInputStream(buf, 0, size()));
                 contentNode.setProperty("jcr:mimeType", mimeType);
 
-                parentNode.save();
+                session.save();
             } catch (RepositoryException re) {
-                repositoryOutputProvider.log.error("Cannot write file " + fileName, re);
-                throw new IOException("Cannot write file " + fileName
-                    + ", reason: " + re.toString());
+                throw (IOException)new IOException("Cannot write file " + fileName + ", reason: " + re.toString()).initCause(re);
             } finally {
-                repositoryOutputProvider.checkNode(parentNode, fileName);
                 if ( session != null ) {
                     session.logout();
                 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 15/18: Use latest jcr api to decouple modules from jcr 1.0 (contained in older jcr api releases)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 9836b67775cb8f49922c51c50e4649b2282aaf5e
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sun Jan 31 12:48:17 2010 +0000

    Use latest jcr api to decouple modules from jcr 1.0 (contained in older jcr api releases)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@905019 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index e80e262..35e3b6f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,7 +86,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.api</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.6</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 13/18: SLING-1316 : Include jackrabbit classloader code to adjust it for Sling needs: Remove unused code and combine the classloader classes into a single class.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit eaea87ba6c113f871c4a53c3888a53a3f812cd85
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jan 27 09:30:55 2010 +0000

    SLING-1316 :  Include jackrabbit classloader code to adjust it for Sling needs:
    Remove unused code and combine the classloader classes into a single class.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@903578 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/classloader/internal/ClassPathEntry.java   |  12 -
 .../internal/DynamicRepositoryClassLoader.java     | 833 +++++++++++++--------
 .../internal/URLRepositoryClassLoader.java         | 794 --------------------
 3 files changed, 532 insertions(+), 1107 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java b/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java
index 3f79345..99ef875 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/ClassPathEntry.java
@@ -199,18 +199,6 @@ public final class ClassPathEntry {
     }
 
     /**
-     * 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() {
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
index 34cfc5f..3b38b95 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
@@ -16,10 +16,24 @@
  */
 package org.apache.sling.jcr.classloader.internal;
 
-import java.util.Arrays;
+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.Property;
 import javax.jcr.RepositoryException;
@@ -34,8 +48,7 @@ import org.slf4j.LoggerFactory;
 
 
 /**
- * The <code>DynamicRepositoryClassLoader</code> class extends the
- * {@link org.apache.sling.jcr.classloader.internal.URLRepositoryClassLoader} and provides the
+ * The <code>DynamicRepositoryClassLoader</code> class 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
@@ -50,27 +63,30 @@ import org.slf4j.LoggerFactory;
  * 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 {
+public final class DynamicRepositoryClassLoader
+    extends URLClassLoader implements EventListener {
+
+    /** 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)
+     */
+    private static final ClassLoaderResource NOT_FOUND_RESOURCE =
+        new ClassLoaderResource(null, "[sentinel]", null) {
+            public boolean isExpired() {
+                return false;
+            }
+        };
+
 
     /** default log category */
-    private final Logger log =
-        LoggerFactory.getLogger(this.getClass().getName());
+    private final Logger log = LoggerFactory.getLogger(this.getClass().getName());
 
     /**
      * Cache of resources used to check class loader expiry. The map is indexed
@@ -83,21 +99,57 @@ public class DynamicRepositoryClassLoader
      * @see #onEvent(EventIterator)
      * @see #findClassLoaderResource(String)
      */
-    private Map<String, ClassLoaderResource> modTimeCache;
+    private final Map<String, ClassLoaderResource> modTimeCache = new HashMap<String, ClassLoaderResource>();
 
     /**
      * Flag indicating whether there are loaded classes which have later been
      * expired (e.g. invalidated or modified)
      */
-    private boolean dirty;
+    private boolean dirty = false;
+
+    /** The registered event listeners. */
+    private EventListener[] proxyListeners;
 
     /**
-     * The list of repositories added through either the {@link #addURL} or the
-     * {@link #addHandle} method.
+     * 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[] addedRepositories;
+    private ClassPathEntry[] repository;
 
-    private EventListener[] proxyListeners;
+    /**
+     * 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 final Map<String, ClassLoaderResource> cache = new HashMap<String, ClassLoaderResource>();
+
+    /**
+     * Flag indicating whether the {@link #destroy()} method has already been
+     * called (<code>true</code>) or not (<code>false</code>)
+     */
+    private boolean destroyed = false;
 
     /**
      * Creates a <code>DynamicRepositoryClassLoader</code> from a list of item
@@ -114,18 +166,29 @@ public class DynamicRepositoryClassLoader
      * @throws NullPointerException if either the session or the handles list
      *      is <code>null</code>.
      */
-    public DynamicRepositoryClassLoader(Session session,
-            String[] classPath, ClassLoader parent) {
-
+    public DynamicRepositoryClassLoader(final Session session,
+                                        final String[] classPath,
+                                        final ClassLoader parent) {
         // initialize the super class with an empty class path
-        super(session, classPath, parent);
+        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
-        dirty = false;
-        modTimeCache = new HashMap<String, ClassLoaderResource>();
+        this.session = session;
+        this.paths = classPath;
+
+        // build the class repositories list
+        buildRepository();
 
         // register with observation service and path pattern list
-        registerModificationListener();
+        registerListeners();
 
         log.debug("DynamicRepositoryClassLoader: {} ready", this);
     }
@@ -144,23 +207,25 @@ public class DynamicRepositoryClassLoader
      * @param parent The parent <code>ClassLoader</code>, which may be
      *            <code>null</code>.
      */
-    private DynamicRepositoryClassLoader(Session session,
-            DynamicRepositoryClassLoader old, ClassLoader parent) {
-
+    private DynamicRepositoryClassLoader(final Session session,
+                                         final DynamicRepositoryClassLoader old,
+                                         final ClassLoader parent) {
         // initialize the super class with an empty class path
-        super(session, old.getPaths(), parent);
+        super(NULL_PATH, parent);
 
-        // set the configuration and fields
-        dirty = false;
-        modTimeCache = new HashMap<String, ClassLoaderResource>();
+        // check session and handles
+        if (session == null) {
+            throw new NullPointerException("session");
+        }
+        // set fields
+        this.session = session;
+        this.paths = old.paths;
 
-        // create a repository from the handles - might get a different one
-        setRepository(resetClassPathEntries(old.getRepository()));
-        setAddedRepositories(resetClassPathEntries(old.getAddedRepositories()));
+        repository = old.repository;
         buildRepository();
 
         // register with observation service and path pattern list
-        registerModificationListener();
+        registerListeners();
 
         // finally finalize the old class loader
         old.destroy();
@@ -181,119 +246,450 @@ public class DynamicRepositoryClassLoader
      */
     public void destroy() {
         // we expect to be called only once, so we stop destroyal here
-        if (isDestroyed()) {
+        if (destroyed) {
             log.debug("Instance is already destroyed");
             return;
         }
 
         // remove ourselves as listeners from other places
-        unregisterListener();
-
-        addedRepositories = null;
-
-        super.destroy();
+        unregisterListeners();
+
+        // set destroyal guard
+        destroyed = true;
+
+        // clear caches and references
+        repository = null;
+        paths = null;
+        session = null;
+
+        // clear the cache of loaded resources and flush cached class
+        // introspections of the JavaBean framework
+        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();
+        modTimeCache.clear();
     }
 
-    //---------- reload support ------------------------------------------------
+    //---------- URLClassLoader overwrites -------------------------------------
 
     /**
-     * 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>.
+     * Finds and loads the class with the specified name from the class path.
      *
-     * @param name The name of the resource to check.
+     * @param name the name of the class
+     * @return the resulting class
      *
-     * @return <code>true</code> if the resource is loaded and reloading would
-     *      take another version than currently loaded.
+     * @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 (destroyed) {
+            throw new ClassNotFoundException(name + " (Classloader destroyed)");
+        }
+
+        log.debug("findClass: Try to find class {}", name);
+
+        try {
+            return AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Class<?>>() {
+
+                    public Class<?> 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
      *
-     * @see #isDirty
+     * @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 synchronized boolean shouldReload(String name) {
-        return shouldReload(name, false);
+    public URL findResource(String name) {
+
+        if (destroyed) {
+            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;
     }
 
     /**
-     * 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.
+     * Returns an Enumeration of URLs representing all of the resources
+     * on the search path having the specified name.
      *
-     * @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.
+     * @param name the resource name
      *
-     * @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.
+     * @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<URL> findResources(String name) {
+
+        if (destroyed) {
+            log.warn("Destroyed class loader cannot find resources");
+            return new Enumeration<URL>() {
+                public boolean hasMoreElements() {
+                    return false;
+                }
+                public URL nextElement() {
+                    throw new NoSuchElementException("No Entries");
+                }
+            };
+        }
+
+        log.debug("findResources: Try to find resources for {}", name);
+
+        List<URL> list = new LinkedList<URL>();
+        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)}.
      *
-     * @see #isDirty
+     * @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 synchronized boolean shouldReload(String name, boolean force) {
-        if (isDestroyed()) {
-            log.warn("Classloader already destroyed, reload required");
-            return true;
+    public URL[] getURLs() {
+        if (destroyed) {
+            log.warn("Destroyed class loader has no URLs any more");
+            return NULL_PATH;
         }
 
-        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;
+        List<URL> urls = new ArrayList<URL>();
+        for (int i=0; i < repository.length; i++) {
+            URL url = repository[i].toURL();
+            if (url != null) {
+                urls.add(url);
+            }
+        }
+        return urls.toArray(new URL[urls.size()]);
+    }
+
+    /**
+     * We don't allow to add new urls at runtime.
+     * @see java.net.URLClassLoader#addURL(java.net.URL)
+     */
+    protected void addURL(URL url) {
+        if (destroyed) {
+            log.warn("Cannot add URL to destroyed class loader");
+        } else {
+            log.warn("addURL: {} unable to add URL at runtime, ignored", url);
+        }
+    }
+
+    //---------- Property access ----------------------------------------------
+
+    /**
+     * 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.
+     */
+    private void cleanCache() {
+        final Iterator<ClassLoaderResource> ci = this.cache.values().iterator();
+        while (ci.hasNext()) {
+            if (ci.next() == NOT_FOUND_RESOURCE) {
+                ci.remove();
             }
+        }
+    }
+
+    //---------- internal ------------------------------------------------------
 
-            return expireResource(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.
+     */
+    private 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);
+            }
         }
 
-        return false;
+        // replace old repository with new one
+        ClassPathEntry[] newClassPath = new ClassPathEntry[newRepository.size()];
+        newRepository.toArray(newClassPath);
+        repository = newClassPath;
+
+        // clear un-found resource cache
+        cleanCache();
     }
 
     /**
-     * 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.
+     * 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.
      *
-     * @return <code>true</code> if any class loader needs to be reinstantiated.
+     * @param name the name of the class
      *
-     * @see #isDirty
+     * @return the resulting class
+     *
+     * @throws ClassNotFoundException if the class could not be found
+     * @throws NullPointerException If this class loader has already been
+     *      destroyed.
      */
-    public synchronized boolean shouldReload() {
+    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);
 
-        // check whether we are already dirty
-        if (isDirty()) {
-            log.debug("shouldReload: Dirty, need reload");
-            return true;
+        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.
+     */
+    private 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) {
+            // 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);
+                    break;
+                }
+            }
+            if ( res == null ) {
+                log.debug("No classpath entry contains {}", name);
+                cache.put(name, NOT_FOUND_RESOURCE);
+                return null;
+            }
+        }
+        // if it could be found, we register it with the caches
+        // 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;
+    }
 
-        // Check whether any class has changed
-        for (Iterator<ClassLoaderResource> iter = getCachedResources(); iter.hasNext();) {
-            if (expireResource(iter.next())) {
-                log.debug("shouldReload: Found expired resource, need reload");
-                return true;
+    /**
+     * 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);
         }
 
-        // No changes, no need to reload
-        log.debug("shouldReload: No expired resource found, no need to reload");
-        return false;
+        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);
+    }
+
+    //---------- reload support ------------------------------------------------
+
+    /**
      * 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.
+     * of the 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.
@@ -309,7 +705,7 @@ public class DynamicRepositoryClassLoader
      *      reinstantiation.
      */
     public boolean isDirty() {
-        return isDestroyed() || dirty || !getSession().isLive();
+        return destroyed || dirty || !session.isLive();
     }
 
     /**
@@ -332,7 +728,7 @@ public class DynamicRepositoryClassLoader
     public DynamicRepositoryClassLoader reinstantiate(Session session, ClassLoader parent) {
         log.debug("reinstantiate: Copying {} with parent {}", this, parent);
 
-        if (isDestroyed()) {
+        if (destroyed) {
             throw new IllegalStateException("Destroyed class loader cannot be recreated");
         }
 
@@ -344,115 +740,7 @@ public class DynamicRepositoryClassLoader
         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 -------------------------------
+    //---------- EventListener interface -------------------------------
 
     /**
      * Handles a repository item modifcation events checking whether a class
@@ -497,58 +785,23 @@ public class DynamicRepositoryClassLoader
      * Returns a string representation of this class loader.
      */
     public String toString() {
-        if (isDestroyed()) {
-            return super.toString();
+        StringBuilder buf = new StringBuilder(getClass().getName());
+        if (destroyed) {
+            buf.append(" - destroyed");
+        } else {
+            buf.append(": parent: { ");
+            buf.append(getParent());
+            buf.append(" }, user: ");
+            buf.append(session.getUserID());
+            buf.append(", dirty: ");
+            buf.append(isDirty());
         }
-
-        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.
@@ -556,16 +809,15 @@ public class DynamicRepositoryClassLoader
      * @throws NullPointerException if this class loader has already been
      *      destroyed.
      */
-    private final void registerModificationListener() {
-        log.debug("registerModificationListener: Registering to the observation service");
+    private final void registerListeners() {
+        log.debug("registerListeners: Registering to the observation service");
 
-        final String[] paths = this.getPaths();
-        this.proxyListeners = new EventListener[this.getPaths().length];
+        this.proxyListeners = new EventListener[this.paths.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();
+                final ObservationManager om = session.getWorkspace().getObservationManager();
                 om.addEventListener(listener, 255, path, true, null, null, false);
                 proxyListeners[i] = listener;
             } catch (RepositoryException re) {
@@ -582,13 +834,13 @@ public class DynamicRepositoryClassLoader
      * @throws NullPointerException if this class loader has already been
      *      destroyed.
      */
-    private final void unregisterListener() {
-        log.debug("registerModificationListener: Deregistering from the observation service");
+    private final void unregisterListeners() {
+        log.debug("unregisterListeners: 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();
+                        final ObservationManager om = session.getWorkspace().getObservationManager();
                         om.removeEventListener(listener);
                     } catch (RepositoryException re) {
                         log.error("unregisterListener: Cannot unregister " +
@@ -622,27 +874,6 @@ public class DynamicRepositoryClassLoader
         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;
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
deleted file mode 100644
index fddfcb9..0000000
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
+++ /dev/null
@@ -1,794 +0,0 @@
-/*
- * 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 AccessController.doPrivileged(
-                new PrivilegedExceptionAction<Class<?>>() {
-
-                    public Class<?> 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<URL> findResources(String name) {
-
-        if (isDestroyed()) {
-            log.warn("Destroyed class loader cannot find resources");
-            return new Enumeration<URL>() {
-                public boolean hasMoreElements() {
-                    return false;
-                }
-                public URL nextElement() {
-                    throw new NoSuchElementException("No Entries");
-                }
-            };
-        }
-
-        log.debug("findResources: Try to find resources for {}", name);
-
-        List<URL> list = new LinkedList<URL>();
-        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)}.
-     *
-     * @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<URL> urls = new ArrayList<URL>();
-        for (int i=0; i < repository.length; i++) {
-            URL url = repository[i].toURL();
-            if (url != null) {
-                urls.add(url);
-            }
-        }
-        return 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<ClassLoaderResource> 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());
-    }
-}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 05/18: SLING-1296 : Potential concurrent modification exception during path creation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 533d6c69272fa24ea6608d8004a2e151334ad531
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jan 19 14:35:40 2010 +0000

    SLING-1296 : Potential concurrent modification exception during path creation
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@900798 13f79535-47bb-0310-9956-ffa450edef68
---
 .../classloader/internal/DynamicClassLoaderProviderImpl.java  | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
index 7befe0d..6b89596 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicClassLoaderProviderImpl.java
@@ -212,12 +212,19 @@ public class DynamicClassLoaderProviderImpl
                     if (parentNode == null) {
                         parentNode = current;
                     }
-                    current = current.addNode(names[i], "nt:folder");
+                    try {
+                        current.addNode(names[i], "nt:folder");
+                        session.save();
+                    } catch (RepositoryException re) {
+                        // we ignore this as this might be a concurrent modification!
+                        session.refresh(false);
+                    }
+                    current = current.getNode(names[i]);
                 }
             }
 
             if (parentNode != null) {
-                parentNode.save();
+                session.save();
                 return true;
             }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 17/18: [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-3.1.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit d803d041ea0ae28eaf99d0bdc73f84d2c9dd023b
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Feb 3 11:18:26 2010 +0000

    [maven-release-plugin] prepare release org.apache.sling.jcr.classloader-3.1.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@906000 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 35e3b6f..1b4f471 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
     </parent>
 
     <artifactId>org.apache.sling.jcr.classloader</artifactId>
-    <version>3.1.0-SNAPSHOT</version>
+    <version>3.1.0</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling JCR ClassLoader</name>
@@ -39,9 +39,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/classloader</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.classloader-3.1.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.classloader-3.1.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.jcr.classloader-3.1.0</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 11/18: SLING-1316 - Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes. Update to Java 5 code.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 631f9f957b45ce5ab833f5852ce497f135acec1b
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 25 12:52:10 2010 +0000

    SLING-1316 -  Include jackrabbit classloader code to adjust it for Sling needs - code import with first changes. Update to Java 5 code.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@902799 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/DynamicRepositoryClassLoader.java     | 12 ++++----
 .../internal/URLRepositoryClassLoader.java         | 33 +++++++++++-----------
 .../classloader/internal/net/JCRURLConnection.java | 23 +++++++--------
 3 files changed, 32 insertions(+), 36 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
index 35df60a..34cfc5f 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/DynamicRepositoryClassLoader.java
@@ -83,7 +83,7 @@ public class DynamicRepositoryClassLoader
      * @see #onEvent(EventIterator)
      * @see #findClassLoaderResource(String)
      */
-    private Map modTimeCache;
+    private Map<String, ClassLoaderResource> modTimeCache;
 
     /**
      * Flag indicating whether there are loaded classes which have later been
@@ -122,7 +122,7 @@ public class DynamicRepositoryClassLoader
 
         // set fields
         dirty = false;
-        modTimeCache = new HashMap();
+        modTimeCache = new HashMap<String, ClassLoaderResource>();
 
         // register with observation service and path pattern list
         registerModificationListener();
@@ -152,7 +152,7 @@ public class DynamicRepositoryClassLoader
 
         // set the configuration and fields
         dirty = false;
-        modTimeCache = new HashMap();
+        modTimeCache = new HashMap<String, ClassLoaderResource>();
 
         // create a repository from the handles - might get a different one
         setRepository(resetClassPathEntries(old.getRepository()));
@@ -277,8 +277,8 @@ public class DynamicRepositoryClassLoader
         }
 
         // Check whether any class has changed
-        for (Iterator iter = getCachedResources(); iter.hasNext();) {
-            if (expireResource((ClassLoaderResource) iter.next())) {
+        for (Iterator<ClassLoaderResource> iter = getCachedResources(); iter.hasNext();) {
+            if (expireResource(iter.next())) {
                 log.debug("shouldReload: Found expired resource, need reload");
                 return true;
             }
@@ -475,7 +475,7 @@ public class DynamicRepositoryClassLoader
             log.debug(
                 "onEvent: Item {} has been modified, checking with cache", path);
 
-            ClassLoaderResource resource = (ClassLoaderResource) modTimeCache.get(path);
+            ClassLoaderResource resource = modTimeCache.get(path);
             if (resource != null) {
                 log.debug("pageModified: Expiring cache entry {}", resource);
                 expireResource(resource);
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java b/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
index 7755fd9..fddfcb9 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/URLRepositoryClassLoader.java
@@ -229,7 +229,7 @@ public class URLRepositoryClassLoader extends URLClassLoader {
      * @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 {
+    protected Class<?> findClass(final String name) throws ClassNotFoundException {
 
         if (isDestroyed()) {
             throw new ClassNotFoundException(name + " (Classloader destroyed)");
@@ -238,10 +238,10 @@ public class URLRepositoryClassLoader extends URLClassLoader {
         log.debug("findClass: Try to find class {}", name);
 
         try {
-            return (Class) AccessController
-                .doPrivileged(new PrivilegedExceptionAction() {
+            return AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Class<?>>() {
 
-                    public Object run() throws ClassNotFoundException {
+                    public Class<?> run() throws ClassNotFoundException {
                         return findClassPrivileged(name);
                     }
                 });
@@ -288,15 +288,15 @@ public class URLRepositoryClassLoader extends URLClassLoader {
      *      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) {
+    public Enumeration<URL> findResources(String name) {
 
         if (isDestroyed()) {
             log.warn("Destroyed class loader cannot find resources");
-            return new Enumeration() {
+            return new Enumeration<URL>() {
                 public boolean hasMoreElements() {
                     return false;
                 }
-                public Object nextElement() {
+                public URL nextElement() {
                     throw new NoSuchElementException("No Entries");
                 }
             };
@@ -304,7 +304,7 @@ public class URLRepositoryClassLoader extends URLClassLoader {
 
         log.debug("findResources: Try to find resources for {}", name);
 
-        List list = new LinkedList();
+        List<URL> list = new LinkedList<URL>();
         for (int i=0; i < repository.length; i++) {
             final ClassPathEntry cp = repository[i];
             log.debug("findResources: Trying {}", cp);
@@ -328,8 +328,7 @@ public class URLRepositoryClassLoader extends URLClassLoader {
     /**
      * 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.
+     * along with any URLs subsequently appended by the {@link #addURL(URL)}.
      *
      * @return the search path of URLs for loading classes and resources. The
      *      list is empty, if this class loader has already been destroyed.
@@ -341,14 +340,14 @@ public class URLRepositoryClassLoader extends URLClassLoader {
             return new URL[0];
         }
 
-        List urls = new ArrayList();
+        List<URL> urls = new ArrayList<URL>();
         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()]);
+        return urls.toArray(new URL[urls.size()]);
     }
 
     /**
@@ -423,7 +422,7 @@ public class URLRepositoryClassLoader extends URLClassLoader {
      * @throws NullPointerException If this class loader has already been
      *      destroyed.
      */
-    /* package */ Iterator getCachedResources() {
+    /* package */ Iterator<ClassLoaderResource> getCachedResources() {
         return cache.values().iterator();
     }
 
@@ -607,7 +606,7 @@ public class URLRepositoryClassLoader extends URLClassLoader {
      * @throws NullPointerException If this class loader has already been
      *      destroyed.
      */
-    private Class findClassPrivileged(String name) throws ClassNotFoundException {
+    private Class<?> findClassPrivileged(String name) throws ClassNotFoundException {
 
         // prepare the name of the class
         final String path = name.replace('.', '/').concat(".class");
@@ -623,7 +622,7 @@ public class URLRepositoryClassLoader extends URLClassLoader {
                     "findClassPrivileged: Loading class from {}, created {}",
                     res, new Date(res.getLastModificationTime()));
 
-                 Class c = defineClass(name, res);
+                 Class<?> c = defineClass(name, res);
                  if (c == null) {
                      log.warn("defineClass returned null for class {}", name);
                      throw new ClassNotFoundException(name);
@@ -703,12 +702,12 @@ public class URLRepositoryClassLoader extends URLClassLoader {
      * @throws ClassFormatError If the class bytes read from the resource are
      *      not a valid class.
      */
-    private Class defineClass(String name, ClassLoaderResource res)
+    private Class<?> defineClass(String name, ClassLoaderResource res)
             throws IOException, RepositoryException {
 
         log.debug("defineClass({}, {})", name, res);
 
-        Class clazz = res.getLoadedClass();
+        Class<?> clazz = res.getLoadedClass();
         if (clazz == null) {
 
             /**
diff --git a/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
index 9e1471a..1cf7509 100644
--- a/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
+++ b/src/main/java/org/apache/sling/jcr/classloader/internal/net/JCRURLConnection.java
@@ -37,7 +37,6 @@ import org.apache.sling.jcr.classloader.internal.Util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-
 /**
  * The <code>JCRURLConnection</code> is the <code>URLConnection</code>
  * implementation to access the data addressed by a JCR Repository URL.
@@ -194,7 +193,7 @@ public class JCRURLConnection extends URLConnection {
      * atom.
      * <p>
      * Implementations are free to decide, how to define the content type. But
-     * they are required to set the type in the {@link #connect(Ticket)}method.
+     * they are required to set the type in the {@link #connect()}method.
      *
      * @see #getContentType()
      * @see #connect()
@@ -207,7 +206,7 @@ public class JCRURLConnection extends URLConnection {
      * atom.
      * <p>
      * Implementations are free to decide, how to define the content type. But
-     * they are required to set the type in the {@link #connect(Ticket)}method.
+     * they are required to set the type in the {@link #connect()}method.
      *
      * @see #getContentEncoding()
      * @see #connect()
@@ -219,7 +218,7 @@ public class JCRURLConnection extends URLConnection {
      * status information of the base atom.
      * <p>
      * Implementations are free to decide, how to define the content length. But
-     * they are required to set the type in the {@link #connect(Ticket)}method.
+     * they are required to set the type in the {@link #connect()}method.
      *
      * @see #getContentLength()
      * @see #connect()
@@ -231,7 +230,7 @@ public class JCRURLConnection extends URLConnection {
      * <p>
      * Implementations are free to decide, how to define the last modification
      * time. But they are required to set the type in the
-     * {@link #connect(Ticket)}method.
+     * {@link #connect()}method.
      *
      * @see #getLastModified()
      * @see #connect()
@@ -378,7 +377,7 @@ public class JCRURLConnection extends URLConnection {
                 } else {
                     lastModified = 0;
                 }
-                
+
                 if (parent.hasProperty("jcr:mimeType")) {
                     contentType = parent.getProperty("jcr:mimeType").getString();
                 } else {
@@ -389,11 +388,9 @@ public class JCRURLConnection extends URLConnection {
                                 : TEXT_PLAIN;
                     }
                 }
-                
+
                 if (parent.hasProperty("jcr:encoding")) {
                     contentEncoding = parent.getProperty("jcr:encoding").getString();
-                } else {
-                    contentEncoding = null;
                 }
 
                 log.debug(
@@ -567,8 +564,8 @@ public class JCRURLConnection extends URLConnection {
      *
      * @see #connect()
      */
-    public Map getHeaderFields() {
-        Map fieldMap = new HashMap();
+    public Map<String, List<String>> getHeaderFields() {
+        Map<String, List<String>> fieldMap = new HashMap<String, List<String>>();
 
         try {
             connect();
@@ -770,9 +767,9 @@ public class JCRURLConnection extends URLConnection {
     /**
      * Returns an unmodifiable list containing just the given string value.
      */
-    private List toList(String value) {
+    private List<String> toList(String value) {
         String[] values = { value };
-        List valueList = Arrays.asList(values);
+        List<String> valueList = Arrays.asList(values);
         return Collections.unmodifiableList(valueList);
     }
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 04/18: SLING-1205 Upgrade all projects to parent POM 8 and use OSGi provided OSGi libraries

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit c36049963d2c61b1959d87d2aec7e082ba7cdae3
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Tue Dec 1 07:32:34 2009 +0000

    SLING-1205 Upgrade all projects to parent POM 8 and use OSGi provided OSGi libraries
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@885680 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 9c7694a..9a2a64f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>7</version>
+        <version>8</version>
     </parent>
 
     <artifactId>org.apache.sling.jcr.classloader</artifactId>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 10/18: Remove spurious comma

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit 9c37a61541c59e75d8e1c3f9c7bd13d5df6fbe92
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 25 12:44:26 2010 +0000

    Remove spurious comma
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@902796 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index c811477..fc06be2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
                             sling,jcr,jackrabbit
                         </Bundle-Category>
                         <Private-Package>
-                            org.apache.sling.jcr.classloader.internal.*,
+                            org.apache.sling.jcr.classloader.internal.*
                         </Private-Package>
                     </instructions>
                 </configuration>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 02/18: Use latest versions to avoid build problems.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit f0080a0fc95ae8689eed8a0e2c91694280f490d9
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Nov 24 16:03:44 2009 +0000

    Use latest versions to avoid build problems.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@883746 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 9c7694a..9b7f556 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,7 +97,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.classloader</artifactId>
-            <version>1.1.0</version>
+            <version>1.1.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-jcr-classloader] 03/18: Use latest releases.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.jcr.classloader-3.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-classloader.git

commit a64993e2a062127ee4b3c3853ad5bd36a7fedb65
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Nov 30 13:46:40 2009 +0000

    Use latest releases.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/classloader@885427 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 9b7f556..9c7694a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,7 +97,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.classloader</artifactId>
-            <version>1.1.1-SNAPSHOT</version>
+            <version>1.1.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.