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:26 UTC

[sling-org-apache-sling-jcr-contentloader] annotated tag org.apache.sling.jcr.contentloader-2.0.4-incubator created (now 5a57e4c)

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

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


      at 5a57e4c  (tag)
 tagging 79cb02b7ec99f57840822ff3bb9201edfe00cfaf (commit)
      by Felix Meschberger
      on Thu Jun 18 09:21:02 2009 +0000

- Log -----------------------------------------------------------------
org.apache.sling.jcr.contentloader-2.0.4-incubator
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new 089d630  Start new module for content loading.
     new e7c3f36  SLING-400: Move content loading to own bundle.
     new 37489b3  SLING-400: Move content loading to own bundle.
     new 6adfd33  SLING-400: Move content loading to own bundle.
     new 6ba13f4  Eclude maven-eclipse.xml generated by the maven eclipse plugin.
     new 96424e1  SLING-413: Get sling.id only once; handling start and stop should be sufficient.
     new 525d242  SLING-413: Handle bundle update and correctly handle overwrite flag.
     new 76f7b76  SLING-412: Remove support for "name" property which defines the name of the root node imported through json.
     new 5e6f6b9  Use new manifest parser in content loader.
     new 1a9cb53  #SLING-414: Move generation and managing of the sling id to own service in the engine.
     new 55e5771  SLING-453: Initial content loading to target location https://issues.apache.org/jira/browse/SLING-453
     new 97fa605  SLING-365 Implement support for initial checkin of mix:versionable nodes on-demand
     new 7a2d4b1  Add explicit dependency to JUnit library
     new d8971e5  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 66b1860  SLING-483: Add excludes for javadocs (WiP)
     new 7857ffd  SLING-495 - NOTICE files generated using mknotice script
     new 74d5c76  SLING-495 - NOTICE files updated according to changes in revision 662927
     new 60e1c27  SLING-495 - NOTICE files regenerated with revision 663097 changes
     new 9b24eb4  Use released version of parent pom.
     new 538a808  Use released versions (or soon to be released versions).
     new ffa3b1c  [maven-release-plugin] prepare release org.apache.sling.jcr.contentloader-2.0.0-incubator
     new 11176fe  [maven-release-plugin] prepare for next development iteration
     new 9e8c1dc  Set version to next development version.
     new 9fe2a8e  Fix reference to parent pom.
     new 5f28fb6  SLING-521: Restore more notice files.
     new b037f12  SLING-521: Separate between notice files for bin and src dists.
     new 289aa18  SLING-521: Separate between notice files for bin and src dists.
     new 7085ebf  Change copyright year to 2008.
     new 5773655  SLING-539: Merge LICENSE.* information to LICENSE files     - kXML license
     new 0487817  Update to released versions.
     new a4f12d3  [maven-release-plugin] prepare release org.apache.sling.jcr.contentloader-2.0.2-incubator
     new c706feb  [maven-release-plugin] prepare for next development iteration
     new 9f2975d  Use latest snapshots again to make project buildable.
     new b748cf1  SLING-545 Apply modified patch (see issue for modification details)
     new 899bdff  SLING-548: Rewrite of content loading from creating mock node/property objects to event based loading. This mechanism allows to create nodes and properties on the fly while reading from the input stream. (ViP) Enable tests again (they were in the wrong directory)
     new 0a0ce10  SLING-548: Remove wrong test src dir.
     new 9fbe62f  Use released versions.
     new 5510767  Use released parent pom where possible.
     new d05e8d2  SLING-548: Add methods for setting properties from objects.
     new b042f5d  SLING-548: Provide methods for creating file nodes and to change the current context node.
     new 24de4c8  SLING-548: Initial version to extract zips and jars into the repository.
     new 32d477e  SLING-548: Refactor code and provide a mechanism to disable import providers.
     new bb27019  Create path if it is not existend
     new e07a62a  Fix input stream handling.
     new ef74c9d  SLING-553 Allow references and paths to be loaded from JSON through JsonReader. Applied patch by Bryce Ewing.
     new 55a2d63  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 7fe9fc0  Create sling:Folder as intermediate node.
     new 63fc0a9  SLING-578 : Create intermediate nodes with type sling:Folder SLING-579 : Allow additional content information for files.
     new f504f45  Refresh session before save.
     new 5736f52  SLING-766 : Don't uninstall content when bundle is stopped. The content is now only uninstalled when the bundle is uninstalled. During installation a string array containing all paths that will be removed later on is collected which makes the uninstall easier. Also refactored the classes and packages a little bit.
     new 921136c  SLING-808 : Increase version to 5-incubator-SNAPSHOT
     new 43b6516  Update notice files to include 2009 in copyright statement.
     new f72f256  SLING-811 Reduce DependencyManagement in the parent pom and add explicit versions to the modules
     new c02f7c7  SLING-829 Cosmetics to the bundle/project names
     new ee4f054  SLING-836 : Always create a new session for content loading / unloading. Try all bundles on startup even if one bundle fails.
     new 94f72a0  SLING-838 : Clean up code, use method for session logout.
     new 01f8990  SLING-865 : Move to bundles
     new 4146903  SLING-865 : Adjust reactor pom and svn information in each module.
     new c653fd3  SLING-865 : Adjust path to parent pom and add profiles for samples and contrib
     new 3e3e882  SLING-857 Apply patch by Vidar Ramdal (Thanks alot) to support XSLT   for XML imports
     new e0a5ce4  SLING-857 - test case for XmlReader using an XSLT transform contributed by Vidar S. Ramdal, thanks!
     new e7184db  SLING-941: Lots of svn:eol-style settings missing
     new 295304a  [maven-release-plugin]  copy for tag org.apache.sling.jcr.contentloader-2.0.4-incubator
     new d153db5  Move Sling to new TLP location
     new 79cb02b  Move Sling to new TLP location

The 65 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-contentloader] 15/36: Fix input stream handling.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit e07a62a041e7bc84f1a30eb5b6adeb406776b8fc
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jun 25 15:55:15 2008 +0000

    Fix input stream handling.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@671591 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/jcr/contentloader/internal/ZipReader.java    | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
index a47d5b8..fff0c82 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
@@ -73,7 +73,6 @@ class ZipReader implements ContentReader {
     throws IOException, RepositoryException {
         creator.createNode(null, NT_FOLDER, null);
         final ZipInputStream zis = new ZipInputStream(ins);
-        final InputStream dataIS = new CloseShieldInputStream(zis);
         ZipEntry entry;
         do {
             entry = zis.getNextEntry();
@@ -84,7 +83,7 @@ class ZipReader implements ContentReader {
                     if ( pos != -1 ) {
                         creator.switchCurrentNode(name.substring(0, pos), NT_FOLDER);
                     }
-                    creator.createFileAndResourceNode(name, dataIS, null, entry.getTime());
+                    creator.createFileAndResourceNode(name, new CloseShieldInputStream(zis), null, entry.getTime());
                     creator.finishNode();
                     creator.finishNode();
                     if ( pos != -1 ) {

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

[sling-org-apache-sling-jcr-contentloader] 30/36: SLING-865 : Adjust path to parent pom and add profiles for samples and contrib

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit c653fd36079845c59122ec5e6fea464fc58f2fc4
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sun Feb 22 15:19:05 2009 +0000

    SLING-865 : Adjust path to parent pom and add profiles for samples and contrib
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader@746728 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index a9521f4..dda6027 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
         <version>5-incubator-SNAPSHOT</version>
-        <relativePath>../../parent/pom.xml</relativePath>
+        <relativePath>../../../parent/pom.xml</relativePath>
     </parent>
 
     <artifactId>org.apache.sling.jcr.contentloader</artifactId>

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

[sling-org-apache-sling-jcr-contentloader] 13/36: SLING-548: Refactor code and provide a mechanism to disable import providers.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 32d477e34d2a40d3ce1e1ae8d497f973f0ece560
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 16:42:47 2008 +0000

    SLING-548: Refactor code and provide a mechanism to disable import providers.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670670 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentCreator.java |   2 +
 .../jcr/contentloader/internal/ContentLoader.java  |  57 +++++++++++-
 .../sling/jcr/contentloader/internal/Loader.java   | 103 ++++++++++-----------
 .../jcr/contentloader/internal/PathEntry.java      |  32 ++++---
 .../jcr/contentloader/internal/ZipReader.java      |  14 ++-
 5 files changed, 129 insertions(+), 79 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index 86b2476..4390f4e 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -25,6 +25,8 @@ import javax.jcr.RepositoryException;
 /**
  * The <code>ContentCreator</code>
  * is used by the {@link ContentReader} to create the actual content.
+ *
+ * @since 2.0.4
  */
 interface ContentCreator {
 
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
index bb1827e..e89389c 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
@@ -38,6 +39,7 @@ import javax.jcr.ValueFactory;
 
 /**
  * The <code>ContentLoader</code> creates the nodes and properties.
+ * @since 2.0.4
  */
 public class ContentLoader implements ContentCreator {
 
@@ -62,21 +64,39 @@ public class ContentLoader implements ContentCreator {
 
     private final ContentLoaderService jcrContentHelper;
 
+    private Map<String, ImportProvider> importProviders;
+
     public ContentLoader(ContentLoaderService jcrContentHelper) {
         this.jcrContentHelper = jcrContentHelper;
     }
 
     /**
      * Initialize this component.
-     * If the defaultRootName is null, we are in ROOT_NODE import mode.
      * @param pathEntry
-     * @param parentNode
-     * @param defaultRootName
      */
     public void init(final PathEntry pathEntry,
-                     final Node parentNode,
-                     final String defaultRootName) {
+                     final Map<String, ImportProvider> defaultImportProviders) {
+
         this.configuration = pathEntry;
+        // create list of allowed import providers
+        this.importProviders = new HashMap<String, ImportProvider>();
+        final Iterator<Map.Entry<String, ImportProvider>> entryIter = defaultImportProviders.entrySet().iterator();
+        while ( entryIter.hasNext() ) {
+            final Map.Entry<String, ImportProvider> current = entryIter.next();
+            if (!configuration.isIgnoredImportProvider(current.getKey()) ) {
+                importProviders.put(current.getKey(), current.getValue());
+            }
+        }
+    }
+
+    /**
+     *
+     * If the defaultRootName is null, we are in ROOT_NODE import mode.
+     * @param parentNode
+     * @param defaultRootName
+     */
+    public void prepareParsing(final Node parentNode,
+                               final String defaultRootName) {
         this.parentNodeStack.clear();
         this.parentNodeStack.push(parentNode);
         this.defaultRootName = defaultRootName;
@@ -105,6 +125,33 @@ public class ContentLoader implements ContentCreator {
         return this.rootNode;
     }
 
+    public Map<String, ImportProvider> getImportProviders() {
+        return this.importProviders;
+    }
+
+    public ImportProvider getImportProvider(String name) {
+        ImportProvider provider = null;
+        final Iterator<String> ipIter = importProviders.keySet().iterator();
+        while (provider == null && ipIter.hasNext()) {
+            final String ext = ipIter.next();
+            if (name.endsWith(ext)) {
+                provider = importProviders.get(ext);
+            }
+        }
+        return provider;
+    }
+
+    public String getImportProviderExtension(String name) {
+        String providerExt = null;
+        final Iterator<String> ipIter = importProviders.keySet().iterator();
+        while (providerExt == null && ipIter.hasNext()) {
+            final String ext = ipIter.next();
+            if (name.endsWith(ext)) {
+                providerExt = ext;
+            }
+        }
+        return providerExt;
+    }
 
     /**
      * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createNode(java.lang.String, java.lang.String, java.lang.String[])
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index ac58191..fa7c191 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -57,6 +57,10 @@ public class Loader {
 
     public static final String EXT_JSON = ".json";
 
+    public static final String EXT_JAR = ".jar";
+
+    public static final String EXT_ZIP = ".zip";
+
     public static final String ROOT_DESCRIPTOR = "/ROOT";
 
     /** default log */
@@ -64,7 +68,8 @@ public class Loader {
 
     private ContentLoaderService jcrContentHelper;
 
-    private Map<String, ImportProvider> importProviders;
+    /** All available import providers. */
+    private Map<String, ImportProvider> defaultImportProviders;
 
     private final ContentLoader contentCreator;
 
@@ -76,12 +81,12 @@ public class Loader {
         this.contentCreator = new ContentLoader(jcrContentHelper);
         this.delayedBundles = new LinkedList<Bundle>();
 
-        importProviders = new LinkedHashMap<String, ImportProvider>();
-        importProviders.put(EXT_JCR_XML, null);
-        importProviders.put(EXT_JSON, JsonReader.PROVIDER);
-        importProviders.put(EXT_XML, XmlReader.PROVIDER);
-        importProviders.put(".jar", ZipReader.JAR_PROVIDER);
-        importProviders.put(".zip", ZipReader.ZIP_PROVIDER);
+        defaultImportProviders = new LinkedHashMap<String, ImportProvider>();
+        defaultImportProviders.put(EXT_JCR_XML, null);
+        defaultImportProviders.put(EXT_JSON, JsonReader.PROVIDER);
+        defaultImportProviders.put(EXT_XML, XmlReader.PROVIDER);
+        defaultImportProviders.put(EXT_JAR, ZipReader.JAR_PROVIDER);
+        defaultImportProviders.put(EXT_ZIP, ZipReader.ZIP_PROVIDER);
     }
 
     public void dispose() {
@@ -90,7 +95,7 @@ public class Loader {
             delayedBundles = null;
         }
         jcrContentHelper = null;
-        importProviders.clear();
+        defaultImportProviders = null;
     }
 
     /**
@@ -290,11 +295,12 @@ public class Loader {
             log.info("install: No initial content entries at {}", path);
             return;
         }
+        //  init content creator
+        this.contentCreator.init(configuration, this.defaultImportProviders);
 
-        Map<URL, Node> processedEntries = new HashMap<URL, Node>();
-
+        final Map<URL, Node> processedEntries = new HashMap<URL, Node>();
         // potential root node import/extension
-        URL rootNodeDescriptor = importRootNode(parent.getSession(), bundle, path, configuration);
+        URL rootNodeDescriptor = importRootNode(parent.getSession(), bundle, path);
         if (rootNodeDescriptor != null) {
             processedEntries.put(rootNodeDescriptor,
                 parent.getSession().getRootNode());
@@ -310,7 +316,7 @@ public class Loader {
                 String name = getName(base);
 
                 URL nodeDescriptor = null;
-                for (String ext : importProviders.keySet()) {
+                for (String ext : this.contentCreator.getImportProviders().keySet()) {
                     nodeDescriptor = bundle.getEntry(base + ext);
                     if (nodeDescriptor != null) {
                         break;
@@ -325,7 +331,7 @@ public class Loader {
                     node = processedEntries.get(nodeDescriptor);
                     if (node == null) {
                         node = createNode(parent, name, nodeDescriptor,
-                            configuration);
+                                          configuration);
                         processedEntries.put(nodeDescriptor, node);
                     }
                 } else {
@@ -347,14 +353,8 @@ public class Loader {
                 }
 
                 // install if it is a descriptor
-                boolean foundProvider = false;
-                final Iterator<String> ipIter = importProviders.keySet().iterator();
-                while (!foundProvider && ipIter.hasNext()) {
-                    final String ext = ipIter.next();
-                    if (entry.endsWith(ext)) {
-                        foundProvider = true;
-                    }
-                }
+                boolean foundProvider = this.contentCreator.getImportProvider(entry) != null;
+
                 if (foundProvider) {
                     Node node = null;
                     if ((node = createNode(parent, getName(entry), file, configuration)) != null) {
@@ -398,20 +398,18 @@ public class Loader {
             }
 
             // get the node reader for this resource
-            ContentReader nodeReader = null;
-            for (Map.Entry<String, ImportProvider> e : importProviders.entrySet()) {
-                if (resourcePath.endsWith(e.getKey())) {
-                    nodeReader = e.getValue().getReader();
-                    break;
-                }
+            final ImportProvider ip = this.contentCreator.getImportProvider(resourcePath);
+            if ( ip == null ) {
+                return null;
             }
+            final ContentReader nodeReader = ip.getReader();
 
             // cannot find out the type
             if (nodeReader == null) {
                 return null;
             }
 
-            this.contentCreator.init(configuration, parent, toPlainName(name));
+            this.contentCreator.prepareParsing(parent, toPlainName(name));
             ins = resourceUrl.openStream();
             nodeReader.parse(ins, this.contentCreator);
 
@@ -487,7 +485,8 @@ public class Loader {
             path = srcPath.substring(0, pos + 1) + name;
         }
 
-        this.contentCreator.init(configuration, parent, name);
+        this.contentCreator.init(configuration, defaultImportProviders);
+        this.contentCreator.prepareParsing(parent, name);
         final URLConnection conn = source.openConnection();
         final long lastModified = conn.getLastModified();
         final String type = conn.getContentType();
@@ -575,7 +574,7 @@ public class Loader {
                 if (entry.isUninstall()) {
                     Node targetNode = getTargetNode(session, entry.getTarget());
                     if (targetNode != null)
-                        uninstallFromPath(bundle, entry.getPath(), targetNode);
+                        uninstallFromPath(bundle, entry.getPath(), entry, targetNode);
                 } else {
                     log.debug(
                         "Ignoring to uninstall content at {}, uninstall directive is not set.",
@@ -611,15 +610,19 @@ public class Loader {
      * @param parent The parent node.
      * @throws RepositoryException
      */
-    private void uninstallFromPath(final Bundle bundle, final String path,
-            final Node parent) throws RepositoryException {
+    private void uninstallFromPath(final Bundle bundle,
+                                   final String path,
+                                   final PathEntry configuration,
+                                   final Node parent) throws RepositoryException {
         @SuppressWarnings("unchecked")
         Enumeration<String> entries = bundle.getEntryPaths(path);
         if (entries == null) {
             return;
         }
 
-        Set<URL> ignoreEntry = new HashSet<URL>();
+        this.contentCreator.init(configuration, defaultImportProviders);
+
+        final Set<URL> ignoreEntry = new HashSet<URL>();
 
         // potential root node import/extension
         Descriptor rootNodeDescriptor = getRootNodeDescriptor(bundle, path);
@@ -636,7 +639,7 @@ public class Loader {
                 String name = getName(base);
 
                 URL nodeDescriptor = null;
-                for (String ext : importProviders.keySet()) {
+                for (String ext : this.contentCreator.getImportProviders().keySet()) {
                     nodeDescriptor = bundle.getEntry(base + ext);
                     if (nodeDescriptor != null) {
                         break;
@@ -657,7 +660,7 @@ public class Loader {
 
                 if (node != null) {
                     // walk down the line
-                    uninstallFromPath(bundle, entry, node);
+                    uninstallFromPath(bundle, entry, configuration, node);
                 }
 
                 if (delete) {
@@ -674,14 +677,7 @@ public class Loader {
                 }
 
                 // uninstall if it is a descriptor
-                boolean foundProvider = false;
-                final Iterator<String> ipIter = importProviders.keySet().iterator();
-                while (!foundProvider && ipIter.hasNext()) {
-                    final String ext = ipIter.next();
-                    if (entry.endsWith(ext)) {
-                        foundProvider = true;
-                    }
-                }
+                boolean foundProvider = this.contentCreator.getImportProvider(entry) != null;
                 if (foundProvider) {
                     deleteNode(parent, toPlainName(getName(entry)));
                     ignoreEntry.add(file);
@@ -710,13 +706,15 @@ public class Loader {
      * @throws IOException If an IO error occurrs reading the XML file.
      */
     private Node importSystemView(Node parent, String name, URL nodeXML)
-            throws IOException {
+    throws IOException {
 
         InputStream ins = null;
         try {
 
             // check whether we have the content already, nothing to do then
-            name = toPlainName(name);
+            if ( name.endsWith(EXT_JCR_XML) ) {
+                name = name.substring(0, name.length() - EXT_JCR_XML.length());
+            }
             if (parent.hasNode(name)) {
                 log.debug(
                     "importSystemView: Node {} for XML {} already exists, nothing to to",
@@ -773,7 +771,7 @@ public class Loader {
                                              final String path) {
         URL rootNodeDescriptor = null;
 
-        for (Map.Entry<String, ImportProvider> e : importProviders.entrySet()) {
+        for (Map.Entry<String, ImportProvider> e : this.contentCreator.getImportProviders().entrySet()) {
             if (e.getValue() != null) {
                 rootNodeDescriptor = bundle.getEntry(path + ROOT_DESCRIPTOR
                     + e.getKey());
@@ -798,7 +796,7 @@ public class Loader {
      * Imports mixin nodes and properties (and optionally child nodes) of the
      * root node.
      */
-    private URL importRootNode(Session session, Bundle bundle, String path, PathEntry configuration)
+    private URL importRootNode(Session session, Bundle bundle, String path)
     throws RepositoryException {
         final Descriptor descriptor = getRootNodeDescriptor(bundle, path);
         // no root descriptor found
@@ -810,7 +808,7 @@ public class Loader {
         try {
 
             ins = descriptor.rootNodeDescriptor.openStream();
-            this.contentCreator.init(configuration, session.getRootNode(), null);
+            this.contentCreator.prepareParsing(session.getRootNode(), null);
             descriptor.nodeReader.parse(ins, this.contentCreator);
 
             return descriptor.rootNodeDescriptor;
@@ -830,14 +828,7 @@ public class Loader {
     }
 
     private String toPlainName(String name) {
-        String providerExt = null;
-        final Iterator<String> ipIter = importProviders.keySet().iterator();
-        while (providerExt == null && ipIter.hasNext()) {
-            final String ext = ipIter.next();
-            if (name.endsWith(ext)) {
-                providerExt = ext;
-            }
-        }
+        final String providerExt = this.contentCreator.getImportProviderExtension(name);
         if (providerExt != null) {
             return name.substring(0, name.length() - providerExt.length());
         }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
index 937462b..64213c5 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
@@ -21,6 +21,7 @@ package org.apache.sling.jcr.contentloader.internal;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.StringTokenizer;
 
 import org.apache.sling.commons.osgi.ManifestHeader;
 import org.osgi.framework.Bundle;
@@ -55,12 +56,12 @@ public class PathEntry {
     public static final String CHECKIN_DIRECTIVE = "checkin";
 
     /**
-     * The expand directive specifying whether the available {@link ImportProvider}s
-     * should be used during content loading. This is a boolean value that
-     * defaults to true.
+     * The ignore import providers directive specifying whether the available {@link ImportProvider}s
+     * should be used during content loading. This is a string value that defaults to the empty
+     * string..
      * @since 2.0.4
      */
-    public static final String EXPAND_DIRECTIVE = "expand";
+    public static final String IGNORE_IMPORT_PROVIDERS_DIRECTIVE = "ignoreImportProviders";
 
     /** The path for the initial content. */
     private final String path;
@@ -74,8 +75,8 @@ public class PathEntry {
     /** Should versionable nodes be checked in? */
     private final boolean checkin;
 
-    /** Should archives be expanded? @since 2.0.4 */
-    private final boolean expand;
+    /** Which import providers should be ignored? @since 2.0.4 */
+    private final List<String> ignoreImportProviders;
 
     /**
      * Target path where initial content will be loaded. If it´s null then
@@ -138,11 +139,13 @@ public class PathEntry {
         }
 
         // expand directive
-        final String expandValue = entry.getDirectiveValue(EXPAND_DIRECTIVE);
-        if ( expandValue != null ) {
-            this.expand = Boolean.valueOf(expandValue);
-        } else {
-            this.expand = true;
+        this.ignoreImportProviders = new ArrayList<String>();
+        final String expandValue = entry.getDirectiveValue(IGNORE_IMPORT_PROVIDERS_DIRECTIVE);
+        if ( expandValue != null && expandValue.length() > 0 ) {
+            final StringTokenizer st = new StringTokenizer(expandValue, ",");
+            while ( st.hasMoreTokens() ) {
+                this.ignoreImportProviders.add(st.nextToken());
+            }
         }
     }
 
@@ -162,8 +165,11 @@ public class PathEntry {
         return this.checkin;
     }
 
-    public boolean isExpand() {
-        return this.expand;
+    public boolean isIgnoredImportProvider(String extension) {
+        if ( extension.startsWith(".") ) {
+            extension = extension.substring(1);
+        }
+        return this.ignoreImportProviders.contains(extension);
     }
 
     public String getTarget() {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
index 70e661c..a47d5b8 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
@@ -29,10 +29,14 @@ import org.apache.commons.io.input.CloseShieldInputStream;
 
 
 /**
- * The <code>JsonReader</code> TODO
+ * The <code>ZipReader</code> TODO
+ *
+ * @since 2.0.4
  */
 class ZipReader implements ContentReader {
 
+    private static final String NT_FOLDER = "nt:folder";
+
     static final ImportProvider ZIP_PROVIDER = new ImportProvider() {
         private ZipReader zipReader;
 
@@ -56,10 +60,10 @@ class ZipReader implements ContentReader {
     };
 
     /** Is this a jar reader? */
-    private final boolean jarReader;
+    //private final boolean jarReader;
 
     public ZipReader(boolean jarReader) {
-        this.jarReader = jarReader;
+        //this.jarReader = jarReader;
     }
 
     /**
@@ -67,7 +71,7 @@ class ZipReader implements ContentReader {
      */
     public void parse(InputStream ins, ContentCreator creator)
     throws IOException, RepositoryException {
-        creator.createNode(null, "nt:folder", null);
+        creator.createNode(null, NT_FOLDER, null);
         final ZipInputStream zis = new ZipInputStream(ins);
         final InputStream dataIS = new CloseShieldInputStream(zis);
         ZipEntry entry;
@@ -78,7 +82,7 @@ class ZipReader implements ContentReader {
                     String name = entry.getName();
                     int pos = name.lastIndexOf('/');
                     if ( pos != -1 ) {
-                        creator.switchCurrentNode(name.substring(0, pos), "nt:folder");
+                        creator.switchCurrentNode(name.substring(0, pos), NT_FOLDER);
                     }
                     creator.createFileAndResourceNode(name, dataIS, null, entry.getTime());
                     creator.finishNode();

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

[sling-org-apache-sling-jcr-contentloader] 17/36: 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.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 55a2d63eea998b587c0c66e85a456e1d7f45fcd5
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jul 21 14:24:25 2008 +0000

    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.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@678436 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 3d20da2..1ad41c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>3-incubator</version>
+        <version>4-incubator-SNAPSHOT</version>
         <relativePath>../../parent/pom.xml</relativePath>
     </parent>
 

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

[sling-org-apache-sling-jcr-contentloader] 31/36: SLING-857 Apply patch by Vidar Ramdal (Thanks alot) to support XSLT for XML imports

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 3e3e882b821fef938030ea62fe264a65990ac6be
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Fri Feb 27 20:04:57 2009 +0000

    SLING-857 Apply patch by Vidar Ramdal (Thanks alot) to support XSLT
      for XML imports
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader@748677 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentReader.java  |  10 +-
 .../sling/jcr/contentloader/internal/Loader.java   |  23 +--
 .../contentloader/internal/readers/JsonReader.java |  20 ++-
 .../contentloader/internal/readers/XmlReader.java  | 185 +++++++++++++++++++--
 .../contentloader/internal/readers/ZipReader.java  |  55 +++---
 5 files changed, 232 insertions(+), 61 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
index 2d5aaf1..fef05d9 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
@@ -19,7 +19,7 @@
 package org.apache.sling.jcr.contentloader.internal;
 
 import java.io.IOException;
-import java.io.InputStream;
+import java.net.URL;
 
 import javax.jcr.RepositoryException;
 
@@ -30,13 +30,11 @@ import javax.jcr.RepositoryException;
 public interface ContentReader {
 
     /**
-     * Read the content from the input stream and create the
+     * Read the content from the URL and create the
      * content throught the provided content creator.
-     * The content reader should not close the input stream, this is
-     * done by the calling component!
-     * @param ins The input stream.
+     * @param url The input stream.
      * @throws IOException
      */
-    void parse(InputStream ins, ContentCreator creator) throws IOException, RepositoryException;
+    void parse(URL url, ContentCreator creator) throws IOException, RepositoryException;
 
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 8db1b94..ef134f6 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -482,8 +482,7 @@ public class Loader {
             }
 
             this.contentCreator.prepareParsing(parent, toPlainName(name));
-            ins = resourceUrl.openStream();
-            nodeReader.parse(ins, this.contentCreator);
+            nodeReader.parse(resourceUrl, this.contentCreator);
 
             return this.contentCreator.getRootNode();
         } catch (RepositoryException re) {
@@ -690,16 +689,16 @@ public class Loader {
             // the xml might not be System or Document View export, fall back
             // to old-style XML reading
             log.info(
-                "importSystemView: XML {} does not seem to be system view export, trying old style",
-                nodeXML);
+                "importSystemView: XML {} does not seem to be system view export, trying old style; cause: {}",
+                nodeXML, isde.toString());
             return null;
 
         } catch (RepositoryException re) {
 
             // any other repository related issue...
             log.info(
-                "importSystemView: Repository issue loading XML {}, trying old style",
-                nodeXML);
+                "importSystemView: Repository issue loading XML {}, trying old style; cause: {}",
+                nodeXML, re.toString());
             return null;
 
         } finally {
@@ -760,25 +759,15 @@ public class Loader {
             return null;
         }
 
-        InputStream ins = null;
         try {
-
-            ins = descriptor.rootNodeDescriptor.openStream();
             this.contentCreator.prepareParsing(session.getRootNode(), null);
-            descriptor.nodeReader.parse(ins, this.contentCreator);
+            descriptor.nodeReader.parse(descriptor.rootNodeDescriptor, this.contentCreator);
 
             return descriptor.rootNodeDescriptor;
         } catch (RepositoryException re) {
             throw re;
         } catch (Throwable t) {
             throw new RepositoryException(t.getMessage(), t);
-        } finally {
-            if (ins != null) {
-                try {
-                    ins.close();
-                } catch (IOException ignore) {
-                }
-            }
         }
 
     }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
index 19d7277..8a4c441 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
@@ -68,10 +68,25 @@ public class JsonReader implements ContentReader {
     };
 
     /**
-     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.io.InputStream, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.net.URL, org.apache.sling.jcr.contentloader.internal.ContentCreator)
      */
-    public void parse(InputStream ins, ContentCreator contentCreator)
+    public void parse(java.net.URL url, ContentCreator contentCreator)
     throws IOException, RepositoryException {
+        InputStream ins = null;
+        try {
+            ins = url.openStream();
+            parse(ins, contentCreator);
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+    }
+
+    public void parse(InputStream ins, ContentCreator contentCreator) throws IOException, RepositoryException {
         try {
             String jsonString = toString(ins).trim();
             if (!jsonString.startsWith("{")) {
@@ -80,7 +95,6 @@ public class JsonReader implements ContentReader {
 
             JSONObject json = new JSONObject(jsonString);
             this.createNode(null, json, contentCreator);
-
         } catch (JSONException je) {
             throw (IOException) new IOException(je.getMessage()).initCause(je);
         }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
index af3fa6a..6b6fcaf 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
@@ -18,13 +18,29 @@
  */
 package org.apache.sling.jcr.contentloader.internal.readers;
 
+import java.io.BufferedInputStream;
+import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
 
 import org.apache.sling.jcr.contentloader.internal.ContentCreator;
 import org.apache.sling.jcr.contentloader.internal.ContentReader;
@@ -73,6 +89,7 @@ public class XmlReader implements ContentReader {
      * --> </properties> </node>
      */
 
+    /** default log */
     private static final String ELEM_NODE = "node";
 
     private static final String ELEM_PRIMARY_NODE_TYPE = "primaryNodeType";
@@ -89,6 +106,10 @@ public class XmlReader implements ContentReader {
 
     private static final String ELEM_TYPE = "type";
 
+    private static final String XML_STYLESHEET_PROCESSING_INSTRUCTION = "xml-stylesheet";
+
+    private static final String HREF_ATTRIBUTE = "href";
+
     public static final ImportProvider PROVIDER = new ImportProvider() {
         private XmlReader xmlReader;
 
@@ -103,7 +124,6 @@ public class XmlReader implements ContentReader {
             return xmlReader;
         }
     };
-
     private KXmlParser xmlParser;
 
     XmlReader() {
@@ -114,24 +134,31 @@ public class XmlReader implements ContentReader {
 
 
     /**
-     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.io.InputStream, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.net.URL, org.apache.sling.jcr.contentloader.internal.ContentCreator)
      */
-    public synchronized void parse(InputStream ins, ContentCreator creator)
-    throws IOException, RepositoryException {
+    public synchronized void parse(java.net.URL url, ContentCreator creator)
+            throws IOException, RepositoryException {
+        BufferedInputStream bufferedInput = null;
         try {
-            this.parseInternal(ins, creator);
+            // We need to buffer input, so that we can reset the stream if we encounter an XSL stylesheet reference
+            bufferedInput = new BufferedInputStream(url.openStream());
+            parseInternal(bufferedInput, creator, url);
         } catch (XmlPullParserException xppe) {
             throw (IOException) new IOException(xppe.getMessage()).initCause(xppe);
+        } finally {
+            closeStream(bufferedInput);
         }
     }
 
-    private void parseInternal(InputStream ins, ContentCreator creator)
-    throws IOException, XmlPullParserException, RepositoryException {
+    private void parseInternal(InputStream bufferedInput, ContentCreator creator, java.net.URL xmlLocation) throws XmlPullParserException, IOException, RepositoryException {
         final StringBuffer contentBuffer = new StringBuffer();
-
+        // Mark the beginning of the stream. We assume that if there's an XSL processing instruction,
+        // it will occur in the first gulp - which makes sense, as processing instructions must be
+        // specified before the root elemeent of an XML file.
+        bufferedInput.mark(bufferedInput.available());
         // set the parser input, use null encoding to force detection with
         // <?xml?>
-        this.xmlParser.setInput(ins, null);
+        this.xmlParser.setInput(bufferedInput, null);
 
         NodeDescription.SHARED.clear();
         PropertyDescription.SHARED.clear();
@@ -140,8 +167,26 @@ public class XmlReader implements ContentReader {
         PropertyDescription currentProperty = null;
         String currentElement;
 
+
         int eventType = this.xmlParser.getEventType();
         while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.PROCESSING_INSTRUCTION) {
+                ProcessingInstruction pi = new ProcessingInstruction(this.xmlParser.getText());
+                // Look for a reference to an XSL stylesheet
+                if (pi.getName().equals(XML_STYLESHEET_PROCESSING_INSTRUCTION)) {
+                    // Rewind the input stream to the beginning, so that it can be transformed with XSL
+                    bufferedInput.reset();
+                    // Pipe the XML input through the XSL transformer
+                    XslTransformerStream transformerStream = new XslTransformerStream(bufferedInput, pi.getAttribute(HREF_ATTRIBUTE), xmlLocation);
+                    // Start the transformer thread
+                    transformerStream.startTransform();
+                    // Re-run the XML parser, now with the transformed XML
+                    parseInternal(transformerStream, creator, xmlLocation);
+                    transformerStream.close();
+                    return;
+
+                }
+            }
             if (eventType == XmlPullParser.START_TAG) {
 
                 currentElement = this.xmlParser.getName();
@@ -196,11 +241,90 @@ public class XmlReader implements ContentReader {
                     currentNode.addMixinType(content);
                 }
 
-            } else if (eventType == XmlPullParser.TEXT) {
+            } else if (eventType == XmlPullParser.TEXT || eventType == XmlPullParser.CDSECT) {
                 contentBuffer.append(this.xmlParser.getText());
             }
 
-            eventType = this.xmlParser.next();
+            eventType = this.xmlParser.nextToken();
+        }
+    }
+
+    /**
+     * Takes an XML input stream and pipes it through an XSL transformer.
+     * Callers should call {@link #startTransform} before trying to use the stream, or the caller will wait indefinately for input.
+     */
+    private static class XslTransformerStream extends PipedInputStream {
+        private InputStream inputXml;
+        private String xslHref;
+        private Thread transformerThread;
+        private PipedOutputStream pipedOut;
+        private URL xmlLocation;
+
+        /**
+         * Instantiate the XslTransformerStream.
+         * @param inputXml XML to be transformed.
+         * @param xslHref Path to an XSL stylesheet
+         * @param xmlLocation
+         * @throws IOException
+         */
+        public XslTransformerStream(InputStream inputXml, String xslHref, URL xmlLocation) throws IOException {
+            super();
+            this.inputXml = inputXml;
+            this.xslHref = xslHref;
+            this.transformerThread = null;
+            this.pipedOut = new PipedOutputStream(this);
+            this.xmlLocation = xmlLocation;
+        }
+
+        /**
+         * Starts the XSL transformer in a new thread, so that it can pipe its output to our <code>PipedInputStream</code>.
+         * @throws IOException
+         */
+        public void startTransform() throws IOException {
+            final URL xslResource = new java.net.URL(xmlLocation, this.xslHref);
+
+/*
+            if (xslResource == null) {
+                throw new IOException("Could not find " + xslHref);
+            }
+*/
+
+            transformerThread = new Thread(
+                    new Runnable() {
+                        public void run() {
+                            try {
+                                Source xml = new StreamSource(inputXml);
+                                Source xsl = new StreamSource(xslResource.toExternalForm());
+                                final StreamResult streamResult;
+                                final Templates templates = TransformerFactory.newInstance().newTemplates(xsl);
+                                streamResult = new StreamResult(pipedOut);
+                                templates.newTransformer().transform(xml, streamResult);
+                            } catch (TransformerConfigurationException e) {
+                                throw new RuntimeException("Error initializing XSL transformer", e);
+                            } catch (TransformerException e) {
+                                throw new RuntimeException("Error transforming", e);
+                            } finally {
+                                closeStream(pipedOut);
+                            }
+                        }
+                    }
+                    , "XslTransformerThread");
+            transformerThread.start();
+        }
+
+
+    }
+
+    /**
+     * Utility function to close a stream if it is still open.
+     * @param closeable Stream to close
+     */
+    private static void closeStream(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (IOException ignore) {
+            }
         }
     }
 
@@ -213,7 +337,7 @@ public class XmlReader implements ContentReader {
         public List<String> mixinTypes;
 
         public static NodeDescription create(NodeDescription desc, ContentCreator creator)
-        throws RepositoryException {
+                throws RepositoryException {
             if ( desc != null ) {
                 creator.createNode(desc.name, desc.primaryNodeType, desc.getMixinTypes());
                 desc.clear();
@@ -250,7 +374,7 @@ public class XmlReader implements ContentReader {
         public static PropertyDescription SHARED = new PropertyDescription();
 
         public static PropertyDescription create(PropertyDescription desc, ContentCreator creator)
-        throws RepositoryException {
+                throws RepositoryException {
             int type = (desc.type == null ? PropertyType.STRING : PropertyType.valueFromName(desc.type));
             if ( desc.isMultiValue ) {
                 creator.createProperty(desc.name, type, desc.getPropertyValues());
@@ -293,4 +417,39 @@ public class XmlReader implements ContentReader {
             this.isMultiValue = false;
         }
     }
+
+    /**
+     * Represents an XML processing instruction.<br />
+     * A processing instruction like <code>&lt;?xml-stylesheet href="stylesheet.xsl" type="text/css"?&gt</code>
+     * will have <code>name</code> == <code>"xml-stylesheet"</code> and two attributes: <code>href</code> and <code>type</code>.
+     */
+    private static class ProcessingInstruction {
+
+        private Map<String, String> attributes = new HashMap<String, String>();
+        private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\s(.[^=\\s]*)\\s?=\\s?\"(.[^\"]*)\"");
+        private static final Pattern NAME_PATTERN = Pattern.compile("^(.[^\\s\\?>]*)");
+        private String name;
+
+        public ProcessingInstruction(String text) throws IOException {
+            final Matcher nameMatcher = NAME_PATTERN.matcher(text);
+            if (!nameMatcher.find()) {
+                throw new IOException("Malformed processing instruction: " + text);
+            }
+
+            this.name = nameMatcher.group(1);
+            final Matcher attributeMatcher = ATTRIBUTE_PATTERN.matcher(text);
+            while (attributeMatcher.find()) {
+                attributes.put(attributeMatcher.group(1), attributeMatcher.group(2));
+            }
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getAttribute(String key) {
+            return this.attributes.get(key);
+        }
+
+    }
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
index 4308f4d..e4debee 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
@@ -70,34 +70,45 @@ public class ZipReader implements ContentReader {
     }
 
     /**
-     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.io.InputStream, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.net.URL, org.apache.sling.jcr.contentloader.internal.ContentCreator)
      */
-    public void parse(InputStream ins, ContentCreator creator)
+    public void parse(java.net.URL url, ContentCreator creator)
     throws IOException, RepositoryException {
-        creator.createNode(null, NT_FOLDER, null);
-        final ZipInputStream zis = new ZipInputStream(ins);
-        ZipEntry entry;
-        do {
-            entry = zis.getNextEntry();
-            if ( entry != null ) {
-                if ( !entry.isDirectory() ) {
-                    String name = entry.getName();
-                    int pos = name.lastIndexOf('/');
-                    if ( pos != -1 ) {
-                        creator.switchCurrentNode(name.substring(0, pos), NT_FOLDER);
-                    }
-                    creator.createFileAndResourceNode(name, new CloseShieldInputStream(zis), null, entry.getTime());
-                    creator.finishNode();
-                    creator.finishNode();
-                    if ( pos != -1 ) {
+        InputStream ins = null;
+        try {
+            ins = url.openStream();
+            creator.createNode(null, NT_FOLDER, null);
+            final ZipInputStream zis = new ZipInputStream(ins);
+            ZipEntry entry;
+            do {
+                entry = zis.getNextEntry();
+                if ( entry != null ) {
+                    if ( !entry.isDirectory() ) {
+                        String name = entry.getName();
+                        int pos = name.lastIndexOf('/');
+                        if ( pos != -1 ) {
+                            creator.switchCurrentNode(name.substring(0, pos), NT_FOLDER);
+                        }
+                        creator.createFileAndResourceNode(name, new CloseShieldInputStream(zis), null, entry.getTime());
+                        creator.finishNode();
                         creator.finishNode();
+                        if ( pos != -1 ) {
+                            creator.finishNode();
+                        }
                     }
+                    zis.closeEntry();
                 }
-                zis.closeEntry();
-            }
 
-        } while ( entry != null );
-        creator.finishNode();
+            } while ( entry != null );
+            creator.finishNode();
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
     }
 
 }

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

[sling-org-apache-sling-jcr-contentloader] 19/36: SLING-578 : Create intermediate nodes with type sling:Folder SLING-579 : Allow additional content information for files.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 63fc0a97e1070dc7192c4ff3beba18d6486cd505
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jul 23 05:59:48 2008 +0000

    SLING-578 : Create intermediate nodes with type sling:Folder
    SLING-579 : Allow additional content information for files.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@678998 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentLoader.java  | 10 ++++-
 .../sling/jcr/contentloader/internal/Loader.java   | 46 ++++++++++++++++------
 2 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
index 233488f..a9c656a 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -60,6 +60,8 @@ public class ContentLoader implements ContentCreator {
 
     private boolean isRootNodeImport;
 
+    private boolean ignoreOverwriteFlag = false;
+
     // default content type for createFile()
     private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
 
@@ -119,6 +121,10 @@ public class ContentLoader implements ContentCreator {
         this.versionables.clear();
     }
 
+    public void setIgnoreOverwriteFlag(boolean flag) {
+        this.ignoreOverwriteFlag = flag;
+    }
+
     /**
      * Get the created root node.
      */
@@ -172,7 +178,7 @@ public class ContentLoader implements ContentCreator {
         // if we are in root node import mode, we don't create the root top level node!
         if ( !isRootNodeImport || this.parentNodeStack.size() > 1 ) {
             // if node already exists but should be overwritten, delete it
-            if (this.configuration.isOverwrite() && parentNode.hasNode(name)) {
+            if (!this.ignoreOverwriteFlag && this.configuration.isOverwrite() && parentNode.hasNode(name)) {
                 parentNode.getNode(name).remove();
             }
 
@@ -185,7 +191,7 @@ public class ContentLoader implements ContentCreator {
 
             } else if (primaryNodeType == null) {
 
-                // node explicit node type, use repository default
+                // no explicit node type, use repository default
                 node = parentNode.addNode(name);
 
             } else {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 96e771e..3d7420c 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -313,8 +313,7 @@ public class Loader {
             if (entry.endsWith("/")) {
 
                 // dir, check for node descriptor , else create dir
-                String base = entry.substring(0, entry.length() - 1);
-                String name = getName(base);
+                final String base = entry.substring(0, entry.length() - 1);
 
                 URL nodeDescriptor = null;
                 for (String ext : this.contentCreator.getImportProviders().keySet()) {
@@ -327,6 +326,7 @@ public class Loader {
                 // if we have a descriptor, which has not been processed yet,
                 // otherwise call createFolder, which creates an nt:folder or
                 // returns an existing node (created by a descriptor)
+                final String name = getName(base);
                 Node node = null;
                 if (nodeDescriptor != null) {
                     node = processedEntries.get(nodeDescriptor);
@@ -347,28 +347,52 @@ public class Loader {
             } else {
 
                 // file => create file
-                URL file = bundle.getEntry(entry);
+                final URL file = bundle.getEntry(entry);
                 if (processedEntries.containsKey(file)) {
                     // this is a consumed node descriptor
                     continue;
                 }
+                final String name = getName(entry);
+
+                // file, check for node descriptor , else create dir
+                URL nodeDescriptor = null;
+                for (String ext : this.contentCreator.getImportProviders().keySet()) {
+                    nodeDescriptor = bundle.getEntry(entry + ext);
+                    if (nodeDescriptor != null) {
+                        break;
+                    }
+                }
 
                 // install if it is a descriptor
                 boolean foundProvider = this.contentCreator.getImportProvider(entry) != null;
 
+                Node node = null;
                 if (foundProvider) {
-                    Node node = null;
-                    if ((node = createNode(parent, getName(entry), file, configuration)) != null) {
+                    if ((node = createNode(parent, name, file, configuration)) != null) {
                         processedEntries.put(file, node);
-                        continue;
                     }
                 }
 
                 // otherwise just place as file
-                try {
-                    createFile(configuration, parent, file);
-                } catch (IOException ioe) {
-                    log.warn("Cannot create file node for {}", file, ioe);
+                if ( node == null ) {
+                    try {
+                        createFile(configuration, parent, file);
+                        node = parent.getNode(name);
+                    } catch (IOException ioe) {
+                        log.warn("Cannot create file node for {}", file, ioe);
+                    }
+                }
+                // if we have a descriptor, which has not been processed yet,
+                // process it
+                if (nodeDescriptor != null && processedEntries.get(nodeDescriptor) == null ) {
+                    try {
+                        this.contentCreator.setIgnoreOverwriteFlag(true);
+                        node = createNode(parent, name, nodeDescriptor,
+                                          configuration);
+                        processedEntries.put(nodeDescriptor, node);
+                    } finally {
+                        this.contentCreator.setIgnoreOverwriteFlag(false);
+                    }
                 }
             }
         }
@@ -463,7 +487,7 @@ public class Loader {
             }
         }
 
-        return parent.addNode(name, "nt:folder");
+        return parent.addNode(name, "sling:Folder");
     }
 
     /**

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

[sling-org-apache-sling-jcr-contentloader] 05/36: SLING-545 Apply modified patch (see issue for modification details)

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit b748cf1319cf88926c945719a8393ea3bea5c186
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Thu Jun 19 12:35:30 2008 +0000

    SLING-545 Apply modified patch (see issue for modification details)
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@669469 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/jcr/contentloader/internal/Loader.java   | 26 +++++++++++++---------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index ca6c28f..53f9d2f 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -295,13 +295,14 @@ public class Loader {
             return;
         }
 
-        Set<URL> ignoreEntry = new HashSet<URL>();
+        Map<URL, Node> processedEntries = new HashMap<URL, Node>();
 
         // potential root node import/extension
         URL rootNodeDescriptor = importRootNode(parent.getSession(), bundle,
             path, versionables, checkin);
         if (rootNodeDescriptor != null) {
-            ignoreEntry.add(rootNodeDescriptor);
+            processedEntries.put(rootNodeDescriptor,
+                parent.getSession().getRootNode());
         }
 
         while (entries.hasMoreElements()) {
@@ -325,11 +326,13 @@ public class Loader {
                 // otherwise call createFolder, which creates an nt:folder or
                 // returns an existing node (created by a descriptor)
                 Node node = null;
-                if (nodeDescriptor != null
-                    && !ignoreEntry.contains(nodeDescriptor)) {
-                    node = createNode(parent, name, nodeDescriptor, overwrite,
-                        versionables, checkin);
-                    ignoreEntry.add(nodeDescriptor);
+                if (nodeDescriptor != null) {
+                    node = processedEntries.get(nodeDescriptor);
+                    if (node == null) {
+                        node = createNode(parent, name, nodeDescriptor,
+                            overwrite, versionables, checkin);
+                        processedEntries.put(nodeDescriptor, node);
+                    }
                 } else {
                     node = createFolder(parent, name, overwrite);
                 }
@@ -344,7 +347,7 @@ public class Loader {
                 
                 // file => create file
                 URL file = bundle.getEntry(entry);
-                if (ignoreEntry.contains(file)) {
+                if (processedEntries.containsKey(file)) {
                     // this is a consumed node descriptor
                     continue;
                 }
@@ -359,9 +362,10 @@ public class Loader {
                     }
                 }
                 if (foundProvider) {
-                    if (createNode(parent, getName(entry), file, overwrite,
-                        versionables, checkin) != null) {
-                        ignoreEntry.add(file);
+                    Node node = null;
+                    if ((node = createNode(parent, getName(entry), file, overwrite,
+                        versionables, checkin)) != null) {
+                        processedEntries.put(file, node);
                         continue;
                     }
                 }

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

[sling-org-apache-sling-jcr-contentloader] 07/36: SLING-548: Remove wrong test src dir.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 0a0ce102b4f0c520c166e47f0da08971f3fb95aa
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 09:19:14 2008 +0000

    SLING-548: Remove wrong test src dir.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670489 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-contentloader] 21/36: SLING-766 : Don't uninstall content when bundle is stopped. The content is now only uninstalled when the bundle is uninstalled. During installation a string array containing all paths that will be removed later on is collected which makes the uninstall easier. Also refactored the classes and packages a little bit.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 5736f529eee826aebb640984215f426f0049ce55
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Dec 16 13:32:17 2008 +0000

    SLING-766 : Don't uninstall content when bundle is stopped. The content is now only uninstalled when the bundle is uninstalled. During installation a string
    array containing all paths that will be removed later on is collected which makes the uninstall easier. Also refactored the classes and packages a little bit.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@727047 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentCreator.java |   2 +-
 .../internal/ContentLoaderService.java             |  40 +++-
 .../jcr/contentloader/internal/ContentReader.java  |   2 +-
 ...ntentLoader.java => DefaultContentCreator.java} |  52 ++++-
 .../jcr/contentloader/internal/ImportProvider.java |   4 +-
 .../sling/jcr/contentloader/internal/Loader.java   | 238 +++++++--------------
 .../internal/{ => readers}/JsonReader.java         |   9 +-
 .../internal/{ => readers}/XmlReader.java          |   7 +-
 .../internal/{ => readers}/ZipReader.java          |  11 +-
 .../jcr/contentloader/internal/JsonReaderTest.java |   1 +
 10 files changed, 177 insertions(+), 189 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index 4390f4e..3e93ac1 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -28,7 +28,7 @@ import javax.jcr.RepositoryException;
  *
  * @since 2.0.4
  */
-interface ContentCreator {
+public interface ContentCreator {
 
     /**
      * Create a new node.
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
index 341fda5..9781ce5 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
@@ -21,6 +21,7 @@ package org.apache.sling.jcr.contentloader.internal;
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
@@ -28,6 +29,7 @@ import java.util.StringTokenizer;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.Value;
 import javax.jcr.lock.LockException;
 
 import org.apache.sling.commons.mime.MimeTypeService;
@@ -56,6 +58,11 @@ import org.slf4j.LoggerFactory;
 public class ContentLoaderService implements SynchronousBundleListener {
 
     public static final String PROPERTY_CONTENT_LOADED = "content-loaded";
+    private static final String PROPERTY_CONTENT_LOADED_AT = "content-load-time";
+    private static final String PROPERTY_CONTENT_LOADED_BY = "content-loaded-by";
+    private static final String PROPERTY_CONTENT_UNLOADED_AT = "content-unload-time";
+    private static final String PROPERTY_CONTENT_UNLOADED_BY = "content-unloaded-by";
+    public static final String PROPERTY_UNINSTALL_PATHS = "uninstall-paths";
 
     public static final String BUNDLE_CONTENT_NODE = "/var/sling/bundle-content";
 
@@ -140,7 +147,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
                 // we will use this info when the new start event is triggered
                 this.updatedBundles.add(event.getBundle().getSymbolicName());
                 break;
-            case BundleEvent.STOPPED:
+            case BundleEvent.UNINSTALLED:
                 try {
                     Session session = getAdminSession();
                     initialContentLoader.unregisterBundle(session, event.getBundle());
@@ -269,11 +276,14 @@ public class ContentLoaderService implements SynchronousBundleListener {
      * @return The map of bundle content info or null.
      * @throws RepositoryException
      */
-    public Map<String, Object> getBundleContentInfo(final Session session, final Bundle bundle)
+    public Map<String, Object> getBundleContentInfo(final Session session, final Bundle bundle, boolean create)
     throws RepositoryException {
         final String nodeName = bundle.getSymbolicName();
         final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
         if ( !parentNode.hasNode(nodeName) ) {
+            if ( !create ) {
+                return null;
+            }
             try {
                 final Node bcNode = parentNode.addNode(nodeName, "nt:unstructured");
                 bcNode.addMixin("mix:lockable");
@@ -300,22 +310,34 @@ public class ContentLoaderService implements SynchronousBundleListener {
         } else {
             info.put(ContentLoaderService.PROPERTY_CONTENT_LOADED, false);
         }
+        if ( bcNode.hasProperty(ContentLoaderService.PROPERTY_UNINSTALL_PATHS) ) {
+            final Value[] values = bcNode.getProperty(PROPERTY_UNINSTALL_PATHS).getValues();
+            final String[] s = new String[values.length];
+            for(int i=0; i<values.length; i++) {
+                s[i] = values[i].getString();
+            }
+            info.put(ContentLoaderService.PROPERTY_UNINSTALL_PATHS, s);
+        }
         return info;
     }
 
     public void unlockBundleContentInfo(final Session session,
                                         final Bundle  bundle,
-                                        final boolean contentLoaded)
+                                        final boolean contentLoaded,
+                                        final List<String> createdNodes)
     throws RepositoryException {
         final String nodeName = bundle.getSymbolicName();
         final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
         final Node bcNode = parentNode.getNode(nodeName);
         if ( contentLoaded ) {
             bcNode.setProperty(ContentLoaderService.PROPERTY_CONTENT_LOADED, contentLoaded);
-            bcNode.setProperty("content-load-time", Calendar.getInstance());
-            bcNode.setProperty("content-loaded-by", this.slingId);
-            bcNode.setProperty("content-unload-time", (String)null);
-            bcNode.setProperty("content-unloaded-by", (String)null);
+            bcNode.setProperty(PROPERTY_CONTENT_LOADED_AT, Calendar.getInstance());
+            bcNode.setProperty(PROPERTY_CONTENT_LOADED_BY, this.slingId);
+            bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, (String)null);
+            bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, (String)null);
+            if ( createdNodes != null && createdNodes.size() > 0 ) {
+                bcNode.setProperty(PROPERTY_UNINSTALL_PATHS, createdNodes.toArray(new String[createdNodes.size()]));
+            }
             bcNode.save();
         }
         bcNode.unlock();
@@ -329,8 +351,8 @@ public class ContentLoaderService implements SynchronousBundleListener {
             if ( parentNode.hasNode(nodeName) ) {
                 final Node bcNode = parentNode.getNode(nodeName);
                 bcNode.setProperty(ContentLoaderService.PROPERTY_CONTENT_LOADED, false);
-                bcNode.setProperty("content-unload-time", Calendar.getInstance());
-                bcNode.setProperty("content-unloaded-by", this.slingId);
+                bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, Calendar.getInstance());
+                bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, this.slingId);
                 bcNode.save();
             }
         } catch (RepositoryException re) {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
index b36fa7d..2d5aaf1 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
@@ -27,7 +27,7 @@ import javax.jcr.RepositoryException;
  * The <code>ContentReader</code>
  * A content reader is provided by an {@link ImportProvider}.
  */
-interface ContentReader {
+public interface ContentReader {
 
     /**
      * Read the content from the input stream and create the
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
similarity index 92%
rename from src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
rename to src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
index a9c656a..c22b509 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
@@ -41,7 +41,7 @@ import javax.jcr.ValueFactory;
  * The <code>ContentLoader</code> creates the nodes and properties.
  * @since 2.0.4
  */
-public class ContentLoader implements ContentCreator {
+public class DefaultContentCreator implements ContentCreator {
 
     private PathEntry configuration;
 
@@ -65,21 +65,32 @@ public class ContentLoader implements ContentCreator {
     // default content type for createFile()
     private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
 
+    /** Helper class to get the mime type of a file. */
     private final ContentLoaderService jcrContentHelper;
 
+    /** List of active import providers mapped by extension. */
     private Map<String, ImportProvider> importProviders;
 
-    public ContentLoader(ContentLoaderService jcrContentHelper) {
+    /** Optional list of created nodes (for uninstall) */
+    private List<String> createdNodes;
+
+    /**
+     * Constructor.
+     * @param jcrContentHelper Helper class to get the mime type of a file
+     */
+    public DefaultContentCreator(ContentLoaderService jcrContentHelper) {
         this.jcrContentHelper = jcrContentHelper;
     }
 
     /**
      * Initialize this component.
-     * @param pathEntry
+     * @param pathEntry The configuration for this import.
+     * @param defaultImportProviders List of all import providers.
+     * @param createdNodes Optional list to store new nodes (for uninstall)
      */
     public void init(final PathEntry pathEntry,
-                     final Map<String, ImportProvider> defaultImportProviders) {
-
+                     final Map<String, ImportProvider> defaultImportProviders,
+                     final List<String> createdNodes) {
         this.configuration = pathEntry;
         // create list of allowed import providers
         this.importProviders = new HashMap<String, ImportProvider>();
@@ -90,6 +101,7 @@ public class ContentLoader implements ContentCreator {
                 importProviders.put(current.getKey(), current.getValue());
             }
         }
+        this.createdNodes = createdNodes;
     }
 
     /**
@@ -121,6 +133,10 @@ public class ContentLoader implements ContentCreator {
         this.versionables.clear();
     }
 
+    /**
+     * Set the ignore overwrite flag.
+     * @param flag
+     */
     public void setIgnoreOverwriteFlag(boolean flag) {
         this.ignoreOverwriteFlag = flag;
     }
@@ -132,10 +148,19 @@ public class ContentLoader implements ContentCreator {
         return this.rootNode;
     }
 
+    /**
+     * Get all active import providers.
+     * @return A map of providers
+     */
     public Map<String, ImportProvider> getImportProviders() {
         return this.importProviders;
     }
 
+    /**
+     * Return the import provider for the name
+     * @param name The file name.
+     * @return The provider or <code>null</code>
+     */
     public ImportProvider getImportProvider(String name) {
         ImportProvider provider = null;
         final Iterator<String> ipIter = importProviders.keySet().iterator();
@@ -148,6 +173,11 @@ public class ContentLoader implements ContentCreator {
         return provider;
     }
 
+    /**
+     * Get the extension of the file name.
+     * @param name The file name.
+     * @return The extension a provider is registered for - or <code>null</code>
+     */
     public String getImportProviderExtension(String name) {
         String providerExt = null;
         final Iterator<String> ipIter = importProviders.keySet().iterator();
@@ -188,16 +218,21 @@ public class ContentLoader implements ContentCreator {
 
                 // use existing node
                 node = parentNode.getNode(name);
-
             } else if (primaryNodeType == null) {
 
                 // no explicit node type, use repository default
                 node = parentNode.addNode(name);
+                if ( this.createdNodes != null ) {
+                    this.createdNodes.add(node.getPath());
+                }
 
             } else {
 
                 // explicit primary node type
                 node = parentNode.addNode(name, primaryNodeType);
+                if ( this.createdNodes != null ) {
+                    this.createdNodes.add(node.getPath());
+                }
             }
 
             // ammend mixin node types
@@ -557,7 +592,10 @@ public class ContentLoader implements ContentCreator {
                 if ( newNodeType == null ) {
                     return false;
                 }
-                node.addNode(token, newNodeType);
+                final Node n = node.addNode(token, newNodeType);
+                if ( this.createdNodes != null ) {
+                    this.createdNodes.add(n.getPath());
+                }
             }
             node = node.getNode(token);
         }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
index 2cf8340..cfd85f6 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
@@ -20,8 +20,8 @@ package org.apache.sling.jcr.contentloader.internal;
 
 import java.io.IOException;
 
-interface ImportProvider {
+public interface ImportProvider {
 
     ContentReader getReader() throws IOException;
-    
+
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 83622af..8db1b94 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -26,15 +26,15 @@ import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.StringTokenizer;
 
 import javax.jcr.InvalidSerializedDataException;
@@ -43,6 +43,9 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
+import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
+import org.apache.sling.jcr.contentloader.internal.readers.XmlReader;
+import org.apache.sling.jcr.contentloader.internal.readers.ZipReader;
 import org.osgi.framework.Bundle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -72,14 +75,14 @@ public class Loader {
     /** All available import providers. */
     private Map<String, ImportProvider> defaultImportProviders;
 
-    private final ContentLoader contentCreator;
+    private final DefaultContentCreator contentCreator;
 
     // bundles whose registration failed and should be retried
     private List<Bundle> delayedBundles;
 
     public Loader(ContentLoaderService jcrContentHelper) {
         this.jcrContentHelper = jcrContentHelper;
-        this.contentCreator = new ContentLoader(jcrContentHelper);
+        this.contentCreator = new DefaultContentCreator(jcrContentHelper);
         this.delayedBundles = new LinkedList<Bundle>();
 
         defaultImportProviders = new LinkedHashMap<String, ImportProvider>();
@@ -108,6 +111,10 @@ public class Loader {
     public void registerBundle(final Session session,
                                final Bundle bundle,
                                final boolean isUpdate) {
+        // if this is an update, we have to uninstall the old content first
+        if ( isUpdate ) {
+            this.unregisterBundle(session, bundle);
+        }
 
         log.debug("Registering bundle {} for content loading.",
             bundle.getSymbolicName());
@@ -153,7 +160,7 @@ public class Loader {
 
             // check if the content has already been loaded
             final Map<String, Object> bundleContentInfo = jcrContentHelper.getBundleContentInfo(
-                session, bundle);
+                session, bundle, true);
 
             // if we don't get an info, someone else is currently loading
             if (bundleContentInfo == null) {
@@ -161,6 +168,7 @@ public class Loader {
             }
 
             boolean success = false;
+            List<String> createdNodes = null;
             try {
 
                 final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED)).booleanValue();
@@ -172,7 +180,7 @@ public class Loader {
 
                 } else {
 
-                    installContent(session, bundle, pathIter,
+                    createdNodes = installContent(session, bundle, pathIter,
                         contentAlreadyLoaded);
 
                     if (isRetry) {
@@ -189,7 +197,7 @@ public class Loader {
 
             } finally {
                 jcrContentHelper.unlockBundleContentInfo(session, bundle,
-                    success);
+                    success, createdNodes);
             }
 
         } catch (RepositoryException re) {
@@ -211,29 +219,48 @@ public class Loader {
      */
     public void unregisterBundle(final Session session, final Bundle bundle) {
 
-        // check if bundle has initial content
-        final Iterator<PathEntry> pathIter = PathEntry.getContentPaths(bundle);
         if (delayedBundles.contains(bundle)) {
 
             delayedBundles.remove(bundle);
 
         } else {
+            try {
+                final Map<String, Object> bundleContentInfo = jcrContentHelper.getBundleContentInfo(
+                        session, bundle, false);
 
-            if (pathIter != null) {
-                uninstallContent(session, bundle, pathIter);
-                jcrContentHelper.contentIsUninstalled(session, bundle);
-            }
+                // if we don't get an info, someone else is currently loading or unloading
+                // or the bundle is already uninstalled
+                if (bundleContentInfo == null) {
+                    return;
+                }
 
+                try {
+                    uninstallContent(session, bundle, (String[])bundleContentInfo.get(ContentLoaderService.PROPERTY_UNINSTALL_PATHS));
+                    jcrContentHelper.contentIsUninstalled(session, bundle);
+                } finally {
+                    jcrContentHelper.unlockBundleContentInfo(session, bundle, false, null);
+
+                }
+            } catch (RepositoryException re) {
+                log.error("Cannot remove initial content for bundle "
+                        + bundle.getSymbolicName() + " : " + re.getMessage(), re);
+            }
         }
     }
 
     // ---------- internal -----------------------------------------------------
 
-    private void installContent(final Session session,
-                                final Bundle bundle,
-                                final Iterator<PathEntry> pathIter,
-                                final boolean contentAlreadyLoaded)
+    /**
+     * Install the content from the bundle.
+     * @return If the content should be removed on uninstall, a list of top nodes
+     */
+    private List<String> installContent(final Session session,
+                                        final Bundle bundle,
+                                        final Iterator<PathEntry> pathIter,
+                                        final boolean contentAlreadyLoaded)
     throws RepositoryException {
+        final List<String> createdNodes = new ArrayList<String>();
+
         log.debug("Installing initial content from bundle {}",
             bundle.getSymbolicName());
         try {
@@ -245,7 +272,23 @@ public class Loader {
                     final Node targetNode = getTargetNode(session, entry.getTarget());
 
                     if (targetNode != null) {
-                        installFromPath(bundle, entry.getPath(), entry, targetNode);
+                        installFromPath(bundle, entry.getPath(), entry, targetNode,
+                            entry.isUninstall() ? createdNodes : null);
+                    }
+                }
+            }
+
+            // now optimize created nodes list
+            Collections.sort(createdNodes);
+            if ( createdNodes.size() > 1) {
+                final Iterator<String> i = createdNodes.iterator();
+                String previous = i.next() + '/';
+                while ( i.hasNext() ) {
+                    final String current = i.next();
+                    if ( current.startsWith(previous) ) {
+                        i.remove();
+                    } else {
+                        previous = current + '/';
                     }
                 }
             }
@@ -274,6 +317,7 @@ public class Loader {
         log.debug("Done installing initial content from bundle {}",
             bundle.getSymbolicName());
 
+        return createdNodes;
     }
 
     /**
@@ -283,12 +327,14 @@ public class Loader {
      * @param path The path
      * @param overwrite Should the content be overwritten.
      * @param parent The parent node.
+     * @param createdNodes An optional list to store all new nodes. This list is used for an uninstall
      * @throws RepositoryException
      */
     private void installFromPath(final Bundle bundle,
                                  final String path,
                                  final PathEntry configuration,
-                                 final Node parent)
+                                 final Node parent,
+                                 final List<String> createdNodes)
     throws RepositoryException {
 
         @SuppressWarnings("unchecked")
@@ -298,7 +344,7 @@ public class Loader {
             return;
         }
         //  init content creator
-        this.contentCreator.init(configuration, this.defaultImportProviders);
+        this.contentCreator.init(configuration, this.defaultImportProviders, createdNodes);
 
         final Map<URL, Node> processedEntries = new HashMap<URL, Node>();
         // potential root node import/extension
@@ -342,7 +388,7 @@ public class Loader {
 
                 // walk down the line
                 if (node != null) {
-                    installFromPath(bundle, entry, configuration, node);
+                    installFromPath(bundle, entry, configuration, node, createdNodes);
                 }
 
             } else {
@@ -377,7 +423,7 @@ public class Loader {
                 // otherwise just place as file
                 if ( node == null ) {
                     try {
-                        createFile(configuration, parent, file);
+                        createFile(configuration, parent, file, createdNodes);
                         node = parent.getNode(name);
                     } catch (IOException ioe) {
                         log.warn("Cannot create file node for {}", file, ioe);
@@ -455,21 +501,6 @@ public class Loader {
     }
 
     /**
-     * Delete the node from the initial content.
-     *
-     * @param parent
-     * @param name
-     * @param nodeXML
-     * @throws RepositoryException
-     */
-    private void deleteNode(Node parent, String name)
-            throws RepositoryException {
-        if (parent.hasNode(name)) {
-            parent.getNode(name).remove();
-        }
-    }
-
-    /**
      * Create a folder
      *
      * @param parent The parent node.
@@ -499,7 +530,7 @@ public class Loader {
      * @throws IOException
      * @throws RepositoryException
      */
-    private void createFile(PathEntry configuration, Node parent, URL source)
+    private void createFile(PathEntry configuration, Node parent, URL source, List<String> createdNodes)
     throws IOException, RepositoryException {
         final String srcPath = source.getPath();
         int pos = srcPath.lastIndexOf("/");
@@ -511,7 +542,7 @@ public class Loader {
             path = srcPath.substring(0, pos + 1) + name;
         }
 
-        this.contentCreator.init(configuration, defaultImportProviders);
+        this.contentCreator.init(configuration, defaultImportProviders, createdNodes);
         this.contentCreator.prepareParsing(parent, name);
         final URLConnection conn = source.openConnection();
         final long lastModified = conn.getLastModified();
@@ -523,22 +554,6 @@ public class Loader {
     }
 
     /**
-     * Delete the file from the given url.
-     *
-     * @param parent
-     * @param source
-     * @throws IOException
-     * @throws RepositoryException
-     */
-    private void deleteFile(Node parent, URL source) throws IOException,
-            RepositoryException {
-        String name = getName(source.getPath());
-        if (parent.hasNode(name)) {
-            parent.getNode(name).remove();
-        }
-    }
-
-    /**
      * Gets and decods the name part of the <code>path</code>. The name is
      * the part of the path after the last slash (or the complete path if no
      * slash is contained). To support names containing unsupported characters
@@ -603,25 +618,20 @@ public class Loader {
     }
 
     private void uninstallContent(final Session session, final Bundle bundle,
-            final Iterator<PathEntry> pathIter) {
+            final String[] uninstallPaths) {
         try {
             log.debug("Uninstalling initial content from bundle {}",
                 bundle.getSymbolicName());
-            while (pathIter.hasNext()) {
-                final PathEntry entry = pathIter.next();
-                if (entry.isUninstall()) {
-                    Node targetNode = getTargetNode(session, entry.getTarget());
-                    if (targetNode != null)
-                        uninstallFromPath(bundle, entry.getPath(), entry, targetNode);
-                } else {
-                    log.debug(
-                        "Ignoring to uninstall content at {}, uninstall directive is not set.",
-                        entry.getPath());
+            if ( uninstallPaths != null && uninstallPaths.length > 0 ) {
+                for(final String path : uninstallPaths) {
+                    if ( session.itemExists(path) ) {
+                        session.getItem(path).remove();
+                    }
                 }
+                // persist modifications now
+                session.save();
             }
 
-            // persist modifications now
-            session.save();
             log.debug("Done uninstalling initial content from bundle {}",
                 bundle.getSymbolicName());
         } catch (RepositoryException re) {
@@ -641,98 +651,6 @@ public class Loader {
     }
 
     /**
-     * Handle content uninstallation for a single path.
-     *
-     * @param bundle The bundle containing the content.
-     * @param path The path
-     * @param parent The parent node.
-     * @throws RepositoryException
-     */
-    private void uninstallFromPath(final Bundle bundle,
-                                   final String path,
-                                   final PathEntry configuration,
-                                   final Node parent) throws RepositoryException {
-        @SuppressWarnings("unchecked")
-        Enumeration<String> entries = bundle.getEntryPaths(path);
-        if (entries == null) {
-            return;
-        }
-
-        this.contentCreator.init(configuration, defaultImportProviders);
-
-        final Set<URL> ignoreEntry = new HashSet<URL>();
-
-        // potential root node import/extension
-        Descriptor rootNodeDescriptor = getRootNodeDescriptor(bundle, path);
-        if (rootNodeDescriptor != null) {
-            ignoreEntry.add(rootNodeDescriptor.rootNodeDescriptor);
-        }
-
-        while (entries.hasMoreElements()) {
-            final String entry = entries.nextElement();
-            log.debug("Processing initial content entry {}", entry);
-            if (entry.endsWith("/")) {
-                // dir, check for node descriptor , else create dir
-                String base = entry.substring(0, entry.length() - 1);
-                String name = getName(base);
-
-                URL nodeDescriptor = null;
-                for (String ext : this.contentCreator.getImportProviders().keySet()) {
-                    nodeDescriptor = bundle.getEntry(base + ext);
-                    if (nodeDescriptor != null) {
-                        break;
-                    }
-                }
-
-                final Node node;
-                boolean delete = false;
-                if (nodeDescriptor != null
-                    && !ignoreEntry.contains(nodeDescriptor)) {
-                    node = (parent.hasNode(toPlainName(name))
-                            ? parent.getNode(toPlainName(name))
-                            : null);
-                    delete = true;
-                } else {
-                    node = (parent.hasNode(name) ? parent.getNode(name) : null);
-                }
-
-                if (node != null) {
-                    // walk down the line
-                    uninstallFromPath(bundle, entry, configuration, node);
-                }
-
-                if (delete) {
-                    deleteNode(parent, toPlainName(name));
-                    ignoreEntry.add(nodeDescriptor);
-                }
-
-            } else {
-                // file => create file
-                URL file = bundle.getEntry(entry);
-                if (ignoreEntry.contains(file)) {
-                    // this is a consumed node descriptor
-                    continue;
-                }
-
-                // uninstall if it is a descriptor
-                boolean foundProvider = this.contentCreator.getImportProvider(entry) != null;
-                if (foundProvider) {
-                    deleteNode(parent, toPlainName(getName(entry)));
-                    ignoreEntry.add(file);
-                    continue;
-                }
-
-                // otherwise just delete the file
-                try {
-                    deleteFile(parent, file);
-                } catch (IOException ioe) {
-                    log.warn("Cannot delete file node for {}", file, ioe);
-                }
-            }
-        }
-    }
-
-    /**
      * Import the XML file as JCR system or document view import. If the XML
      * file is not a valid system or document view export/import file,
      * <code>false</code> is returned.
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
similarity index 94%
rename from src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
rename to src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
index 78a7f3e..19d7277 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.jcr.contentloader.internal;
+package org.apache.sling.jcr.contentloader.internal.readers;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
@@ -31,12 +31,15 @@ import javax.jcr.RepositoryException;
 import org.apache.sling.commons.json.JSONArray;
 import org.apache.sling.commons.json.JSONException;
 import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+import org.apache.sling.jcr.contentloader.internal.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.ImportProvider;
 
 
 /**
  * The <code>JsonReader</code> TODO
  */
-class JsonReader implements ContentReader {
+public class JsonReader implements ContentReader {
 
     private static final String REFERENCE = "jcr:reference:";
     private static final String PATH = "jcr:path:";
@@ -53,7 +56,7 @@ class JsonReader implements ContentReader {
         ignoredNames.add("jcr:created");
     }
 
-    static final ImportProvider PROVIDER = new ImportProvider() {
+    public static final ImportProvider PROVIDER = new ImportProvider() {
         private JsonReader jsonReader;
 
         public ContentReader getReader() {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
similarity index 96%
rename from src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java
rename to src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
index 98dd4a6..af3fa6a 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.jcr.contentloader.internal;
+package org.apache.sling.jcr.contentloader.internal.readers;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -26,6 +26,9 @@ import java.util.List;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+import org.apache.sling.jcr.contentloader.internal.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.ImportProvider;
 import org.kxml2.io.KXmlParser;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -86,7 +89,7 @@ public class XmlReader implements ContentReader {
 
     private static final String ELEM_TYPE = "type";
 
-    static final ImportProvider PROVIDER = new ImportProvider() {
+    public static final ImportProvider PROVIDER = new ImportProvider() {
         private XmlReader xmlReader;
 
         public ContentReader getReader() throws IOException {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
similarity index 86%
rename from src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
rename to src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
index fff0c82..4308f4d 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.jcr.contentloader.internal;
+package org.apache.sling.jcr.contentloader.internal.readers;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -26,6 +26,9 @@ import java.util.zip.ZipInputStream;
 import javax.jcr.RepositoryException;
 
 import org.apache.commons.io.input.CloseShieldInputStream;
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+import org.apache.sling.jcr.contentloader.internal.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.ImportProvider;
 
 
 /**
@@ -33,11 +36,11 @@ import org.apache.commons.io.input.CloseShieldInputStream;
  *
  * @since 2.0.4
  */
-class ZipReader implements ContentReader {
+public class ZipReader implements ContentReader {
 
     private static final String NT_FOLDER = "nt:folder";
 
-    static final ImportProvider ZIP_PROVIDER = new ImportProvider() {
+    public static final ImportProvider ZIP_PROVIDER = new ImportProvider() {
         private ZipReader zipReader;
 
         public ContentReader getReader() {
@@ -48,7 +51,7 @@ class ZipReader implements ContentReader {
         }
     };
 
-    static final ImportProvider JAR_PROVIDER = new ImportProvider() {
+    public static final ImportProvider JAR_PROVIDER = new ImportProvider() {
         private ZipReader zipReader;
 
         public ContentReader getReader() {
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
index 5b3463f..d403145 100644
--- a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
@@ -26,6 +26,7 @@ import javax.jcr.RepositoryException;
 
 import org.apache.sling.commons.json.JSONArray;
 import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 import org.jmock.Sequence;

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

[sling-org-apache-sling-jcr-contentloader] 03/36: [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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit c706febf53d197494b22005be79e7d1374d43e3b
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jun 17 07:27:48 2008 +0000

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

diff --git a/pom.xml b/pom.xml
index 55725f2..d46c665 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
     </parent>
 
     <artifactId>org.apache.sling.jcr.contentloader</artifactId>
-    <version>2.0.2-incubator</version>
+    <version>2.0.3-incubator-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Sling - Initial Content Loader</name>
@@ -36,9 +36,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.2-incubator</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.2-incubator</developerConnection>
-        <url>http://svn.apache.org/viewvc/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.2-incubator</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader</developerConnection>
+        <url>http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/contentloader</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-contentloader] 11/36: SLING-548: Provide methods for creating file nodes and to change the current context node.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit b042f5d41b7e6b4815b777bcb40d417edbd13c2e
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 13:27:25 2008 +0000

    SLING-548: Provide methods for creating file nodes and to change the current context node.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670562 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentCreator.java | 40 ++++++++++-
 .../jcr/contentloader/internal/ContentLoader.java  | 83 +++++++++++++++++++++-
 .../internal/ContentLoaderService.java             |  2 +-
 .../sling/jcr/contentloader/internal/Loader.java   | 57 ++++++---------
 4 files changed, 143 insertions(+), 39 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index bd72de7..3e9acaf 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -18,6 +18,9 @@
  */
 package org.apache.sling.jcr.contentloader.internal;
 
+import java.io.InputStream;
+
+import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 
 /**
@@ -38,7 +41,7 @@ interface ContentCreator {
      * @param mixinNodeTypes The mixin node types or null.
      * @throws RepositoryException If anything goes wrong.
      */
-    void createNode(String name,
+    Node createNode(String name,
                     String primaryNodeType,
                     String[] mixinNodeTypes)
     throws RepositoryException;
@@ -95,4 +98,39 @@ interface ContentCreator {
     void createProperty(String name,
                         Object[] values)
     throws RepositoryException;
+
+    /**
+     * Create a file and a resource node.
+     * After the nodes have been created, the current node is the resource node.
+     * So this method call should be followed by two calls to {@link #finishNode()}
+     * to be on the same level as before the file creation.
+     * @param name The name of the file node
+     * @param data The data of the file
+     * @param mimeType The mime type or null
+     * @param lastModified The last modified or -1
+     * @throws RepositoryException
+     */
+    void createFileAndResourceNode(String name,
+                                   InputStream data,
+                                   String      mimeType,
+                                   long         lastModified)
+    throws RepositoryException;
+
+    /**
+     * Switch the current node to the path (which must be relative
+     * to the root node of the import).
+     * If the path does not exist and a node type is supplied,
+     * the nodes are created with the given node type.
+     * If the path does not exist and node type is null, false is
+     * returned.
+     * Switching is only allowed if the current node is the root node
+     * of the import.
+     * When the changes to the node are finished, {@link #finishNode()}
+     * must be callsed.
+     * @param subPath The relative path
+     * @param newNodeType Node typ for newly created nodes.
+     * @throws RepositoryException
+     */
+    boolean switchCurrentNode(String subPath, String newNodeType)
+    throws RepositoryException;
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
index 415b5a7..fe2e544 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
+import java.util.StringTokenizer;
 
 import javax.jcr.Item;
 import javax.jcr.Node;
@@ -56,6 +57,15 @@ public class ContentLoader implements ContentCreator {
 
     private boolean isRootNodeImport;
 
+    // default content type for createFile()
+    private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
+
+    private final ContentLoaderService jcrContentHelper;
+
+    public ContentLoader(ContentLoaderService jcrContentHelper) {
+        this.jcrContentHelper = jcrContentHelper;
+    }
+
     /**
      * Initialize this component.
      * If the defaultRootName is null, we are in ROOT_NODE import mode.
@@ -90,7 +100,7 @@ public class ContentLoader implements ContentCreator {
     /**
      * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createNode(java.lang.String, java.lang.String, java.lang.String[])
      */
-    public void createNode(String name,
+    public Node createNode(String name,
                            String primaryNodeType,
                            String[] mixinNodeTypes)
     throws RepositoryException {
@@ -147,7 +157,9 @@ public class ContentLoader implements ContentCreator {
             if ( this.rootNode == null ) {
                 this.rootNode = node;
             }
+            return node;
         }
+        return null;
     }
 
     /**
@@ -364,4 +376,73 @@ public class ContentLoader implements ContentCreator {
         return (item.isNode()) ? (Node) item : null;
     }
 
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createFileAndResourceNode(java.lang.String, java.io.InputStream, java.lang.String, long)
+     */
+    public void createFileAndResourceNode(String name,
+                                          InputStream data,
+                                          String mimeType,
+                                          long lastModified)
+    throws RepositoryException {
+        int lastSlash = name.lastIndexOf('/');
+        name = (lastSlash < 0) ? name : name.substring(lastSlash + 1);
+        final Node parentNode = this.parentNodeStack.peek();
+
+        // if node already exists but should be overwritten, delete it
+        if (this.configuration.isOverwrite() && parentNode.hasNode(name)) {
+            parentNode.getNode(name).remove();
+        } else if (parentNode.hasNode(name)) {
+            return;
+        }
+
+        // ensure content type
+        if (mimeType == null) {
+            mimeType = jcrContentHelper.getMimeType(name);
+            if (mimeType == null) {
+                jcrContentHelper.log.info(
+                    "createFile: Cannot find content type for {}, using {}",
+                    name, DEFAULT_CONTENT_TYPE);
+                mimeType = DEFAULT_CONTENT_TYPE;
+            }
+        }
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+
+        this.createNode(name, "nt:file", null);
+        this.createNode("jcr:content", "nt:resource", null);
+        this.createProperty("jcr:mimeType", mimeType);
+        this.createProperty("jcr:lastModified", lastModified);
+        this.createProperty("jcr:data", data);
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#switchCurrentNode(java.lang.String, java.lang.String)
+     */
+    public boolean switchCurrentNode(String subPath, String newNodeType)
+    throws RepositoryException {
+        if ( this.parentNodeStack.size() > 1 ) {
+            throw new RepositoryException("Switching the current node is not allowed.");
+        }
+        if ( subPath.startsWith("/") ) {
+            subPath = subPath.substring(1);
+        }
+        final StringTokenizer st = new StringTokenizer(subPath, "/");
+        Node node = this.parentNodeStack.peek();
+        while ( st.hasMoreTokens() ) {
+            final String token = st.nextToken();
+            if ( !node.hasNode(token) ) {
+                if ( newNodeType == null ) {
+                    return false;
+                }
+                node = node.addNode(token, newNodeType);
+            }
+        }
+        this.parentNodeStack.push(node);
+        return true;
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
index 347ce30..341fda5 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
@@ -60,7 +60,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
     public static final String BUNDLE_CONTENT_NODE = "/var/sling/bundle-content";
 
     /** default log */
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    final Logger log = LoggerFactory.getLogger(getClass());
 
     /**
      * The JCR Repository we access to resolve resources
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 9e5efe8..fd55242 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -59,9 +59,6 @@ public class Loader {
 
     public static final String ROOT_DESCRIPTOR = "/ROOT";
 
-    // default content type for createFile()
-    private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
-
     /** default log */
     private final Logger log = LoggerFactory.getLogger(Loader.class);
 
@@ -69,13 +66,14 @@ public class Loader {
 
     private Map<String, ImportProvider> importProviders;
 
-    private ContentLoader contentCreator = new ContentLoader();
+    private final ContentLoader contentCreator;
 
     // bundles whose registration failed and should be retried
     private List<Bundle> delayedBundles;
 
     public Loader(ContentLoaderService jcrContentHelper) {
         this.jcrContentHelper = jcrContentHelper;
+        this.contentCreator = new ContentLoader(jcrContentHelper);
         this.delayedBundles = new LinkedList<Bundle>();
 
         importProviders = new LinkedHashMap<String, ImportProvider>();
@@ -365,7 +363,7 @@ public class Loader {
 
                 // otherwise just place as file
                 try {
-                    createFile(parent, file);
+                    createFile(configuration, parent, file);
                 } catch (IOException ioe) {
                     log.warn("Cannot create file node for {}", file, ioe);
                 }
@@ -475,39 +473,26 @@ public class Loader {
      * @throws IOException
      * @throws RepositoryException
      */
-    private void createFile(Node parent, URL source) throws IOException,
-            RepositoryException {
-        String name = getName(source.getPath());
-        if (parent.hasNode(name)) {
-            return;
-        }
-
-        URLConnection conn = source.openConnection();
-        long lastModified = conn.getLastModified();
-        String type = conn.getContentType();
-        InputStream data = conn.getInputStream();
-
-        // ensure content type
-        if (type == null) {
-            type = jcrContentHelper.getMimeType(name);
-            if (type == null) {
-                log.info(
-                    "createFile: Cannot find content type for {}, using {}",
-                    source.getPath(), DEFAULT_CONTENT_TYPE);
-                type = DEFAULT_CONTENT_TYPE;
-            }
-        }
-
-        // ensure sensible last modification date
-        if (lastModified <= 0) {
-            lastModified = System.currentTimeMillis();
+    private void createFile(PathEntry configuration, Node parent, URL source)
+    throws IOException, RepositoryException {
+        final String srcPath = source.getPath();
+        int pos = srcPath.lastIndexOf("/");
+        final String name = getName(source.getPath());
+        final String path;
+        if ( pos == -1 ) {
+            path = name;
+        } else {
+            path = srcPath.substring(0, pos + 1) + name;
         }
 
-        Node file = parent.addNode(name, "nt:file");
-        Node content = file.addNode("jcr:content", "nt:resource");
-        content.setProperty("jcr:mimeType", type);
-        content.setProperty("jcr:lastModified", lastModified);
-        content.setProperty("jcr:data", data);
+        this.contentCreator.init(configuration, parent, name);
+        final URLConnection conn = source.openConnection();
+        final long lastModified = conn.getLastModified();
+        final String type = conn.getContentType();
+        final InputStream data = conn.getInputStream();
+        this.contentCreator.createFileAndResourceNode(path, data, type, lastModified);
+        this.contentCreator.finishNode();
+        this.contentCreator.finishNode();
     }
 
     /**

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

[sling-org-apache-sling-jcr-contentloader] 27/36: SLING-838 : Clean up code, use method for session logout.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 94f72a0bdfbf73d30727f3692ce8837548734466
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jan 21 08:59:53 2009 +0000

    SLING-838 : Clean up code, use method for session logout.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@736268 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/ContentLoaderService.java             | 33 +++++++++++++---------
 1 file changed, 20 insertions(+), 13 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
index c454fe5..1f94f63 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
@@ -128,7 +128,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
                 // as node types are registered when the bundle is installed
                 // we can safely add the content at this point.
                 try {
-                    session = getAdminSession();
+                    session = this.getSession();
                     final boolean isUpdate = this.updatedBundles.remove(event.getBundle().getSymbolicName());
                     initialContentLoader.registerBundle(session, event.getBundle(), isUpdate);
                 } catch (Throwable t) {
@@ -137,9 +137,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
                             + event.getBundle().getSymbolicName() + " ("
                             + event.getBundle().getBundleId() + ")", t);
                 } finally {
-                    if ( session != null ) {
-                        session.logout();
-                    }
+                    this.ungetSession(session);
                 }
                 break;
             case BundleEvent.UPDATED:
@@ -149,7 +147,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
                 break;
             case BundleEvent.UNINSTALLED:
                 try {
-                    session = getAdminSession();
+                    session = this.getSession();
                     initialContentLoader.unregisterBundle(session, event.getBundle());
                 } catch (Throwable t) {
                     log.error(
@@ -157,9 +155,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
                             + event.getBundle().getSymbolicName() + " ("
                             + event.getBundle().getBundleId() + ")", t);
                 } finally {
-                    if ( session != null ) {
-                        session.logout();
-                    }
+                    this.ungetSession(session);
                 }
                 break;
         }
@@ -211,7 +207,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
 
         Session session = null;
         try {
-            session = this.getAdminSession();
+            session = this.getSession();
             this.createRepositoryPath(session, ContentLoaderService.BUNDLE_CONTENT_NODE);
             log.debug(
                     "Activated - attempting to load content from all "
@@ -250,9 +246,7 @@ public class ContentLoaderService implements SynchronousBundleListener {
             log.error("activate: Problem while loading initial content and"
                 + " registering mappings for existing bundles", t);
         } finally {
-            if ( session != null ) {
-                session.logout();
-            }
+            this.ungetSession(session);
         }
     }
 
@@ -276,12 +270,25 @@ public class ContentLoaderService implements SynchronousBundleListener {
     /**
      * Returns an administrative session to the default workspace.
      */
-    private Session getAdminSession()
+    private Session getSession()
     throws RepositoryException {
         return getRepository().loginAdministrative(null);
     }
 
     /**
+     * Return the administrative session and close it.
+     */
+    private void ungetSession(final Session session) {
+        if ( session != null ) {
+            try {
+                session.logout();
+            } catch (Throwable t) {
+                log.error("Unable to log out of session: " + t.getMessage(), t);
+            }
+        }
+    }
+
+    /**
      * Return the bundle content info and make an exclusive lock.
      * @param session
      * @param bundle

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

[sling-org-apache-sling-jcr-contentloader] 20/36: Refresh session before save.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit f504f459521a984300fee24ee1f8ec797f4a331c
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Jul 24 06:33:59 2008 +0000

    Refresh session before save.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@679285 13f79535-47bb-0310-9956-ffa450edef68
---
 src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 3d7420c..83622af 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -251,6 +251,7 @@ public class Loader {
             }
 
             // persist modifications now
+            session.refresh(true);
             session.save();
 
             // finally checkin versionable nodes

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

[sling-org-apache-sling-jcr-contentloader] 26/36: SLING-836 : Always create a new session for content loading / unloading. Try all bundles on startup even if one bundle fails.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit ee4f054b75f399a40660dda16694b7cbe8da55db
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jan 21 08:47:15 2009 +0000

    SLING-836 : Always create a new session for content loading / unloading. Try all bundles on startup even if one bundle fails.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@736264 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/ContentLoaderService.java             | 50 ++++++++++++++--------
 1 file changed, 31 insertions(+), 19 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
index 9781ce5..c454fe5 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
@@ -85,11 +85,6 @@ public class ContentLoaderService implements SynchronousBundleListener {
     private MimeTypeService mimeTypeService;
 
     /**
-     * Administrative sessions used to check item existence.
-     */
-    private Session adminSession;
-
-    /**
      * The initial content loader which is called to load initial content up
      * into the repository when the providing bundle is installed.
      */
@@ -126,13 +121,14 @@ public class ContentLoaderService implements SynchronousBundleListener {
         // This is synchronous - take care to not block the system !!
         //
 
+        Session session = null;
         switch (event.getType()) {
             case BundleEvent.STARTING:
                 // register content when the bundle content is available
                 // as node types are registered when the bundle is installed
                 // we can safely add the content at this point.
                 try {
-                    Session session = getAdminSession();
+                    session = getAdminSession();
                     final boolean isUpdate = this.updatedBundles.remove(event.getBundle().getSymbolicName());
                     initialContentLoader.registerBundle(session, event.getBundle(), isUpdate);
                 } catch (Throwable t) {
@@ -140,6 +136,10 @@ public class ContentLoaderService implements SynchronousBundleListener {
                         "bundleChanged: Problem loading initial content of bundle "
                             + event.getBundle().getSymbolicName() + " ("
                             + event.getBundle().getBundleId() + ")", t);
+                } finally {
+                    if ( session != null ) {
+                        session.logout();
+                    }
                 }
                 break;
             case BundleEvent.UPDATED:
@@ -149,13 +149,17 @@ public class ContentLoaderService implements SynchronousBundleListener {
                 break;
             case BundleEvent.UNINSTALLED:
                 try {
-                    Session session = getAdminSession();
+                    session = getAdminSession();
                     initialContentLoader.unregisterBundle(session, event.getBundle());
                 } catch (Throwable t) {
                     log.error(
                         "bundleChanged: Problem unloading initial content of bundle "
                             + event.getBundle().getSymbolicName() + " ("
                             + event.getBundle().getBundleId() + ")", t);
+                } finally {
+                    if ( session != null ) {
+                        session.logout();
+                    }
                 }
                 break;
         }
@@ -205,8 +209,9 @@ public class ContentLoaderService implements SynchronousBundleListener {
 
         componentContext.getBundleContext().addBundleListener(this);
 
+        Session session = null;
         try {
-            final Session session = getAdminSession();
+            session = this.getAdminSession();
             this.createRepositoryPath(session, ContentLoaderService.BUNDLE_CONTENT_NODE);
             log.debug(
                     "Activated - attempting to load content from all "
@@ -218,7 +223,18 @@ public class ContentLoaderService implements SynchronousBundleListener {
                 if ((bundle.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
                     // load content for bundles which are neither INSTALLED nor
                     // UNINSTALLED
-                    initialContentLoader.registerBundle(session, bundle, false);
+                    try {
+                        initialContentLoader.registerBundle(session, bundle, false);
+                    } catch (Throwable t) {
+                        log.error(
+                            "Problem loading initial content of bundle "
+                                + bundle.getSymbolicName() + " ("
+                                + bundle.getBundleId() + ")", t);
+                    } finally {
+                        if ( session.hasPendingChanges() ) {
+                            session.refresh(false);
+                        }
+                    }
                 } else {
                     ignored++;
                 }
@@ -233,6 +249,10 @@ public class ContentLoaderService implements SynchronousBundleListener {
         } catch (Throwable t) {
             log.error("activate: Problem while loading initial content and"
                 + " registering mappings for existing bundles", t);
+        } finally {
+            if ( session != null ) {
+                session.logout();
+            }
         }
     }
 
@@ -244,11 +264,6 @@ public class ContentLoaderService implements SynchronousBundleListener {
             this.initialContentLoader.dispose();
             this.initialContentLoader = null;
         }
-
-        if ( adminSession != null ) {
-            this.adminSession.logout();
-            this.adminSession = null;
-        }
     }
 
     // ---------- internal helper ----------------------------------------------
@@ -261,12 +276,9 @@ public class ContentLoaderService implements SynchronousBundleListener {
     /**
      * Returns an administrative session to the default workspace.
      */
-    private synchronized Session getAdminSession()
+    private Session getAdminSession()
     throws RepositoryException {
-        if ( adminSession == null ) {
-            adminSession = getRepository().loginAdministrative(null);
-        }
-        return adminSession;
+        return getRepository().loginAdministrative(null);
     }
 
     /**

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

[sling-org-apache-sling-jcr-contentloader] 25/36: SLING-829 Cosmetics to the bundle/project names

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit c02f7c763d3376ee52405b20bd0db869be1ea098
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Thu Jan 15 10:25:41 2009 +0000

    SLING-829 Cosmetics to the bundle/project names
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@734658 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 4f724f8..810642f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,7 +30,7 @@
     <version>2.0.3-incubator-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
-    <name>Sling - Initial Content Loader</name>
+    <name>Apache Sling Initial Content Loader</name>
     <description>
         This bundle provides initial content installation through bundles.
     </description>

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

[sling-org-apache-sling-jcr-contentloader] 12/36: SLING-548: Initial version to extract zips and jars into the repository.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 24de4c8b799022f30edf65780ac48a96fde5db49
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 15:16:33 2008 +0000

    SLING-548: Initial version to extract zips and jars into the repository.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670650 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  4 ++++
 .../jcr/contentloader/internal/ContentCreator.java |  7 ++-----
 .../jcr/contentloader/internal/ContentLoader.java  | 21 +++++++++++++-------
 .../sling/jcr/contentloader/internal/Loader.java   |  2 ++
 .../jcr/contentloader/internal/ZipReader.java      | 23 +++++++++++++++++++++-
 5 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7da5dc6..3d20da2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -123,6 +123,10 @@
             <artifactId>kxml2</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
     <!-- Testing -->        
         <dependency>
             <groupId>junit</groupId>
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index 3e9acaf..86b2476 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -20,7 +20,6 @@ package org.apache.sling.jcr.contentloader.internal;
 
 import java.io.InputStream;
 
-import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 
 /**
@@ -41,7 +40,7 @@ interface ContentCreator {
      * @param mixinNodeTypes The mixin node types or null.
      * @throws RepositoryException If anything goes wrong.
      */
-    Node createNode(String name,
+    void createNode(String name,
                     String primaryNodeType,
                     String[] mixinNodeTypes)
     throws RepositoryException;
@@ -118,13 +117,11 @@ interface ContentCreator {
 
     /**
      * Switch the current node to the path (which must be relative
-     * to the root node of the import).
+     * to the current node).
      * If the path does not exist and a node type is supplied,
      * the nodes are created with the given node type.
      * If the path does not exist and node type is null, false is
      * returned.
-     * Switching is only allowed if the current node is the root node
-     * of the import.
      * When the changes to the node are finished, {@link #finishNode()}
      * must be callsed.
      * @param subPath The relative path
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
index fe2e544..bb1827e 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -84,14 +84,23 @@ public class ContentLoader implements ContentCreator {
         isRootNodeImport = defaultRootName == null;
     }
 
+    /**
+     * Get the list of versionable nodes.
+     */
     public List<Node> getVersionables() {
         return this.versionables;
     }
 
+    /**
+     * Clear the content loader.
+     */
     public void clear() {
         this.versionables.clear();
     }
 
+    /**
+     * Get the created root node.
+     */
     public Node getRootNode() {
         return this.rootNode;
     }
@@ -100,7 +109,7 @@ public class ContentLoader implements ContentCreator {
     /**
      * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createNode(java.lang.String, java.lang.String, java.lang.String[])
      */
-    public Node createNode(String name,
+    public void createNode(String name,
                            String primaryNodeType,
                            String[] mixinNodeTypes)
     throws RepositoryException {
@@ -157,9 +166,7 @@ public class ContentLoader implements ContentCreator {
             if ( this.rootNode == null ) {
                 this.rootNode = node;
             }
-            return node;
         }
-        return null;
     }
 
     /**
@@ -393,6 +400,8 @@ public class ContentLoader implements ContentCreator {
         if (this.configuration.isOverwrite() && parentNode.hasNode(name)) {
             parentNode.getNode(name).remove();
         } else if (parentNode.hasNode(name)) {
+            this.parentNodeStack.push(parentNode.getNode(name));
+            this.parentNodeStack.push(parentNode.getNode(name).getNode("jcr:content"));
             return;
         }
 
@@ -424,9 +433,6 @@ public class ContentLoader implements ContentCreator {
      */
     public boolean switchCurrentNode(String subPath, String newNodeType)
     throws RepositoryException {
-        if ( this.parentNodeStack.size() > 1 ) {
-            throw new RepositoryException("Switching the current node is not allowed.");
-        }
         if ( subPath.startsWith("/") ) {
             subPath = subPath.substring(1);
         }
@@ -438,8 +444,9 @@ public class ContentLoader implements ContentCreator {
                 if ( newNodeType == null ) {
                     return false;
                 }
-                node = node.addNode(token, newNodeType);
+                node.addNode(token, newNodeType);
             }
+            node = node.getNode(token);
         }
         this.parentNodeStack.push(node);
         return true;
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index fd55242..ac58191 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -80,6 +80,8 @@ public class Loader {
         importProviders.put(EXT_JCR_XML, null);
         importProviders.put(EXT_JSON, JsonReader.PROVIDER);
         importProviders.put(EXT_XML, XmlReader.PROVIDER);
+        importProviders.put(".jar", ZipReader.JAR_PROVIDER);
+        importProviders.put(".zip", ZipReader.ZIP_PROVIDER);
     }
 
     public void dispose() {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
index 176c9bb..70e661c 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
@@ -25,6 +25,8 @@ import java.util.zip.ZipInputStream;
 
 import javax.jcr.RepositoryException;
 
+import org.apache.commons.io.input.CloseShieldInputStream;
+
 
 /**
  * The <code>JsonReader</code> TODO
@@ -60,17 +62,36 @@ class ZipReader implements ContentReader {
         this.jarReader = jarReader;
     }
 
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.io.InputStream, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     */
     public void parse(InputStream ins, ContentCreator creator)
     throws IOException, RepositoryException {
+        creator.createNode(null, "nt:folder", null);
         final ZipInputStream zis = new ZipInputStream(ins);
+        final InputStream dataIS = new CloseShieldInputStream(zis);
         ZipEntry entry;
         do {
             entry = zis.getNextEntry();
             if ( entry != null ) {
-                entry.getName();
+                if ( !entry.isDirectory() ) {
+                    String name = entry.getName();
+                    int pos = name.lastIndexOf('/');
+                    if ( pos != -1 ) {
+                        creator.switchCurrentNode(name.substring(0, pos), "nt:folder");
+                    }
+                    creator.createFileAndResourceNode(name, dataIS, null, entry.getTime());
+                    creator.finishNode();
+                    creator.finishNode();
+                    if ( pos != -1 ) {
+                        creator.finishNode();
+                    }
+                }
+                zis.closeEntry();
             }
 
         } while ( entry != null );
+        creator.finishNode();
     }
 
 }

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

[sling-org-apache-sling-jcr-contentloader] 01/36: Update to released versions.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 048781730d94aa5aaaaf9ddc866510c9a1b960f1
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jun 17 06:57:15 2008 +0000

    Update to released versions.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@668413 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9bdddb2..4428b59 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>2-incubator-SNAPSHOT</version>
+        <version>3-incubator</version>
         <relativePath>../../parent/pom.xml</relativePath>
     </parent>
 
@@ -92,27 +92,27 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.api</artifactId>
-            <version>2.0.1-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.1-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.mime</artifactId>
-            <version>2.0.1-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.0.1-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.engine</artifactId>
-            <version>2.0.1-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>

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

[sling-org-apache-sling-jcr-contentloader] 02/36: [maven-release-plugin] prepare release org.apache.sling.jcr.contentloader-2.0.2-incubator

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit a4f12d312657b754535071a043cccd32a7b0a820
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jun 17 07:27:38 2008 +0000

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

diff --git a/pom.xml b/pom.xml
index 4428b59..55725f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
     </parent>
 
     <artifactId>org.apache.sling.jcr.contentloader</artifactId>
-    <version>2.0.1-incubator-SNAPSHOT</version>
+    <version>2.0.2-incubator</version>
     <packaging>bundle</packaging>
 
     <name>Sling - Initial Content Loader</name>
@@ -36,9 +36,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader</developerConnection>
-        <url>http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/contentloader</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.2-incubator</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.2-incubator</developerConnection>
+        <url>http://svn.apache.org/viewvc/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.2-incubator</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-contentloader] 33/36: SLING-941: Lots of svn:eol-style settings missing

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit e7184dba825c74597b9c21de2c5daaca07be6f91
Author: Jukka Zitting <ju...@apache.org>
AuthorDate: Fri Apr 24 12:14:03 2009 +0000

    SLING-941: Lots of svn:eol-style settings missing
    
    Set svn:eol-style to native on all Java files that lack the setting.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader@768268 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-contentloader] 35/36: Move Sling to new TLP location

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit d153db54b1baaf91f05fd85cac579f4376e113cf
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Thu Jun 18 09:21:02 2009 +0000

    Move Sling to new TLP location
    
    git-svn-id: https://svn.eu.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator@785979 13f79535-47bb-0310-9956-ffa450edef68
---
 LICENSE                                            | 202 ++++++
 NOTICE                                             |   8 +
 README.txt                                         |  38 +
 pom.xml                                            | 148 ++++
 .../jcr/contentloader/internal/ContentCreator.java | 135 ++++
 .../internal/ContentLoaderService.java             | 381 ++++++++++
 .../jcr/contentloader/internal/ContentReader.java  |  40 ++
 .../internal/DefaultContentCreator.java            | 606 ++++++++++++++++
 .../jcr/contentloader/internal/ImportProvider.java |  27 +
 .../sling/jcr/contentloader/internal/Loader.java   | 783 +++++++++++++++++++++
 .../jcr/contentloader/internal/PathEntry.java      | 178 +++++
 .../contentloader/internal/readers/JsonReader.java | 216 ++++++
 .../contentloader/internal/readers/XmlReader.java  | 455 ++++++++++++
 .../contentloader/internal/readers/ZipReader.java  | 114 +++
 src/main/resources/META-INF/DISCLAIMER             |   7 +
 src/main/resources/META-INF/LICENSE                | 232 ++++++
 src/main/resources/META-INF/NOTICE                 |  11 +
 .../jcr/contentloader/internal/JsonReaderTest.java | 253 +++++++
 .../internal/readers/XmlReaderTest.java            |  76 ++
 src/test/resources/reader/sample.xml               |  32 +
 src/test/resources/reader/sample.xsl               |  58 ++
 21 files changed, 4000 insertions(+)

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..4543b0a
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,8 @@
+Apache Sling Initial Content Loader
+Copyright 2008-2009 The Apache Software Foundation
+
+Apache Sling is based on source code originally developed 
+by Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..9c00331
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,38 @@
+Apache Sling Initial Content Loader
+
+This bundle provides initial content installation through bundles.
+
+
+Disclaimer
+==========
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+    mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+    svn checkout http://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader
+
+See the Subversion documentation for other source control features.
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..88a7753
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>5-incubator</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.jcr.contentloader</artifactId>
+    <version>2.0.4-incubator</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Initial Content Loader</name>
+    <description>
+        This bundle provides initial content installation through bundles.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator</developerConnection>
+        <url>http://svn.apache.org/viewvc/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Private-Package>
+                            org.apache.sling.jcr.contentloader.internal.*,
+                            org.kxml2.io, org.xmlpull.v1
+                        </Private-Package>
+
+                        <Embed-Dependency>
+                            kxml2
+                        </Embed-Dependency>
+
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <excludePackageNames>
+                        org.apache.sling.jcr.contentloader.internal
+                    </excludePackageNames>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.api</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.mime</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.engine</artifactId>
+            <version>2.0.2-incubator</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.kxml</groupId>
+            <artifactId>kxml2</artifactId>
+            <version>2.2.2</version>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>xmlpull</groupId>
+                    <artifactId>xmlpull</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.4</version>
+        </dependency>
+    <!-- Testing -->        
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jmock</groupId>
+            <artifactId>jmock-junit4</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
new file mode 100644
index 0000000..3e93ac1
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -0,0 +1,135 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.InputStream;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * The <code>ContentCreator</code>
+ * is used by the {@link ContentReader} to create the actual content.
+ *
+ * @since 2.0.4
+ */
+public interface ContentCreator {
+
+    /**
+     * Create a new node.
+     * To add properties to this node, one of the createProperty() methods
+     * should be called.
+     * To add child nodes, this method should be called to create a new child node.
+     * If all properties and child nodes have been added {@link #finishNode()} must be called.
+     *
+     * @param name The name of the node.
+     * @param primaryNodeType The primary node type or null.
+     * @param mixinNodeTypes The mixin node types or null.
+     * @throws RepositoryException If anything goes wrong.
+     */
+    void createNode(String name,
+                    String primaryNodeType,
+                    String[] mixinNodeTypes)
+    throws RepositoryException;
+
+    /**
+     * Indicates that a node is finished.
+     * The parent node of the current node becomes the current node.
+     * @throws RepositoryException
+     */
+    void finishNode()
+    throws RepositoryException;
+
+    /**
+     * Create a new property to the current node.
+     * @param name The property name.
+     * @param propertyType The type of the property.
+     * @param value The string value.
+     * @throws RepositoryException
+     */
+    void createProperty(String name,
+                        int    propertyType,
+                        String value)
+    throws RepositoryException;
+
+    /**
+     * Create a new multi value property to the current node.
+     * @param name The property name.
+     * @param propertyType The type of the property.
+     * @param values The string values.
+     * @throws RepositoryException
+     */
+    void createProperty(String name,
+                        int    propertyType,
+                        String[] values)
+    throws RepositoryException;
+
+    /**
+     * Add a new property to the current node.
+     * @param name The property name.
+     * @param value The value.
+     * @throws RepositoryException
+     */
+    void createProperty(String name,
+                        Object value)
+    throws RepositoryException;
+
+    /**
+     * Add a new multi value property to the current node.
+     * @param name The property name.
+     * @param propertyType The type of the property.
+     * @param values The values.
+     * @throws RepositoryException
+     */
+    void createProperty(String name,
+                        Object[] values)
+    throws RepositoryException;
+
+    /**
+     * Create a file and a resource node.
+     * After the nodes have been created, the current node is the resource node.
+     * So this method call should be followed by two calls to {@link #finishNode()}
+     * to be on the same level as before the file creation.
+     * @param name The name of the file node
+     * @param data The data of the file
+     * @param mimeType The mime type or null
+     * @param lastModified The last modified or -1
+     * @throws RepositoryException
+     */
+    void createFileAndResourceNode(String name,
+                                   InputStream data,
+                                   String      mimeType,
+                                   long         lastModified)
+    throws RepositoryException;
+
+    /**
+     * Switch the current node to the path (which must be relative
+     * to the current node).
+     * If the path does not exist and a node type is supplied,
+     * the nodes are created with the given node type.
+     * If the path does not exist and node type is null, false is
+     * returned.
+     * When the changes to the node are finished, {@link #finishNode()}
+     * must be callsed.
+     * @param subPath The relative path
+     * @param newNodeType Node typ for newly created nodes.
+     * @throws RepositoryException
+     */
+    boolean switchCurrentNode(String subPath, String newNodeType)
+    throws RepositoryException;
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
new file mode 100644
index 0000000..1f94f63
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoaderService.java
@@ -0,0 +1,381 @@
+/*
+ * 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.contentloader.internal;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.lock.LockException;
+
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.apache.sling.engine.SlingSettingsService;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>ContentLoaderService</code> is the service
+ * providing the following functionality:
+ * <ul>
+ * <li>Bundle listener to load initial content.
+ * <li>Fires OSGi EventAdmin events on behalf of internal helper objects
+ * </ul>
+ *
+ * @scr.component metatype="no"
+ * @scr.property name="service.description" value="Sling
+ *               Content Loader Implementation"
+ * @scr.property name="service.vendor" value="The Apache Software Foundation"
+ */
+public class ContentLoaderService implements SynchronousBundleListener {
+
+    public static final String PROPERTY_CONTENT_LOADED = "content-loaded";
+    private static final String PROPERTY_CONTENT_LOADED_AT = "content-load-time";
+    private static final String PROPERTY_CONTENT_LOADED_BY = "content-loaded-by";
+    private static final String PROPERTY_CONTENT_UNLOADED_AT = "content-unload-time";
+    private static final String PROPERTY_CONTENT_UNLOADED_BY = "content-unloaded-by";
+    public static final String PROPERTY_UNINSTALL_PATHS = "uninstall-paths";
+
+    public static final String BUNDLE_CONTENT_NODE = "/var/sling/bundle-content";
+
+    /** default log */
+    final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * The JCR Repository we access to resolve resources
+     *
+     * @scr.reference
+     */
+    private SlingRepository repository;
+
+    /**
+     * The MimeTypeService used by the initial content initialContentLoader to
+     * resolve MIME types for files to be installed.
+     *
+     * @scr.reference
+     */
+    private MimeTypeService mimeTypeService;
+
+    /**
+     * The initial content loader which is called to load initial content up
+     * into the repository when the providing bundle is installed.
+     */
+    private Loader initialContentLoader;
+
+    /**
+     * The id of the current instance
+     */
+    private String slingId;
+
+    /**
+     * List of currently updated bundles.
+     */
+    private final Set<String> updatedBundles = new HashSet<String>();
+
+    /** @scr.reference
+     *  Sling settings service. */
+    protected SlingSettingsService settingsService;
+
+    // ---------- BundleListener -----------------------------------------------
+
+    /**
+     * Loads and unloads any content provided by the bundle whose state
+     * changed. If the bundle has been started, the content is loaded. If
+     * the bundle is about to stop, the content are unloaded.
+     *
+     * @param event The <code>BundleEvent</code> representing the bundle state
+     *            change.
+     */
+    public void bundleChanged(BundleEvent event) {
+
+        //
+        // NOTE:
+        // This is synchronous - take care to not block the system !!
+        //
+
+        Session session = null;
+        switch (event.getType()) {
+            case BundleEvent.STARTING:
+                // register content when the bundle content is available
+                // as node types are registered when the bundle is installed
+                // we can safely add the content at this point.
+                try {
+                    session = this.getSession();
+                    final boolean isUpdate = this.updatedBundles.remove(event.getBundle().getSymbolicName());
+                    initialContentLoader.registerBundle(session, event.getBundle(), isUpdate);
+                } catch (Throwable t) {
+                    log.error(
+                        "bundleChanged: Problem loading initial content of bundle "
+                            + event.getBundle().getSymbolicName() + " ("
+                            + event.getBundle().getBundleId() + ")", t);
+                } finally {
+                    this.ungetSession(session);
+                }
+                break;
+            case BundleEvent.UPDATED:
+                // we just add the symbolic name to the list of updated bundles
+                // we will use this info when the new start event is triggered
+                this.updatedBundles.add(event.getBundle().getSymbolicName());
+                break;
+            case BundleEvent.UNINSTALLED:
+                try {
+                    session = this.getSession();
+                    initialContentLoader.unregisterBundle(session, event.getBundle());
+                } catch (Throwable t) {
+                    log.error(
+                        "bundleChanged: Problem unloading initial content of bundle "
+                            + event.getBundle().getSymbolicName() + " ("
+                            + event.getBundle().getBundleId() + ")", t);
+                } finally {
+                    this.ungetSession(session);
+                }
+                break;
+        }
+    }
+
+    // ---------- Implementation helpers --------------------------------------
+
+    /** Returns the MIME type from the MimeTypeService for the given name */
+    public String getMimeType(String name) {
+        // local copy to not get NPE despite check for null due to concurrent
+        // unbind
+        MimeTypeService mts = mimeTypeService;
+        return (mts != null) ? mts.getMimeType(name) : null;
+    }
+
+    protected void createRepositoryPath(final Session writerSession, final String repositoryPath)
+    throws RepositoryException {
+        if ( !writerSession.itemExists(repositoryPath) ) {
+            Node node = writerSession.getRootNode();
+            String path = repositoryPath.substring(1);
+            int pos = path.lastIndexOf('/');
+            if ( pos != -1 ) {
+                final StringTokenizer st = new StringTokenizer(path.substring(0, pos), "/");
+                while ( st.hasMoreTokens() ) {
+                    final String token = st.nextToken();
+                    if ( !node.hasNode(token) ) {
+                        node.addNode(token, "sling:Folder");
+                        node.save();
+                    }
+                    node = node.getNode(token);
+                }
+                path = path.substring(pos + 1);
+            }
+            if ( !node.hasNode(path) ) {
+                node.addNode(path, "sling:Folder");
+                node.save();
+            }
+        }
+    }
+
+    // ---------- SCR Integration ---------------------------------------------
+
+    /** Activates this component, called by SCR before registering as a service */
+    protected void activate(ComponentContext componentContext) {
+        this.slingId = this.settingsService.getSlingId();
+        this.initialContentLoader = new Loader(this);
+
+        componentContext.getBundleContext().addBundleListener(this);
+
+        Session session = null;
+        try {
+            session = this.getSession();
+            this.createRepositoryPath(session, ContentLoaderService.BUNDLE_CONTENT_NODE);
+            log.debug(
+                    "Activated - attempting to load content from all "
+                    + "bundles which are neither INSTALLED nor UNINSTALLED");
+
+            int ignored = 0;
+            Bundle[] bundles = componentContext.getBundleContext().getBundles();
+            for (Bundle bundle : bundles) {
+                if ((bundle.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
+                    // load content for bundles which are neither INSTALLED nor
+                    // UNINSTALLED
+                    try {
+                        initialContentLoader.registerBundle(session, bundle, false);
+                    } catch (Throwable t) {
+                        log.error(
+                            "Problem loading initial content of bundle "
+                                + bundle.getSymbolicName() + " ("
+                                + bundle.getBundleId() + ")", t);
+                    } finally {
+                        if ( session.hasPendingChanges() ) {
+                            session.refresh(false);
+                        }
+                    }
+                } else {
+                    ignored++;
+                }
+
+            }
+
+            log.debug(
+                    "Out of {} bundles, {} were not in a suitable state for initial content loading",
+                    bundles.length, ignored
+                    );
+
+        } catch (Throwable t) {
+            log.error("activate: Problem while loading initial content and"
+                + " registering mappings for existing bundles", t);
+        } finally {
+            this.ungetSession(session);
+        }
+    }
+
+    /** Deativates this component, called by SCR to take out of service */
+    protected void deactivate(ComponentContext componentContext) {
+        componentContext.getBundleContext().removeBundleListener(this);
+
+        if ( this.initialContentLoader != null ) {
+            this.initialContentLoader.dispose();
+            this.initialContentLoader = null;
+        }
+    }
+
+    // ---------- internal helper ----------------------------------------------
+
+    /** Returns the JCR repository used by this service. */
+    protected SlingRepository getRepository() {
+        return repository;
+    }
+
+    /**
+     * Returns an administrative session to the default workspace.
+     */
+    private Session getSession()
+    throws RepositoryException {
+        return getRepository().loginAdministrative(null);
+    }
+
+    /**
+     * Return the administrative session and close it.
+     */
+    private void ungetSession(final Session session) {
+        if ( session != null ) {
+            try {
+                session.logout();
+            } catch (Throwable t) {
+                log.error("Unable to log out of session: " + t.getMessage(), t);
+            }
+        }
+    }
+
+    /**
+     * Return the bundle content info and make an exclusive lock.
+     * @param session
+     * @param bundle
+     * @return The map of bundle content info or null.
+     * @throws RepositoryException
+     */
+    public Map<String, Object> getBundleContentInfo(final Session session, final Bundle bundle, boolean create)
+    throws RepositoryException {
+        final String nodeName = bundle.getSymbolicName();
+        final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
+        if ( !parentNode.hasNode(nodeName) ) {
+            if ( !create ) {
+                return null;
+            }
+            try {
+                final Node bcNode = parentNode.addNode(nodeName, "nt:unstructured");
+                bcNode.addMixin("mix:lockable");
+                parentNode.save();
+            } catch (RepositoryException re) {
+                // for concurrency issues (running in a cluster) we ignore exceptions
+                this.log.warn("Unable to create node " + nodeName, re);
+                session.refresh(true);
+            }
+        }
+        final Node bcNode = parentNode.getNode(nodeName);
+        if ( bcNode.isLocked() ) {
+            return null;
+        }
+        try {
+            bcNode.lock(false, true);
+        } catch (LockException le) {
+            return null;
+        }
+        final Map<String, Object> info = new HashMap<String, Object>();
+        if ( bcNode.hasProperty(ContentLoaderService.PROPERTY_CONTENT_LOADED) ) {
+            info.put(ContentLoaderService.PROPERTY_CONTENT_LOADED,
+                    bcNode.getProperty(ContentLoaderService.PROPERTY_CONTENT_LOADED).getBoolean());
+        } else {
+            info.put(ContentLoaderService.PROPERTY_CONTENT_LOADED, false);
+        }
+        if ( bcNode.hasProperty(ContentLoaderService.PROPERTY_UNINSTALL_PATHS) ) {
+            final Value[] values = bcNode.getProperty(PROPERTY_UNINSTALL_PATHS).getValues();
+            final String[] s = new String[values.length];
+            for(int i=0; i<values.length; i++) {
+                s[i] = values[i].getString();
+            }
+            info.put(ContentLoaderService.PROPERTY_UNINSTALL_PATHS, s);
+        }
+        return info;
+    }
+
+    public void unlockBundleContentInfo(final Session session,
+                                        final Bundle  bundle,
+                                        final boolean contentLoaded,
+                                        final List<String> createdNodes)
+    throws RepositoryException {
+        final String nodeName = bundle.getSymbolicName();
+        final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
+        final Node bcNode = parentNode.getNode(nodeName);
+        if ( contentLoaded ) {
+            bcNode.setProperty(ContentLoaderService.PROPERTY_CONTENT_LOADED, contentLoaded);
+            bcNode.setProperty(PROPERTY_CONTENT_LOADED_AT, Calendar.getInstance());
+            bcNode.setProperty(PROPERTY_CONTENT_LOADED_BY, this.slingId);
+            bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, (String)null);
+            bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, (String)null);
+            if ( createdNodes != null && createdNodes.size() > 0 ) {
+                bcNode.setProperty(PROPERTY_UNINSTALL_PATHS, createdNodes.toArray(new String[createdNodes.size()]));
+            }
+            bcNode.save();
+        }
+        bcNode.unlock();
+    }
+
+    public void contentIsUninstalled(final Session session,
+                                     final Bundle  bundle) {
+        final String nodeName = bundle.getSymbolicName();
+        try {
+            final Node parentNode = (Node)session.getItem(BUNDLE_CONTENT_NODE);
+            if ( parentNode.hasNode(nodeName) ) {
+                final Node bcNode = parentNode.getNode(nodeName);
+                bcNode.setProperty(ContentLoaderService.PROPERTY_CONTENT_LOADED, false);
+                bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_AT, Calendar.getInstance());
+                bcNode.setProperty(PROPERTY_CONTENT_UNLOADED_BY, this.slingId);
+                bcNode.save();
+            }
+        } catch (RepositoryException re) {
+            this.log.error("Unable to update bundle content info.", re);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
new file mode 100644
index 0000000..fef05d9
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
@@ -0,0 +1,40 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * The <code>ContentReader</code>
+ * A content reader is provided by an {@link ImportProvider}.
+ */
+public interface ContentReader {
+
+    /**
+     * Read the content from the URL and create the
+     * content throught the provided content creator.
+     * @param url The input stream.
+     * @throws IOException
+     */
+    void parse(URL url, ContentCreator creator) throws IOException, RepositoryException;
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
new file mode 100644
index 0000000..c22b509
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
@@ -0,0 +1,606 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.StringTokenizer;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+/**
+ * The <code>ContentLoader</code> creates the nodes and properties.
+ * @since 2.0.4
+ */
+public class DefaultContentCreator implements ContentCreator {
+
+    private PathEntry configuration;
+
+    private final Stack<Node> parentNodeStack = new Stack<Node>();
+
+    /** The list of versionables. */
+    private final List<Node> versionables = new ArrayList<Node>();
+
+    /** Delayed references during content loading for the reference property. */
+    private final Map<String, List<String>> delayedReferences = new HashMap<String, List<String>>();
+    private final Map<String, String[]> delayedMultipleReferences = new HashMap<String, String[]>();
+
+    private String defaultRootName;
+
+    private Node rootNode;
+
+    private boolean isRootNodeImport;
+
+    private boolean ignoreOverwriteFlag = false;
+
+    // default content type for createFile()
+    private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
+
+    /** Helper class to get the mime type of a file. */
+    private final ContentLoaderService jcrContentHelper;
+
+    /** List of active import providers mapped by extension. */
+    private Map<String, ImportProvider> importProviders;
+
+    /** Optional list of created nodes (for uninstall) */
+    private List<String> createdNodes;
+
+    /**
+     * Constructor.
+     * @param jcrContentHelper Helper class to get the mime type of a file
+     */
+    public DefaultContentCreator(ContentLoaderService jcrContentHelper) {
+        this.jcrContentHelper = jcrContentHelper;
+    }
+
+    /**
+     * Initialize this component.
+     * @param pathEntry The configuration for this import.
+     * @param defaultImportProviders List of all import providers.
+     * @param createdNodes Optional list to store new nodes (for uninstall)
+     */
+    public void init(final PathEntry pathEntry,
+                     final Map<String, ImportProvider> defaultImportProviders,
+                     final List<String> createdNodes) {
+        this.configuration = pathEntry;
+        // create list of allowed import providers
+        this.importProviders = new HashMap<String, ImportProvider>();
+        final Iterator<Map.Entry<String, ImportProvider>> entryIter = defaultImportProviders.entrySet().iterator();
+        while ( entryIter.hasNext() ) {
+            final Map.Entry<String, ImportProvider> current = entryIter.next();
+            if (!configuration.isIgnoredImportProvider(current.getKey()) ) {
+                importProviders.put(current.getKey(), current.getValue());
+            }
+        }
+        this.createdNodes = createdNodes;
+    }
+
+    /**
+     *
+     * If the defaultRootName is null, we are in ROOT_NODE import mode.
+     * @param parentNode
+     * @param defaultRootName
+     */
+    public void prepareParsing(final Node parentNode,
+                               final String defaultRootName) {
+        this.parentNodeStack.clear();
+        this.parentNodeStack.push(parentNode);
+        this.defaultRootName = defaultRootName;
+        this.rootNode = null;
+        isRootNodeImport = defaultRootName == null;
+    }
+
+    /**
+     * Get the list of versionable nodes.
+     */
+    public List<Node> getVersionables() {
+        return this.versionables;
+    }
+
+    /**
+     * Clear the content loader.
+     */
+    public void clear() {
+        this.versionables.clear();
+    }
+
+    /**
+     * Set the ignore overwrite flag.
+     * @param flag
+     */
+    public void setIgnoreOverwriteFlag(boolean flag) {
+        this.ignoreOverwriteFlag = flag;
+    }
+
+    /**
+     * Get the created root node.
+     */
+    public Node getRootNode() {
+        return this.rootNode;
+    }
+
+    /**
+     * Get all active import providers.
+     * @return A map of providers
+     */
+    public Map<String, ImportProvider> getImportProviders() {
+        return this.importProviders;
+    }
+
+    /**
+     * Return the import provider for the name
+     * @param name The file name.
+     * @return The provider or <code>null</code>
+     */
+    public ImportProvider getImportProvider(String name) {
+        ImportProvider provider = null;
+        final Iterator<String> ipIter = importProviders.keySet().iterator();
+        while (provider == null && ipIter.hasNext()) {
+            final String ext = ipIter.next();
+            if (name.endsWith(ext)) {
+                provider = importProviders.get(ext);
+            }
+        }
+        return provider;
+    }
+
+    /**
+     * Get the extension of the file name.
+     * @param name The file name.
+     * @return The extension a provider is registered for - or <code>null</code>
+     */
+    public String getImportProviderExtension(String name) {
+        String providerExt = null;
+        final Iterator<String> ipIter = importProviders.keySet().iterator();
+        while (providerExt == null && ipIter.hasNext()) {
+            final String ext = ipIter.next();
+            if (name.endsWith(ext)) {
+                providerExt = ext;
+            }
+        }
+        return providerExt;
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createNode(java.lang.String, java.lang.String, java.lang.String[])
+     */
+    public void createNode(String name,
+                           String primaryNodeType,
+                           String[] mixinNodeTypes)
+    throws RepositoryException {
+        final Node parentNode = this.parentNodeStack.peek();
+        if ( name == null ) {
+            if ( this.parentNodeStack.size() > 1 ) {
+                throw new RepositoryException("Node needs to have a name.");
+            }
+            name = this.defaultRootName;
+        }
+
+        // if we are in root node import mode, we don't create the root top level node!
+        if ( !isRootNodeImport || this.parentNodeStack.size() > 1 ) {
+            // if node already exists but should be overwritten, delete it
+            if (!this.ignoreOverwriteFlag && this.configuration.isOverwrite() && parentNode.hasNode(name)) {
+                parentNode.getNode(name).remove();
+            }
+
+            // ensure repository node
+            Node node;
+            if (parentNode.hasNode(name)) {
+
+                // use existing node
+                node = parentNode.getNode(name);
+            } else if (primaryNodeType == null) {
+
+                // no explicit node type, use repository default
+                node = parentNode.addNode(name);
+                if ( this.createdNodes != null ) {
+                    this.createdNodes.add(node.getPath());
+                }
+
+            } else {
+
+                // explicit primary node type
+                node = parentNode.addNode(name, primaryNodeType);
+                if ( this.createdNodes != null ) {
+                    this.createdNodes.add(node.getPath());
+                }
+            }
+
+            // ammend mixin node types
+            if (mixinNodeTypes != null) {
+                for (final String mixin : mixinNodeTypes) {
+                    if (!node.isNodeType(mixin)) {
+                        node.addMixin(mixin);
+                    }
+                }
+            }
+
+            // check if node is versionable
+            final boolean addToVersionables = this.configuration.isCheckin()
+                                        && node.isNodeType("mix:versionable");
+            if ( addToVersionables ) {
+                this.versionables.add(node);
+            }
+
+            this.parentNodeStack.push(node);
+            if ( this.rootNode == null ) {
+                this.rootNode = node;
+            }
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, int, java.lang.String)
+     */
+    public void createProperty(String name, int propertyType, String value)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+
+        if ( propertyType == PropertyType.REFERENCE ) {
+            // need to resolve the reference
+            String propPath = node.getPath() + "/" + name;
+            String uuid = getUUID(node.getSession(), propPath, getAbsPath(node, value));
+            if (uuid != null) {
+                node.setProperty(name, uuid, propertyType);
+            }
+
+        } else if ("jcr:isCheckedOut".equals(name)) {
+
+            // don't try to write the property but record its state
+            // for later checkin if set to false
+            final boolean checkedout = Boolean.valueOf(value);
+            if (!checkedout) {
+                if ( !this.versionables.contains(node) ) {
+                    this.versionables.add(node);
+                }
+            }
+        } else {
+            node.setProperty(name, value, propertyType);
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, int, java.lang.String[])
+     */
+    public void createProperty(String name, int propertyType, String[] values)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+        if ( propertyType == PropertyType.REFERENCE ) {
+            String propPath = node.getPath() + "/" + name;
+
+            boolean hasAll = true;
+            String[] uuids = new String[values.length];
+            String[] uuidOrPaths = new String[values.length];
+            for (int i = 0; i < values.length; i++) {
+                uuids[i] = getUUID(node.getSession(), propPath, getAbsPath(node, values[i]));
+                uuidOrPaths[i] = uuids[i] != null ? uuids[i] : getAbsPath(node, values[i]);
+                if (uuids[i] == null) hasAll = false;
+            }
+
+            node.setProperty(name, uuids, propertyType);
+
+            if (!hasAll) {
+                delayedMultipleReferences.put(propPath, uuidOrPaths);
+            }
+        } else {
+            node.setProperty(name, values, propertyType);
+        }
+    }
+
+    protected Value createValue(final ValueFactory factory, Object value) {
+        if ( value == null ) {
+            return null;
+        }
+        if ( value instanceof Long ) {
+            return factory.createValue((Long)value);
+        } else if ( value instanceof Date ) {
+            final Calendar c = Calendar.getInstance();
+            c.setTime((Date)value);
+            return factory.createValue(c);
+        } else if ( value instanceof Calendar ) {
+            return factory.createValue((Calendar)value);
+        } else if ( value instanceof Double ) {
+            return factory.createValue((Double)value);
+        } else if ( value instanceof Boolean ) {
+            return factory.createValue((Boolean)value);
+        } else if ( value instanceof InputStream ) {
+            return factory.createValue((InputStream)value);
+        } else {
+            return factory.createValue(value.toString());
+        }
+
+    }
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, java.lang.Object)
+     */
+    public void createProperty(String name, Object value)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+        if ( value == null ) {
+            if ( node.hasProperty(name) ) {
+                node.getProperty(name).remove();
+            }
+        } else {
+            final Value jcrValue = this.createValue(node.getSession().getValueFactory(), value);
+            node.setProperty(name, jcrValue);
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, java.lang.Object[])
+     */
+    public void createProperty(String name, Object[] values)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+        if ( values == null || values.length == 0 ) {
+            if ( node.hasProperty(name) ) {
+                node.getProperty(name).remove();
+            }
+        } else {
+            final Value[] jcrValues = new Value[values.length];
+            for(int i = 0; i < values.length; i++) {
+                jcrValues[i] = this.createValue(node.getSession().getValueFactory(), values[i]);
+            }
+            node.setProperty(name, jcrValues);
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#finishNode()
+     */
+    public void finishNode()
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.pop();
+        // resolve REFERENCE property values pointing to this node
+        resolveReferences(node);
+    }
+
+    private String getAbsPath(Node node, String path) throws RepositoryException {
+        if (path.startsWith("/")) return path;
+
+        while (path.startsWith("../")) {
+            path = path.substring(3);
+            node = node.getParent();
+        }
+
+        while (path.startsWith("./")) {
+            path = path.substring(2);
+        }
+
+        return node.getPath() + "/" + path;
+    }
+
+    private String getUUID(Session session, String propPath,
+                          String referencePath)
+    throws RepositoryException {
+        if (session.itemExists(referencePath)) {
+            Item item = session.getItem(referencePath);
+            if (item.isNode()) {
+                Node refNode = (Node) item;
+                if (refNode.isNodeType("mix:referenceable")) {
+                    return refNode.getUUID();
+                }
+            }
+        } else {
+            // not existing yet, keep for delayed setting
+            List<String> current = delayedReferences.get(referencePath);
+            if (current == null) {
+                current = new ArrayList<String>();
+                delayedReferences.put(referencePath, current);
+            }
+            current.add(propPath);
+        }
+
+        // no UUID found
+        return null;
+    }
+
+    private void resolveReferences(Node node) throws RepositoryException {
+        List<String> props = delayedReferences.remove(node.getPath());
+        if (props == null || props.size() == 0) {
+            return;
+        }
+
+        // check whether we can set at all
+        if (!node.isNodeType("mix:referenceable")) {
+            return;
+        }
+
+        Session session = node.getSession();
+        String uuid = node.getUUID();
+
+        for (String property : props) {
+            String name = getName(property);
+            Node parentNode = getParentNode(session, property);
+            if (parentNode != null) {
+                if (parentNode.hasProperty(name) && parentNode.getProperty(name).getDefinition().isMultiple()) {
+                    boolean hasAll = true;
+                    String[] uuidOrPaths = delayedMultipleReferences.get(property);
+                    String[] uuids = new String[uuidOrPaths.length];
+                    for (int i = 0; i < uuidOrPaths.length; i++) {
+                        // is the reference still a path
+                        if (uuidOrPaths[i].startsWith("/")) {
+                            if (uuidOrPaths[i].equals(node.getPath())) {
+                                uuidOrPaths[i] = uuid;
+                                uuids[i] = uuid;
+                            } else {
+                                uuids[i] = null;
+                                hasAll = false;
+                            }
+                        } else {
+                            uuids[i] = uuidOrPaths[i];
+                        }
+                    }
+                    parentNode.setProperty(name, uuids, PropertyType.REFERENCE);
+
+                    if (hasAll) {
+                        delayedMultipleReferences.remove(property);
+                    }
+                } else {
+                    parentNode.setProperty(name, uuid, PropertyType.REFERENCE);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the name part of the <code>path</code>. The name is
+     * the part of the path after the last slash (or the complete path if no
+     * slash is contained).
+     *
+     * @param path The path from which to extract the name part.
+     * @return The name part.
+     */
+    private String getName(String path) {
+        int lastSlash = path.lastIndexOf('/');
+        String name = (lastSlash < 0) ? path : path.substring(lastSlash + 1);
+
+        return name;
+    }
+
+    private Node getParentNode(Session session, String path)
+            throws RepositoryException {
+        int lastSlash = path.lastIndexOf('/');
+
+        // not an absolute path, cannot find parent
+        if (lastSlash < 0) {
+            return null;
+        }
+
+        // node below root
+        if (lastSlash == 0) {
+            return session.getRootNode();
+        }
+
+        // item in the hierarchy
+        path = path.substring(0, lastSlash);
+        if (!session.itemExists(path)) {
+            return null;
+        }
+
+        Item item = session.getItem(path);
+        return (item.isNode()) ? (Node) item : null;
+    }
+
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createFileAndResourceNode(java.lang.String, java.io.InputStream, java.lang.String, long)
+     */
+    public void createFileAndResourceNode(String name,
+                                          InputStream data,
+                                          String mimeType,
+                                          long lastModified)
+    throws RepositoryException {
+        int lastSlash = name.lastIndexOf('/');
+        name = (lastSlash < 0) ? name : name.substring(lastSlash + 1);
+        final Node parentNode = this.parentNodeStack.peek();
+
+        // if node already exists but should be overwritten, delete it
+        if (this.configuration.isOverwrite() && parentNode.hasNode(name)) {
+            parentNode.getNode(name).remove();
+        } else if (parentNode.hasNode(name)) {
+            this.parentNodeStack.push(parentNode.getNode(name));
+            this.parentNodeStack.push(parentNode.getNode(name).getNode("jcr:content"));
+            return;
+        }
+
+        // ensure content type
+        if (mimeType == null) {
+            mimeType = jcrContentHelper.getMimeType(name);
+            if (mimeType == null) {
+                jcrContentHelper.log.info(
+                    "createFile: Cannot find content type for {}, using {}",
+                    name, DEFAULT_CONTENT_TYPE);
+                mimeType = DEFAULT_CONTENT_TYPE;
+            }
+        }
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+
+        this.createNode(name, "nt:file", null);
+        this.createNode("jcr:content", "nt:resource", null);
+        this.createProperty("jcr:mimeType", mimeType);
+        this.createProperty("jcr:lastModified", lastModified);
+        this.createProperty("jcr:data", data);
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#switchCurrentNode(java.lang.String, java.lang.String)
+     */
+    public boolean switchCurrentNode(String subPath, String newNodeType)
+    throws RepositoryException {
+        if ( subPath.startsWith("/") ) {
+            subPath = subPath.substring(1);
+        }
+        final StringTokenizer st = new StringTokenizer(subPath, "/");
+        Node node = this.parentNodeStack.peek();
+        while ( st.hasMoreTokens() ) {
+            final String token = st.nextToken();
+            if ( !node.hasNode(token) ) {
+                if ( newNodeType == null ) {
+                    return false;
+                }
+                final Node n = node.addNode(token, newNodeType);
+                if ( this.createdNodes != null ) {
+                    this.createdNodes.add(n.getPath());
+                }
+            }
+            node = node.getNode(token);
+        }
+        this.parentNodeStack.push(node);
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
new file mode 100644
index 0000000..cfd85f6
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.IOException;
+
+public interface ImportProvider {
+
+    ContentReader getReader() throws IOException;
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
new file mode 100644
index 0000000..ef134f6
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -0,0 +1,783 @@
+/*
+ * 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.contentloader.internal;
+
+import static javax.jcr.ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.jcr.InvalidSerializedDataException;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
+import org.apache.sling.jcr.contentloader.internal.readers.XmlReader;
+import org.apache.sling.jcr.contentloader.internal.readers.ZipReader;
+import org.osgi.framework.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>Loader</code> loads initial content from the bundle.
+ */
+public class Loader {
+
+    public static final String EXT_XML = ".xml";
+
+    public static final String EXT_JCR_XML = ".jcr.xml";
+
+    public static final String EXT_JSON = ".json";
+
+    public static final String EXT_JAR = ".jar";
+
+    public static final String EXT_ZIP = ".zip";
+
+    public static final String ROOT_DESCRIPTOR = "/ROOT";
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(Loader.class);
+
+    private ContentLoaderService jcrContentHelper;
+
+    /** All available import providers. */
+    private Map<String, ImportProvider> defaultImportProviders;
+
+    private final DefaultContentCreator contentCreator;
+
+    // bundles whose registration failed and should be retried
+    private List<Bundle> delayedBundles;
+
+    public Loader(ContentLoaderService jcrContentHelper) {
+        this.jcrContentHelper = jcrContentHelper;
+        this.contentCreator = new DefaultContentCreator(jcrContentHelper);
+        this.delayedBundles = new LinkedList<Bundle>();
+
+        defaultImportProviders = new LinkedHashMap<String, ImportProvider>();
+        defaultImportProviders.put(EXT_JCR_XML, null);
+        defaultImportProviders.put(EXT_JSON, JsonReader.PROVIDER);
+        defaultImportProviders.put(EXT_XML, XmlReader.PROVIDER);
+        defaultImportProviders.put(EXT_JAR, ZipReader.JAR_PROVIDER);
+        defaultImportProviders.put(EXT_ZIP, ZipReader.ZIP_PROVIDER);
+    }
+
+    public void dispose() {
+        if (delayedBundles != null) {
+            delayedBundles.clear();
+            delayedBundles = null;
+        }
+        jcrContentHelper = null;
+        defaultImportProviders = null;
+    }
+
+    /**
+     * Register a bundle and install its content.
+     *
+     * @param session
+     * @param bundle
+     */
+    public void registerBundle(final Session session,
+                               final Bundle bundle,
+                               final boolean isUpdate) {
+        // if this is an update, we have to uninstall the old content first
+        if ( isUpdate ) {
+            this.unregisterBundle(session, bundle);
+        }
+
+        log.debug("Registering bundle {} for content loading.",
+            bundle.getSymbolicName());
+
+        if (registerBundleInternal(session, bundle, false, isUpdate)) {
+
+            // handle delayed bundles, might help now
+            int currentSize = -1;
+            for (int i = delayedBundles.size(); i > 0
+                && currentSize != delayedBundles.size()
+                && !delayedBundles.isEmpty(); i--) {
+
+                for (Iterator<Bundle> di = delayedBundles.iterator(); di.hasNext();) {
+
+                    Bundle delayed = di.next();
+                    if (registerBundleInternal(session, delayed, true, false)) {
+                        di.remove();
+                    }
+
+                }
+
+                currentSize = delayedBundles.size();
+            }
+
+        } else if (!isUpdate) {
+            // add to delayed bundles - if this is not an update!
+            delayedBundles.add(bundle);
+        }
+    }
+
+    private boolean registerBundleInternal(final Session session,
+            final Bundle bundle, final boolean isRetry, final boolean isUpdate) {
+
+        // check if bundle has initial content
+        final Iterator<PathEntry> pathIter = PathEntry.getContentPaths(bundle);
+        if (pathIter == null) {
+            log.debug("Bundle {} has no initial content",
+                bundle.getSymbolicName());
+            return true;
+        }
+
+        try {
+
+            // check if the content has already been loaded
+            final Map<String, Object> bundleContentInfo = jcrContentHelper.getBundleContentInfo(
+                session, bundle, true);
+
+            // if we don't get an info, someone else is currently loading
+            if (bundleContentInfo == null) {
+                return false;
+            }
+
+            boolean success = false;
+            List<String> createdNodes = null;
+            try {
+
+                final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED)).booleanValue();
+
+                if (!isUpdate && contentAlreadyLoaded) {
+
+                    log.info("Content of bundle already loaded {}.",
+                        bundle.getSymbolicName());
+
+                } else {
+
+                    createdNodes = installContent(session, bundle, pathIter,
+                        contentAlreadyLoaded);
+
+                    if (isRetry) {
+                        // log success of retry
+                        log.info(
+                            "Retrytring to load initial content for bundle {} succeeded.",
+                            bundle.getSymbolicName());
+                    }
+
+                }
+
+                success = true;
+                return true;
+
+            } finally {
+                jcrContentHelper.unlockBundleContentInfo(session, bundle,
+                    success, createdNodes);
+            }
+
+        } catch (RepositoryException re) {
+            // if we are retrying we already logged this message once, so we
+            // won't log it again
+            if (!isRetry) {
+                log.error("Cannot load initial content for bundle "
+                    + bundle.getSymbolicName() + " : " + re.getMessage(), re);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Unregister a bundle. Remove installed content.
+     *
+     * @param bundle The bundle.
+     */
+    public void unregisterBundle(final Session session, final Bundle bundle) {
+
+        if (delayedBundles.contains(bundle)) {
+
+            delayedBundles.remove(bundle);
+
+        } else {
+            try {
+                final Map<String, Object> bundleContentInfo = jcrContentHelper.getBundleContentInfo(
+                        session, bundle, false);
+
+                // if we don't get an info, someone else is currently loading or unloading
+                // or the bundle is already uninstalled
+                if (bundleContentInfo == null) {
+                    return;
+                }
+
+                try {
+                    uninstallContent(session, bundle, (String[])bundleContentInfo.get(ContentLoaderService.PROPERTY_UNINSTALL_PATHS));
+                    jcrContentHelper.contentIsUninstalled(session, bundle);
+                } finally {
+                    jcrContentHelper.unlockBundleContentInfo(session, bundle, false, null);
+
+                }
+            } catch (RepositoryException re) {
+                log.error("Cannot remove initial content for bundle "
+                        + bundle.getSymbolicName() + " : " + re.getMessage(), re);
+            }
+        }
+    }
+
+    // ---------- internal -----------------------------------------------------
+
+    /**
+     * Install the content from the bundle.
+     * @return If the content should be removed on uninstall, a list of top nodes
+     */
+    private List<String> installContent(final Session session,
+                                        final Bundle bundle,
+                                        final Iterator<PathEntry> pathIter,
+                                        final boolean contentAlreadyLoaded)
+    throws RepositoryException {
+        final List<String> createdNodes = new ArrayList<String>();
+
+        log.debug("Installing initial content from bundle {}",
+            bundle.getSymbolicName());
+        try {
+
+            while (pathIter.hasNext()) {
+                final PathEntry entry = pathIter.next();
+                if (!contentAlreadyLoaded || entry.isOverwrite()) {
+
+                    final Node targetNode = getTargetNode(session, entry.getTarget());
+
+                    if (targetNode != null) {
+                        installFromPath(bundle, entry.getPath(), entry, targetNode,
+                            entry.isUninstall() ? createdNodes : null);
+                    }
+                }
+            }
+
+            // now optimize created nodes list
+            Collections.sort(createdNodes);
+            if ( createdNodes.size() > 1) {
+                final Iterator<String> i = createdNodes.iterator();
+                String previous = i.next() + '/';
+                while ( i.hasNext() ) {
+                    final String current = i.next();
+                    if ( current.startsWith(previous) ) {
+                        i.remove();
+                    } else {
+                        previous = current + '/';
+                    }
+                }
+            }
+
+            // persist modifications now
+            session.refresh(true);
+            session.save();
+
+            // finally checkin versionable nodes
+            for (final Node versionable : this.contentCreator.getVersionables()) {
+                versionable.checkin();
+            }
+
+        } finally {
+            try {
+                if (session.hasPendingChanges()) {
+                    session.refresh(false);
+                }
+            } catch (RepositoryException re) {
+                log.warn(
+                    "Failure to rollback partial initial content for bundle {}",
+                    bundle.getSymbolicName(), re);
+            }
+            this.contentCreator.clear();
+        }
+        log.debug("Done installing initial content from bundle {}",
+            bundle.getSymbolicName());
+
+        return createdNodes;
+    }
+
+    /**
+     * Handle content installation for a single path.
+     *
+     * @param bundle The bundle containing the content.
+     * @param path The path
+     * @param overwrite Should the content be overwritten.
+     * @param parent The parent node.
+     * @param createdNodes An optional list to store all new nodes. This list is used for an uninstall
+     * @throws RepositoryException
+     */
+    private void installFromPath(final Bundle bundle,
+                                 final String path,
+                                 final PathEntry configuration,
+                                 final Node parent,
+                                 final List<String> createdNodes)
+    throws RepositoryException {
+
+        @SuppressWarnings("unchecked")
+        Enumeration<String> entries = bundle.getEntryPaths(path);
+        if (entries == null) {
+            log.info("install: No initial content entries at {}", path);
+            return;
+        }
+        //  init content creator
+        this.contentCreator.init(configuration, this.defaultImportProviders, createdNodes);
+
+        final Map<URL, Node> processedEntries = new HashMap<URL, Node>();
+        // potential root node import/extension
+        URL rootNodeDescriptor = importRootNode(parent.getSession(), bundle, path);
+        if (rootNodeDescriptor != null) {
+            processedEntries.put(rootNodeDescriptor,
+                parent.getSession().getRootNode());
+        }
+
+        while (entries.hasMoreElements()) {
+            final String entry = entries.nextElement();
+            log.debug("Processing initial content entry {}", entry);
+            if (entry.endsWith("/")) {
+
+                // dir, check for node descriptor , else create dir
+                final String base = entry.substring(0, entry.length() - 1);
+
+                URL nodeDescriptor = null;
+                for (String ext : this.contentCreator.getImportProviders().keySet()) {
+                    nodeDescriptor = bundle.getEntry(base + ext);
+                    if (nodeDescriptor != null) {
+                        break;
+                    }
+                }
+
+                // if we have a descriptor, which has not been processed yet,
+                // otherwise call createFolder, which creates an nt:folder or
+                // returns an existing node (created by a descriptor)
+                final String name = getName(base);
+                Node node = null;
+                if (nodeDescriptor != null) {
+                    node = processedEntries.get(nodeDescriptor);
+                    if (node == null) {
+                        node = createNode(parent, name, nodeDescriptor,
+                                          configuration);
+                        processedEntries.put(nodeDescriptor, node);
+                    }
+                } else {
+                    node = createFolder(parent, name, configuration.isOverwrite());
+                }
+
+                // walk down the line
+                if (node != null) {
+                    installFromPath(bundle, entry, configuration, node, createdNodes);
+                }
+
+            } else {
+
+                // file => create file
+                final URL file = bundle.getEntry(entry);
+                if (processedEntries.containsKey(file)) {
+                    // this is a consumed node descriptor
+                    continue;
+                }
+                final String name = getName(entry);
+
+                // file, check for node descriptor , else create dir
+                URL nodeDescriptor = null;
+                for (String ext : this.contentCreator.getImportProviders().keySet()) {
+                    nodeDescriptor = bundle.getEntry(entry + ext);
+                    if (nodeDescriptor != null) {
+                        break;
+                    }
+                }
+
+                // install if it is a descriptor
+                boolean foundProvider = this.contentCreator.getImportProvider(entry) != null;
+
+                Node node = null;
+                if (foundProvider) {
+                    if ((node = createNode(parent, name, file, configuration)) != null) {
+                        processedEntries.put(file, node);
+                    }
+                }
+
+                // otherwise just place as file
+                if ( node == null ) {
+                    try {
+                        createFile(configuration, parent, file, createdNodes);
+                        node = parent.getNode(name);
+                    } catch (IOException ioe) {
+                        log.warn("Cannot create file node for {}", file, ioe);
+                    }
+                }
+                // if we have a descriptor, which has not been processed yet,
+                // process it
+                if (nodeDescriptor != null && processedEntries.get(nodeDescriptor) == null ) {
+                    try {
+                        this.contentCreator.setIgnoreOverwriteFlag(true);
+                        node = createNode(parent, name, nodeDescriptor,
+                                          configuration);
+                        processedEntries.put(nodeDescriptor, node);
+                    } finally {
+                        this.contentCreator.setIgnoreOverwriteFlag(false);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a new node from a content resource found in the bundle.
+     * @param parent The parent node
+     * @param name   The name of the new content node
+     * @param resourceUrl The resource url.
+     * @param overwrite Should the content be overwritten?
+     * @param versionables
+     * @param checkin
+     * @return
+     * @throws RepositoryException
+     */
+    private Node createNode(Node parent,
+                            String name,
+                            URL resourceUrl,
+                            PathEntry configuration)
+    throws RepositoryException {
+        final String resourcePath = resourceUrl.getPath().toLowerCase();
+        InputStream ins = null;
+        try {
+            // special treatment for system view imports
+            if (resourcePath.endsWith(EXT_JCR_XML)) {
+                return importSystemView(parent, name, resourceUrl);
+            }
+
+            // get the node reader for this resource
+            final ImportProvider ip = this.contentCreator.getImportProvider(resourcePath);
+            if ( ip == null ) {
+                return null;
+            }
+            final ContentReader nodeReader = ip.getReader();
+
+            // cannot find out the type
+            if (nodeReader == null) {
+                return null;
+            }
+
+            this.contentCreator.prepareParsing(parent, toPlainName(name));
+            nodeReader.parse(resourceUrl, this.contentCreator);
+
+            return this.contentCreator.getRootNode();
+        } catch (RepositoryException re) {
+            throw re;
+        } catch (Throwable t) {
+            throw new RepositoryException(t.getMessage(), t);
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a folder
+     *
+     * @param parent The parent node.
+     * @param name The name of the folder
+     * @param overwrite If set to true, an existing folder is removed first.
+     * @return The node pointing to the folder.
+     * @throws RepositoryException
+     */
+    private Node createFolder(Node parent, String name, final boolean overwrite)
+            throws RepositoryException {
+        if (parent.hasNode(name)) {
+            if (overwrite) {
+                parent.getNode(name).remove();
+            } else {
+                return parent.getNode(name);
+            }
+        }
+
+        return parent.addNode(name, "sling:Folder");
+    }
+
+    /**
+     * Create a file from the given url.
+     *
+     * @param parent
+     * @param source
+     * @throws IOException
+     * @throws RepositoryException
+     */
+    private void createFile(PathEntry configuration, Node parent, URL source, List<String> createdNodes)
+    throws IOException, RepositoryException {
+        final String srcPath = source.getPath();
+        int pos = srcPath.lastIndexOf("/");
+        final String name = getName(source.getPath());
+        final String path;
+        if ( pos == -1 ) {
+            path = name;
+        } else {
+            path = srcPath.substring(0, pos + 1) + name;
+        }
+
+        this.contentCreator.init(configuration, defaultImportProviders, createdNodes);
+        this.contentCreator.prepareParsing(parent, name);
+        final URLConnection conn = source.openConnection();
+        final long lastModified = conn.getLastModified();
+        final String type = conn.getContentType();
+        final InputStream data = conn.getInputStream();
+        this.contentCreator.createFileAndResourceNode(path, data, type, lastModified);
+        this.contentCreator.finishNode();
+        this.contentCreator.finishNode();
+    }
+
+    /**
+     * Gets and decods the name part of the <code>path</code>. The name is
+     * the part of the path after the last slash (or the complete path if no
+     * slash is contained). To support names containing unsupported characters
+     * such as colon (<code>:</code>), names may be URL encoded (see
+     * <code>java.net.URLEncoder</code>) using the <i>UTF-8</i> character
+     * encoding. In this case, this method decodes the name using the
+     * <code>java.netURLDecoder</code> class with the <i>UTF-8</i> character
+     * encoding.
+     *
+     * @param path The path from which to extract the name part.
+     * @return The URL decoded name part.
+     */
+    private String getName(String path) {
+        int lastSlash = path.lastIndexOf('/');
+        String name = (lastSlash < 0) ? path : path.substring(lastSlash + 1);
+
+        // check for encoded characters (%xx)
+        // has encoded characters, need to decode
+        if (name.indexOf('%') >= 0) {
+            try {
+                return URLDecoder.decode(name, "UTF-8");
+            } catch (UnsupportedEncodingException uee) {
+                // actually unexpected because UTF-8 is required by the spec
+                log.error("Cannot decode "
+                    + name
+                    + " beause the platform has no support for UTF-8, using undecoded");
+            } catch (Exception e) {
+                // IllegalArgumentException or failure to decode
+                log.error("Cannot decode " + name + ", using undecoded", e);
+            }
+        }
+
+        // not encoded or problems decoding, return the name unmodified
+        return name;
+    }
+
+    private Node getTargetNode(Session session, String path)
+            throws RepositoryException {
+
+        // not specyfied path directive
+        if (path == null) return session.getRootNode();
+
+        int firstSlash = path.indexOf("/");
+
+        // it's a relative path
+        if (firstSlash != 0) path = "/" + path;
+
+        if ( !session.itemExists(path) ) {
+            Node currentNode = session.getRootNode();
+            final StringTokenizer st = new StringTokenizer(path.substring(1), "/");
+            while ( st.hasMoreTokens() ) {
+                final String name = st.nextToken();
+                if ( !currentNode.hasNode(name) ) {
+                    currentNode.addNode(name, "sling:Folder");
+                }
+                currentNode = currentNode.getNode(name);
+            }
+            return currentNode;
+        }
+        Item item = session.getItem(path);
+        return (item.isNode()) ? (Node) item : null;
+    }
+
+    private void uninstallContent(final Session session, final Bundle bundle,
+            final String[] uninstallPaths) {
+        try {
+            log.debug("Uninstalling initial content from bundle {}",
+                bundle.getSymbolicName());
+            if ( uninstallPaths != null && uninstallPaths.length > 0 ) {
+                for(final String path : uninstallPaths) {
+                    if ( session.itemExists(path) ) {
+                        session.getItem(path).remove();
+                    }
+                }
+                // persist modifications now
+                session.save();
+            }
+
+            log.debug("Done uninstalling initial content from bundle {}",
+                bundle.getSymbolicName());
+        } catch (RepositoryException re) {
+            log.error("Unable to uninstall initial content from bundle "
+                + bundle.getSymbolicName(), re);
+        } finally {
+            try {
+                if (session.hasPendingChanges()) {
+                    session.refresh(false);
+                }
+            } catch (RepositoryException re) {
+                log.warn(
+                    "Failure to rollback uninstaling initial content for bundle {}",
+                    bundle.getSymbolicName(), re);
+            }
+        }
+    }
+
+    /**
+     * Import the XML file as JCR system or document view import. If the XML
+     * file is not a valid system or document view export/import file,
+     * <code>false</code> is returned.
+     *
+     * @param parent The parent node below which to import
+     * @param nodeXML The URL to the XML file to import
+     * @return <code>true</code> if the import succeeds, <code>false</code>
+     *         if the import fails due to XML format errors.
+     * @throws IOException If an IO error occurrs reading the XML file.
+     */
+    private Node importSystemView(Node parent, String name, URL nodeXML)
+    throws IOException {
+
+        InputStream ins = null;
+        try {
+
+            // check whether we have the content already, nothing to do then
+            if ( name.endsWith(EXT_JCR_XML) ) {
+                name = name.substring(0, name.length() - EXT_JCR_XML.length());
+            }
+            if (parent.hasNode(name)) {
+                log.debug(
+                    "importSystemView: Node {} for XML {} already exists, nothing to to",
+                    name, nodeXML);
+                return parent.getNode(name);
+            }
+
+            ins = nodeXML.openStream();
+            Session session = parent.getSession();
+            session.importXML(parent.getPath(), ins, IMPORT_UUID_CREATE_NEW);
+
+            // additionally check whether the expected child node exists
+            return (parent.hasNode(name)) ? parent.getNode(name) : null;
+
+        } catch (InvalidSerializedDataException isde) {
+
+            // the xml might not be System or Document View export, fall back
+            // to old-style XML reading
+            log.info(
+                "importSystemView: XML {} does not seem to be system view export, trying old style; cause: {}",
+                nodeXML, isde.toString());
+            return null;
+
+        } catch (RepositoryException re) {
+
+            // any other repository related issue...
+            log.info(
+                "importSystemView: Repository issue loading XML {}, trying old style; cause: {}",
+                nodeXML, re.toString());
+            return null;
+
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                    // ignore
+                }
+            }
+        }
+
+    }
+
+    protected static final class Descriptor {
+        public URL rootNodeDescriptor;
+
+        public ContentReader nodeReader;
+    }
+
+    /**
+     * Return the root node descriptor.
+     */
+    private Descriptor getRootNodeDescriptor(final Bundle bundle,
+                                             final String path) {
+        URL rootNodeDescriptor = null;
+
+        for (Map.Entry<String, ImportProvider> e : this.contentCreator.getImportProviders().entrySet()) {
+            if (e.getValue() != null) {
+                rootNodeDescriptor = bundle.getEntry(path + ROOT_DESCRIPTOR
+                    + e.getKey());
+                if (rootNodeDescriptor != null) {
+                    try {
+                        final Descriptor d = new Descriptor();
+                        d.rootNodeDescriptor = rootNodeDescriptor;
+                        d.nodeReader = e.getValue().getReader();
+                        return d;
+                    } catch (IOException ioe) {
+                        log.error("Unable to setup node reader for "
+                            + e.getKey(), ioe);
+                        return null;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Imports mixin nodes and properties (and optionally child nodes) of the
+     * root node.
+     */
+    private URL importRootNode(Session session, Bundle bundle, String path)
+    throws RepositoryException {
+        final Descriptor descriptor = getRootNodeDescriptor(bundle, path);
+        // no root descriptor found
+        if (descriptor == null) {
+            return null;
+        }
+
+        try {
+            this.contentCreator.prepareParsing(session.getRootNode(), null);
+            descriptor.nodeReader.parse(descriptor.rootNodeDescriptor, this.contentCreator);
+
+            return descriptor.rootNodeDescriptor;
+        } catch (RepositoryException re) {
+            throw re;
+        } catch (Throwable t) {
+            throw new RepositoryException(t.getMessage(), t);
+        }
+
+    }
+
+    private String toPlainName(String name) {
+        final String providerExt = this.contentCreator.getImportProviderExtension(name);
+        if (providerExt != null) {
+            return name.substring(0, name.length() - providerExt.length());
+        }
+        return name;
+
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
new file mode 100644
index 0000000..64213c5
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
@@ -0,0 +1,178 @@
+/*
+ * 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.contentloader.internal;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.apache.sling.commons.osgi.ManifestHeader;
+import org.osgi.framework.Bundle;
+
+/**
+ * A path entry from the manifest for initial content.
+ */
+public class PathEntry {
+
+    /** The manifest header to specify initial content to be loaded. */
+    public static final String CONTENT_HEADER = "Sling-Initial-Content";
+
+    /**
+     * The overwrite directive specifying if content should be overwritten or
+     * just initially added.
+     */
+    public static final String OVERWRITE_DIRECTIVE = "overwrite";
+
+    /** The uninstall directive specifying if content should be uninstalled. */
+    public static final String UNINSTALL_DIRECTIVE = "uninstall";
+
+    /**
+     * The path directive specifying the target node where initial content will
+     * be loaded.
+     */
+    public static final String PATH_DIRECTIVE = "path";
+
+    /**
+     * The checkin directive specifying whether versionable nodes should be
+     * checked in
+     */
+    public static final String CHECKIN_DIRECTIVE = "checkin";
+
+    /**
+     * The ignore import providers directive specifying whether the available {@link ImportProvider}s
+     * should be used during content loading. This is a string value that defaults to the empty
+     * string..
+     * @since 2.0.4
+     */
+    public static final String IGNORE_IMPORT_PROVIDERS_DIRECTIVE = "ignoreImportProviders";
+
+    /** The path for the initial content. */
+    private final String path;
+
+    /** Should existing content be overwritten? */
+    private final boolean overwrite;
+
+    /** Should existing content be uninstalled? */
+    private final boolean uninstall;
+
+    /** Should versionable nodes be checked in? */
+    private final boolean checkin;
+
+    /** Which import providers should be ignored? @since 2.0.4 */
+    private final List<String> ignoreImportProviders;
+
+    /**
+     * Target path where initial content will be loaded. If it´s null then
+     * target node is the root node
+     */
+    private final String target;
+
+    public static Iterator<PathEntry> getContentPaths(final Bundle bundle) {
+        final List<PathEntry> entries = new ArrayList<PathEntry>();
+
+        final String root = (String) bundle.getHeaders().get(CONTENT_HEADER);
+        if (root != null) {
+            final ManifestHeader header = ManifestHeader.parse(root);
+            for (final ManifestHeader.Entry entry : header.getEntries()) {
+                entries.add(new PathEntry(entry));
+            }
+        }
+
+        if (entries.size() == 0) {
+            return null;
+        }
+        return entries.iterator();
+    }
+
+    public PathEntry(ManifestHeader.Entry entry) {
+        this.path = entry.getValue();
+
+        // check for directives
+
+        // overwrite directive
+        final String overwriteValue = entry.getDirectiveValue(OVERWRITE_DIRECTIVE);
+        if (overwriteValue != null) {
+            this.overwrite = Boolean.valueOf(overwriteValue);
+        } else {
+            this.overwrite = false;
+        }
+
+        // uninstall directive
+        final String uninstallValue = entry.getDirectiveValue(UNINSTALL_DIRECTIVE);
+        if (uninstallValue != null) {
+            this.uninstall = Boolean.valueOf(uninstallValue);
+        } else {
+            this.uninstall = this.overwrite;
+        }
+
+        // path directive
+        final String pathValue = entry.getDirectiveValue(PATH_DIRECTIVE);
+        if (pathValue != null) {
+            this.target = pathValue;
+        } else {
+            this.target = null;
+        }
+
+        // checkin directive
+        final String checkinValue = entry.getDirectiveValue(CHECKIN_DIRECTIVE);
+        if (checkinValue != null) {
+            this.checkin = Boolean.valueOf(checkinValue);
+        } else {
+            this.checkin = false;
+        }
+
+        // expand directive
+        this.ignoreImportProviders = new ArrayList<String>();
+        final String expandValue = entry.getDirectiveValue(IGNORE_IMPORT_PROVIDERS_DIRECTIVE);
+        if ( expandValue != null && expandValue.length() > 0 ) {
+            final StringTokenizer st = new StringTokenizer(expandValue, ",");
+            while ( st.hasMoreTokens() ) {
+                this.ignoreImportProviders.add(st.nextToken());
+            }
+        }
+    }
+
+    public String getPath() {
+        return this.path;
+    }
+
+    public boolean isOverwrite() {
+        return this.overwrite;
+    }
+
+    public boolean isUninstall() {
+        return this.uninstall;
+    }
+
+    public boolean isCheckin() {
+        return this.checkin;
+    }
+
+    public boolean isIgnoredImportProvider(String extension) {
+        if ( extension.startsWith(".") ) {
+            extension = extension.substring(1);
+        }
+        return this.ignoreImportProviders.contains(extension);
+    }
+
+    public String getTarget() {
+        return target;
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
new file mode 100644
index 0000000..8a4c441
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
@@ -0,0 +1,216 @@
+/*
+ * 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.contentloader.internal.readers;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+import org.apache.sling.jcr.contentloader.internal.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.ImportProvider;
+
+
+/**
+ * The <code>JsonReader</code> TODO
+ */
+public class JsonReader implements ContentReader {
+
+    private static final String REFERENCE = "jcr:reference:";
+    private static final String PATH = "jcr:path:";
+
+    private static final Set<String> ignoredNames = new HashSet<String>();
+    static {
+        ignoredNames.add("jcr:primaryType");
+        ignoredNames.add("jcr:mixinTypes");
+        ignoredNames.add("jcr:uuid");
+        ignoredNames.add("jcr:baseVersion");
+        ignoredNames.add("jcr:predecessors");
+        ignoredNames.add("jcr:successors");
+        ignoredNames.add("jcr:checkedOut");
+        ignoredNames.add("jcr:created");
+    }
+
+    public static final ImportProvider PROVIDER = new ImportProvider() {
+        private JsonReader jsonReader;
+
+        public ContentReader getReader() {
+            if (jsonReader == null) {
+                jsonReader = new JsonReader();
+            }
+            return jsonReader;
+        }
+    };
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.net.URL, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     */
+    public void parse(java.net.URL url, ContentCreator contentCreator)
+    throws IOException, RepositoryException {
+        InputStream ins = null;
+        try {
+            ins = url.openStream();
+            parse(ins, contentCreator);
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+    }
+
+    public void parse(InputStream ins, ContentCreator contentCreator) throws IOException, RepositoryException {
+        try {
+            String jsonString = toString(ins).trim();
+            if (!jsonString.startsWith("{")) {
+                jsonString = "{" + jsonString + "}";
+            }
+
+            JSONObject json = new JSONObject(jsonString);
+            this.createNode(null, json, contentCreator);
+        } catch (JSONException je) {
+            throw (IOException) new IOException(je.getMessage()).initCause(je);
+        }
+    }
+
+    protected void createNode(String name, JSONObject obj, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+        Object primaryTypeObj = obj.opt("jcr:primaryType");
+        String primaryType = null;
+        if (primaryTypeObj != null) {
+            primaryType = String.valueOf(primaryTypeObj);
+        }
+
+        String[] mixinTypes = null;
+        Object mixinsObject = obj.opt("jcr:mixinTypes");
+        if (mixinsObject instanceof JSONArray) {
+            JSONArray mixins = (JSONArray) mixinsObject;
+            mixinTypes = new String[mixins.length()];
+            for (int i = 0; i < mixins.length(); i++) {
+                mixinTypes[i] = mixins.getString(i);
+            }
+        }
+
+        contentCreator.createNode(name, primaryType, mixinTypes);
+
+        // add properties and nodes
+        JSONArray names = obj.names();
+        for (int i = 0; names != null && i < names.length(); i++) {
+            final String n = names.getString(i);
+            // skip well known objects
+            if (!ignoredNames.contains(n)) {
+                Object o = obj.get(n);
+                if (o instanceof JSONObject) {
+                    this.createNode(n, (JSONObject) o, contentCreator);
+                } else {
+                    this.createProperty(n, o, contentCreator);
+                }
+            }
+        }
+        contentCreator.finishNode();
+    }
+
+    protected void createProperty(String name, Object value, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+        // assume simple value
+        if (value instanceof JSONArray) {
+            // multivalue
+            final JSONArray array = (JSONArray) value;
+            if (array.length() > 0) {
+                final String values[] = new String[array.length()];
+                for (int i = 0; i < array.length(); i++) {
+                    values[i] = array.get(i).toString();
+                }
+                final int propertyType = getType(name, values[0]);
+                contentCreator.createProperty(getName(name), propertyType, values);
+            } else {
+                contentCreator.createProperty(getName(name), PropertyType.STRING, new String[0]);
+            }
+
+        } else {
+            // single value
+            final int propertyType = getType(name, value);
+            contentCreator.createProperty(getName(name), propertyType, value.toString());
+        }
+    }
+
+    protected int getType(String name, Object object) {
+        if (object instanceof Double || object instanceof Float) {
+            return PropertyType.DOUBLE;
+        } else if (object instanceof Number) {
+            return PropertyType.LONG;
+        } else if (object instanceof Boolean) {
+            return PropertyType.BOOLEAN;
+        } else if (object instanceof String) {
+            if (name.startsWith(REFERENCE)) return PropertyType.REFERENCE;
+            if (name.startsWith(PATH)) return PropertyType.PATH;
+        }
+
+        // fall back to default
+        return PropertyType.STRING;
+    }
+
+    protected String getName(String name) {
+        if (name.startsWith(REFERENCE)) return name.substring(REFERENCE.length());
+        if (name.startsWith(PATH)) return name.substring(PATH.length());
+        return name;
+    }
+
+    private String toString(InputStream ins) throws IOException {
+        if (!ins.markSupported()) {
+            ins = new BufferedInputStream(ins);
+        }
+
+        String encoding;
+        ins.mark(5);
+        int c = ins.read();
+        if (c == '#') {
+            // character encoding following
+            StringBuffer buf = new StringBuffer();
+            for (c = ins.read(); !Character.isWhitespace((char) c); c = ins.read()) {
+                buf.append((char) c);
+            }
+            encoding = buf.toString();
+        } else {
+            ins.reset();
+            encoding = "UTF-8";
+        }
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        byte[] buf = new byte[1024];
+        int rd;
+        while ( (rd = ins.read(buf)) >= 0) {
+            bos.write(buf, 0, rd);
+        }
+        bos.close(); // just to comply with the contract
+
+        return new String(bos.toByteArray(), encoding);
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
new file mode 100644
index 0000000..6b6fcaf
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReader.java
@@ -0,0 +1,455 @@
+/*
+ * 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.contentloader.internal.readers;
+
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+import org.apache.sling.jcr.contentloader.internal.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.ImportProvider;
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * This reader reads an xml file defining the content.
+ * The xml format should have this format:
+ * <node>
+ *   <name>the name of the node</name>
+ *   <primaryNodeType>type</primaryNodeType>
+ *   <mixinNodeTypes>
+ *     <mixinNodeType>mixtype1</mixinNodeType>
+ *     <mixinNodeType>mixtype2</mixinNodeType>
+ *   </mixingNodeTypes>
+ *   <properties>
+ *     <property>
+ *       <name>propName</name>
+ *       <value>propValue</value>
+ *           or
+ *       <values>
+ *         <value/> for multi value properties
+ *       </values>
+ *       <type>propType</type>
+ *     </property>
+ *     <!-- more properties -->
+ *   </properties>
+ *   <nodes>
+ *     <!-- child nodes -->
+ *     <node>
+ *       ..
+ *     </node>
+ *   </nodes>
+ * </node>
+ */
+public class XmlReader implements ContentReader {
+
+    /*
+     * <node> <primaryNodeType>type</primaryNodeType> <mixinNodeTypes>
+     * <mixinNodeType>mixtype1</mixinNodeType> <mixinNodeType>mixtype2</mixinNodeType>
+     * </mixinNodeTypes> <properties> <property> <name>propName</name>
+     * <value>propValue</value> <type>propType</type> </property> <!-- more
+     * --> </properties> </node>
+     */
+
+    /** default log */
+    private static final String ELEM_NODE = "node";
+
+    private static final String ELEM_PRIMARY_NODE_TYPE = "primaryNodeType";
+
+    private static final String ELEM_MIXIN_NODE_TYPE = "mixinNodeType";
+
+    private static final String ELEM_PROPERTY = "property";
+
+    private static final String ELEM_NAME = "name";
+
+    private static final String ELEM_VALUE = "value";
+
+    private static final String ELEM_VALUES = "values";
+
+    private static final String ELEM_TYPE = "type";
+
+    private static final String XML_STYLESHEET_PROCESSING_INSTRUCTION = "xml-stylesheet";
+
+    private static final String HREF_ATTRIBUTE = "href";
+
+    public static final ImportProvider PROVIDER = new ImportProvider() {
+        private XmlReader xmlReader;
+
+        public ContentReader getReader() throws IOException {
+            if (xmlReader == null) {
+                try {
+                    xmlReader = new XmlReader();
+                } catch (Throwable t) {
+                    throw (IOException) new IOException(t.getMessage()).initCause(t);
+                }
+            }
+            return xmlReader;
+        }
+    };
+    private KXmlParser xmlParser;
+
+    XmlReader() {
+        this.xmlParser = new KXmlParser();
+    }
+
+    // ---------- XML content access -------------------------------------------
+
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.net.URL, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     */
+    public synchronized void parse(java.net.URL url, ContentCreator creator)
+            throws IOException, RepositoryException {
+        BufferedInputStream bufferedInput = null;
+        try {
+            // We need to buffer input, so that we can reset the stream if we encounter an XSL stylesheet reference
+            bufferedInput = new BufferedInputStream(url.openStream());
+            parseInternal(bufferedInput, creator, url);
+        } catch (XmlPullParserException xppe) {
+            throw (IOException) new IOException(xppe.getMessage()).initCause(xppe);
+        } finally {
+            closeStream(bufferedInput);
+        }
+    }
+
+    private void parseInternal(InputStream bufferedInput, ContentCreator creator, java.net.URL xmlLocation) throws XmlPullParserException, IOException, RepositoryException {
+        final StringBuffer contentBuffer = new StringBuffer();
+        // Mark the beginning of the stream. We assume that if there's an XSL processing instruction,
+        // it will occur in the first gulp - which makes sense, as processing instructions must be
+        // specified before the root elemeent of an XML file.
+        bufferedInput.mark(bufferedInput.available());
+        // set the parser input, use null encoding to force detection with
+        // <?xml?>
+        this.xmlParser.setInput(bufferedInput, null);
+
+        NodeDescription.SHARED.clear();
+        PropertyDescription.SHARED.clear();
+
+        NodeDescription currentNode = null;
+        PropertyDescription currentProperty = null;
+        String currentElement;
+
+
+        int eventType = this.xmlParser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.PROCESSING_INSTRUCTION) {
+                ProcessingInstruction pi = new ProcessingInstruction(this.xmlParser.getText());
+                // Look for a reference to an XSL stylesheet
+                if (pi.getName().equals(XML_STYLESHEET_PROCESSING_INSTRUCTION)) {
+                    // Rewind the input stream to the beginning, so that it can be transformed with XSL
+                    bufferedInput.reset();
+                    // Pipe the XML input through the XSL transformer
+                    XslTransformerStream transformerStream = new XslTransformerStream(bufferedInput, pi.getAttribute(HREF_ATTRIBUTE), xmlLocation);
+                    // Start the transformer thread
+                    transformerStream.startTransform();
+                    // Re-run the XML parser, now with the transformed XML
+                    parseInternal(transformerStream, creator, xmlLocation);
+                    transformerStream.close();
+                    return;
+
+                }
+            }
+            if (eventType == XmlPullParser.START_TAG) {
+
+                currentElement = this.xmlParser.getName();
+
+                if (ELEM_PROPERTY.equals(currentElement)) {
+                    currentNode = NodeDescription.create(currentNode, creator);
+                    currentProperty = PropertyDescription.SHARED;
+                } else if (ELEM_NODE.equals(currentElement)) {
+                    currentNode = NodeDescription.create(currentNode, creator);
+                    currentNode = NodeDescription.SHARED;
+                }
+
+            } else if (eventType == XmlPullParser.END_TAG) {
+
+                String qName = this.xmlParser.getName();
+                String content = contentBuffer.toString().trim();
+                contentBuffer.delete(0, contentBuffer.length());
+
+                if (ELEM_PROPERTY.equals(qName)) {
+                    currentProperty = PropertyDescription.create(currentProperty, creator);
+
+                } else if (ELEM_NAME.equals(qName)) {
+                    if (currentProperty != null) {
+                        currentProperty.name = content;
+                    } else if (currentNode != null) {
+                        currentNode.name = content;
+                    }
+
+                } else if (ELEM_VALUE.equals(qName)) {
+                    currentProperty.addValue(content);
+
+                } else if (ELEM_VALUES.equals(qName)) {
+                    currentProperty.isMultiValue = true;
+
+                } else if (ELEM_TYPE.equals(qName)) {
+                    currentProperty.type = content;
+
+                } else if (ELEM_NODE.equals(qName)) {
+                    currentNode = NodeDescription.create(currentNode, creator);
+                    creator.finishNode();
+
+                } else if (ELEM_PRIMARY_NODE_TYPE.equals(qName)) {
+                    if ( currentNode == null ) {
+                        throw new IOException("Element is not allowed at this location: " + qName);
+                    }
+                    currentNode.primaryNodeType = content;
+
+                } else if (ELEM_MIXIN_NODE_TYPE.equals(qName)) {
+                    if ( currentNode == null ) {
+                        throw new IOException("Element is not allowed at this location: " + qName);
+                    }
+                    currentNode.addMixinType(content);
+                }
+
+            } else if (eventType == XmlPullParser.TEXT || eventType == XmlPullParser.CDSECT) {
+                contentBuffer.append(this.xmlParser.getText());
+            }
+
+            eventType = this.xmlParser.nextToken();
+        }
+    }
+
+    /**
+     * Takes an XML input stream and pipes it through an XSL transformer.
+     * Callers should call {@link #startTransform} before trying to use the stream, or the caller will wait indefinately for input.
+     */
+    private static class XslTransformerStream extends PipedInputStream {
+        private InputStream inputXml;
+        private String xslHref;
+        private Thread transformerThread;
+        private PipedOutputStream pipedOut;
+        private URL xmlLocation;
+
+        /**
+         * Instantiate the XslTransformerStream.
+         * @param inputXml XML to be transformed.
+         * @param xslHref Path to an XSL stylesheet
+         * @param xmlLocation
+         * @throws IOException
+         */
+        public XslTransformerStream(InputStream inputXml, String xslHref, URL xmlLocation) throws IOException {
+            super();
+            this.inputXml = inputXml;
+            this.xslHref = xslHref;
+            this.transformerThread = null;
+            this.pipedOut = new PipedOutputStream(this);
+            this.xmlLocation = xmlLocation;
+        }
+
+        /**
+         * Starts the XSL transformer in a new thread, so that it can pipe its output to our <code>PipedInputStream</code>.
+         * @throws IOException
+         */
+        public void startTransform() throws IOException {
+            final URL xslResource = new java.net.URL(xmlLocation, this.xslHref);
+
+/*
+            if (xslResource == null) {
+                throw new IOException("Could not find " + xslHref);
+            }
+*/
+
+            transformerThread = new Thread(
+                    new Runnable() {
+                        public void run() {
+                            try {
+                                Source xml = new StreamSource(inputXml);
+                                Source xsl = new StreamSource(xslResource.toExternalForm());
+                                final StreamResult streamResult;
+                                final Templates templates = TransformerFactory.newInstance().newTemplates(xsl);
+                                streamResult = new StreamResult(pipedOut);
+                                templates.newTransformer().transform(xml, streamResult);
+                            } catch (TransformerConfigurationException e) {
+                                throw new RuntimeException("Error initializing XSL transformer", e);
+                            } catch (TransformerException e) {
+                                throw new RuntimeException("Error transforming", e);
+                            } finally {
+                                closeStream(pipedOut);
+                            }
+                        }
+                    }
+                    , "XslTransformerThread");
+            transformerThread.start();
+        }
+
+
+    }
+
+    /**
+     * Utility function to close a stream if it is still open.
+     * @param closeable Stream to close
+     */
+    private static void closeStream(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (IOException ignore) {
+            }
+        }
+    }
+
+    protected static final class NodeDescription {
+
+        public static NodeDescription SHARED = new NodeDescription();
+
+        public String name;
+        public String primaryNodeType;
+        public List<String> mixinTypes;
+
+        public static NodeDescription create(NodeDescription desc, ContentCreator creator)
+                throws RepositoryException {
+            if ( desc != null ) {
+                creator.createNode(desc.name, desc.primaryNodeType, desc.getMixinTypes());
+                desc.clear();
+            }
+            return null;
+        }
+
+        public void addMixinType(String v) {
+            if ( this.mixinTypes == null ) {
+                this.mixinTypes = new ArrayList<String>();
+            }
+            this.mixinTypes.add(v);
+        }
+
+
+        private String[] getMixinTypes() {
+            if ( this.mixinTypes == null || this.mixinTypes.size() == 0) {
+                return null;
+            }
+            return mixinTypes.toArray(new String[this.mixinTypes.size()]);
+        }
+
+        private void clear() {
+            this.name = null;
+            this.primaryNodeType = null;
+            if ( this.mixinTypes != null ) {
+                this.mixinTypes.clear();
+            }
+        }
+    }
+
+    protected static final class PropertyDescription {
+
+        public static PropertyDescription SHARED = new PropertyDescription();
+
+        public static PropertyDescription create(PropertyDescription desc, ContentCreator creator)
+                throws RepositoryException {
+            int type = (desc.type == null ? PropertyType.STRING : PropertyType.valueFromName(desc.type));
+            if ( desc.isMultiValue ) {
+                creator.createProperty(desc.name, type, desc.getPropertyValues());
+            } else {
+                String value = null;
+                if ( desc.values != null && desc.values.size() == 1 ) {
+                    value = desc.values.get(0);
+                }
+                creator.createProperty(desc.name, type, value);
+            }
+            desc.clear();
+            return null;
+        }
+
+        public String name;
+        public String type;
+        public List<String> values;
+        public boolean isMultiValue;
+
+        public void addValue(String v) {
+            if ( this.values == null ) {
+                this.values = new ArrayList<String>();
+            }
+            this.values.add(v);
+        }
+
+        private String[] getPropertyValues() {
+            if ( this.values == null || this.values.size() == 0) {
+                return null;
+            }
+            return values.toArray(new String[this.values.size()]);
+        }
+
+        private void clear() {
+            this.name = null;
+            this.type = null;
+            if ( this.values != null ) {
+                this.values.clear();
+            }
+            this.isMultiValue = false;
+        }
+    }
+
+    /**
+     * Represents an XML processing instruction.<br />
+     * A processing instruction like <code>&lt;?xml-stylesheet href="stylesheet.xsl" type="text/css"?&gt</code>
+     * will have <code>name</code> == <code>"xml-stylesheet"</code> and two attributes: <code>href</code> and <code>type</code>.
+     */
+    private static class ProcessingInstruction {
+
+        private Map<String, String> attributes = new HashMap<String, String>();
+        private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\s(.[^=\\s]*)\\s?=\\s?\"(.[^\"]*)\"");
+        private static final Pattern NAME_PATTERN = Pattern.compile("^(.[^\\s\\?>]*)");
+        private String name;
+
+        public ProcessingInstruction(String text) throws IOException {
+            final Matcher nameMatcher = NAME_PATTERN.matcher(text);
+            if (!nameMatcher.find()) {
+                throw new IOException("Malformed processing instruction: " + text);
+            }
+
+            this.name = nameMatcher.group(1);
+            final Matcher attributeMatcher = ATTRIBUTE_PATTERN.matcher(text);
+            while (attributeMatcher.find()) {
+                attributes.put(attributeMatcher.group(1), attributeMatcher.group(2));
+            }
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getAttribute(String key) {
+            return this.attributes.get(key);
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
new file mode 100644
index 0000000..e4debee
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/ZipReader.java
@@ -0,0 +1,114 @@
+/*
+ * 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.contentloader.internal.readers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.input.CloseShieldInputStream;
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+import org.apache.sling.jcr.contentloader.internal.ContentReader;
+import org.apache.sling.jcr.contentloader.internal.ImportProvider;
+
+
+/**
+ * The <code>ZipReader</code> TODO
+ *
+ * @since 2.0.4
+ */
+public class ZipReader implements ContentReader {
+
+    private static final String NT_FOLDER = "nt:folder";
+
+    public static final ImportProvider ZIP_PROVIDER = new ImportProvider() {
+        private ZipReader zipReader;
+
+        public ContentReader getReader() {
+            if (zipReader == null) {
+                zipReader = new ZipReader(false);
+            }
+            return zipReader;
+        }
+    };
+
+    public static final ImportProvider JAR_PROVIDER = new ImportProvider() {
+        private ZipReader zipReader;
+
+        public ContentReader getReader() {
+            if (zipReader == null) {
+                zipReader = new ZipReader(true);
+            }
+            return zipReader;
+        }
+    };
+
+    /** Is this a jar reader? */
+    //private final boolean jarReader;
+
+    public ZipReader(boolean jarReader) {
+        //this.jarReader = jarReader;
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.net.URL, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     */
+    public void parse(java.net.URL url, ContentCreator creator)
+    throws IOException, RepositoryException {
+        InputStream ins = null;
+        try {
+            ins = url.openStream();
+            creator.createNode(null, NT_FOLDER, null);
+            final ZipInputStream zis = new ZipInputStream(ins);
+            ZipEntry entry;
+            do {
+                entry = zis.getNextEntry();
+                if ( entry != null ) {
+                    if ( !entry.isDirectory() ) {
+                        String name = entry.getName();
+                        int pos = name.lastIndexOf('/');
+                        if ( pos != -1 ) {
+                            creator.switchCurrentNode(name.substring(0, pos), NT_FOLDER);
+                        }
+                        creator.createFileAndResourceNode(name, new CloseShieldInputStream(zis), null, entry.getTime());
+                        creator.finishNode();
+                        creator.finishNode();
+                        if ( pos != -1 ) {
+                            creator.finishNode();
+                        }
+                    }
+                    zis.closeEntry();
+                }
+
+            } while ( entry != null );
+            creator.finishNode();
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+    }
+
+}
diff --git a/src/main/resources/META-INF/DISCLAIMER b/src/main/resources/META-INF/DISCLAIMER
new file mode 100644
index 0000000..90850c2
--- /dev/null
+++ b/src/main/resources/META-INF/DISCLAIMER
@@ -0,0 +1,7 @@
+Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF),
+sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted
+projects until a further review indicates that the infrastructure, communications,
+and decision making process have stabilized in a manner consistent with other
+successful ASF projects. While incubation status is not necessarily a reflection of
+the completeness or stability of the code, it does indicate that the project has yet
+to be fully endorsed by the ASF.
\ No newline at end of file
diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..1a45730
--- /dev/null
+++ b/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,232 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+APACHE SLING SUBCOMPONENTS:
+
+Apache Sling includes subcomponents with separate copyright notices and
+license terms. Your use of these subcomponents is subject to the terms
+and conditions of the following licenses.
+
+kXML parser
+
+   Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
+
+   Permission is hereby granted, free of charge, to any person obtaining
+   a copy of this software and associated documentation files (the
+   "Software"), to deal in the Software without restriction, including
+   without limitation the rights to use, copy, modify, merge, publish,
+   distribute, sublicense, and/or sell copies of the Software, and to permit
+   persons to whom the Software is furnished to do so, subject to the
+   following conditions:
+
+   The above copyright notice and this permission notice shall be included in
+   all copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+   IN THE SOFTWARE. 
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
new file mode 100644
index 0000000..da7213c
--- /dev/null
+++ b/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,11 @@
+Apache Sling Initial Content Loader
+Copyright 2008-2009 The Apache Software Foundation
+
+Apache Sling is based on source code originally developed 
+by Day Software (http://www.day.com/).
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software from http://kxml.sourceforge.net.
+Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany.
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
new file mode 100644
index 0000000..d403145
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.runner.RunWith;
+
+@RunWith(JMock.class)
+public class JsonReaderTest {
+
+    JsonReader jsonReader;
+
+    Mockery mockery = new JUnit4Mockery();
+
+    ContentCreator creator;
+
+    Sequence mySequence;
+
+    @org.junit.Before public void setUp() throws Exception {
+        this.jsonReader = new JsonReader();
+        this.creator = this.mockery.mock(ContentCreator.class);
+        this.mySequence = this.mockery.sequence("my-sequence");
+    }
+
+    @org.junit.After public void tearDown() throws Exception {
+        this.jsonReader = null;
+    }
+
+    @org.junit.Test public void testEmptyObject() throws Exception {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse("");
+    }
+
+    @org.junit.Test public void testEmpty() throws IOException, RepositoryException {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse("{}");
+    }
+
+    @org.junit.Test public void testDefaultPrimaryNodeTypeWithSurroundWhitespace() throws Exception {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        String json = "     {  }     ";
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testDefaultPrimaryNodeTypeWithoutEnclosingBracesWithSurroundWhitespace() throws Exception {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        String json = "             ";
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testExplicitePrimaryNodeType() throws Exception {
+        final String type = "xyz:testType";
+        String json = "{ \"jcr:primaryType\": \"" + type + "\" }";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, type, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testMixinNodeTypes1() throws Exception {
+        final String[] mixins = new String[]{ "xyz:mix1" };
+        String json = "{ \"jcr:mixinTypes\": " + this.toJsonArray(mixins) + "}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, mixins); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testMixinNodeTypes2() throws Exception {
+        final String[] mixins = new String[]{ "xyz:mix1", "abc:mix2" };
+        String json = "{ \"jcr:mixinTypes\": " + this.toJsonArray(mixins) + "}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, mixins); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesEmpty() throws Exception {
+        String json = "{ \"property\": \"\"}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("property", PropertyType.STRING, ""); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesSingleValue() throws Exception {
+        String json = "{ \"p1\": \"v1\"}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, "v1"); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesTwoSingleValue() throws Exception {
+        String json = "{ \"p1\": \"v1\", \"p2\": \"v2\"}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, "v1"); inSequence(mySequence);
+            allowing(creator).createProperty("p2", PropertyType.STRING, "v2"); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesMultiValue() throws Exception {
+        String json = "{ \"p1\": [\"v1\"]}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, new String[] {"v1"}); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesMultiValueEmpty() throws Exception {
+        String json = "{ \"p1\": []}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, new String[0]); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testChild() throws Exception {
+        String json = "{ " +
+                      " c1 : {}" +
+                      "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testChildWithMixin() throws Exception {
+        String json = "{ " +
+        " c1 : {" +
+              "\"jcr:mixinTypes\" : [\"xyz:TestType\"]" +
+              "}" +
+        "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, new String[] {"xyz:TestType"}); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testTwoChildren() throws Exception {
+        String json = "{ " +
+        " c1 : {}," +
+        " c2 : {}" +
+        "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).createNode("c2", null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testChildWithProperty() throws Exception {
+        String json = "{ " +
+        " c1 : {" +
+        "      c1p1 : \"v1\"" +
+              "}" +
+        "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, null); inSequence(mySequence);
+            allowing(creator).createProperty("c1p1", PropertyType.STRING, "v1");
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    //---------- internal helper ----------------------------------------------
+
+    private void parse(String json) throws IOException, RepositoryException {
+        String charSet = "ISO-8859-1";
+        json = "#" + charSet + "\r\n" + json;
+        InputStream ins = new ByteArrayInputStream(json.getBytes(charSet));
+        this.jsonReader.parse(ins, this.creator);
+    }
+
+    private JSONArray toJsonArray(String[] array) throws JSONException {
+        return new JSONArray(Arrays.asList(array));
+    }
+}
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
new file mode 100644
index 0000000..554c5ea
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.contentloader.internal.readers;
+
+import junit.framework.TestCase;
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+
+import javax.jcr.RepositoryException;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+
+/**
+ * 	Test the XmlReader with an XSLT transform
+ */
+public class XmlReaderTest extends TestCase {
+
+    public void testXmlReader() throws Exception {
+        XmlReader reader = new XmlReader();
+        File file = new File("src/test/resources/reader/sample.xml");
+        final URL testdata  = file.toURI().toURL();
+        final MockContentCreator creator = new MockContentCreator();
+        reader.parse(testdata, creator);
+        assertEquals("Did not create expected number of nodes", 1, creator.size());
+    }
+
+    @SuppressWarnings("serial")
+	private static class MockContentCreator extends ArrayList<String> implements ContentCreator {
+
+        public MockContentCreator() {
+        }
+
+        public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+            this.add(name);
+        }
+
+        public void finishNode() throws RepositoryException {
+        }
+
+        public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        }
+
+        public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        }
+
+        public void createProperty(String name, Object value) throws RepositoryException {
+        }
+
+        public void createProperty(String name, Object[] values) throws RepositoryException {
+        }
+
+        public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        }
+
+        public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+            return true;
+        }
+    }
+}
diff --git a/src/test/resources/reader/sample.xml b/src/test/resources/reader/sample.xml
new file mode 100644
index 0000000..b0374d1
--- /dev/null
+++ b/src/test/resources/reader/sample.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+ * 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.
+-->
+
+
+<!-- 
+    This needs to be transformed by sample.xsl
+    to create valid node/propertie structures
+    that the standard XML importer can accept.
+-->
+<?xml-stylesheet type="text/xsl" href="sample.xsl"?>
+<pages>
+    <page name="firstpage">
+        <content>some text</content>
+    </page>
+</pages>
\ No newline at end of file
diff --git a/src/test/resources/reader/sample.xsl b/src/test/resources/reader/sample.xsl
new file mode 100644
index 0000000..e9072cc
--- /dev/null
+++ b/src/test/resources/reader/sample.xsl
@@ -0,0 +1,58 @@
+<?xml version="1.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.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+    <xsl:output indent="yes" />
+
+    <xsl:template match="/">
+        <xsl:apply-templates select="pages/page"/>
+    </xsl:template>
+
+    <xsl:template match="page">
+        <node>
+            <name><xsl:value-of select="@name"/></name>
+            <primaryNodeType>nt:unstructured</primaryNodeType>
+            <mixinNodeTypes/>
+            <properties>
+                <xsl:call-template name="property">
+                    <xsl:with-param name="name">content</xsl:with-param>
+                    <xsl:with-param name="value"><xsl:value-of select="content"/></xsl:with-param>
+                </xsl:call-template>
+                <xsl:call-template name="property">
+                    <xsl:with-param name="name">sling:resourceType</xsl:with-param>
+                    <xsl:with-param name="value">page</xsl:with-param>
+                </xsl:call-template>
+            </properties>
+        </node>
+    </xsl:template>
+
+    <xsl:template name="property">
+        <xsl:param name="name"/>
+        <xsl:param name="value"/>
+        <property>
+            <name><xsl:value-of select="$name"/></name>
+            <type>String</type>
+            <value><xsl:value-of select="$value"/></value>
+        </property>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file

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

[sling-org-apache-sling-jcr-contentloader] 16/36: SLING-553 Allow references and paths to be loaded from JSON through JsonReader. Applied patch by Bryce Ewing.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit ef74c9d809d9cfc3842a8b9c7743dd0ac472733a
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jul 2 14:36:49 2008 +0000

    SLING-553 Allow references and paths to be loaded from JSON through JsonReader. Applied patch by Bryce Ewing.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@673405 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentLoader.java  | 66 +++++++++++++++++++++-
 .../jcr/contentloader/internal/JsonReader.java     | 24 ++++++--
 2 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
index e89389c..233488f 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -52,6 +52,7 @@ public class ContentLoader implements ContentCreator {
 
     /** Delayed references during content loading for the reference property. */
     private final Map<String, List<String>> delayedReferences = new HashMap<String, List<String>>();
+    private final Map<String, String[]> delayedMultipleReferences = new HashMap<String, String[]>();
 
     private String defaultRootName;
 
@@ -231,7 +232,7 @@ public class ContentLoader implements ContentCreator {
         if ( propertyType == PropertyType.REFERENCE ) {
             // need to resolve the reference
             String propPath = node.getPath() + "/" + name;
-            String uuid = getUUID(node.getSession(), propPath, value);
+            String uuid = getUUID(node.getSession(), propPath, getAbsPath(node, value));
             if (uuid != null) {
                 node.setProperty(name, uuid, propertyType);
             }
@@ -262,7 +263,26 @@ public class ContentLoader implements ContentCreator {
             && !node.getProperty(name).isNew()) {
             return;
         }
-        node.setProperty(name, values, propertyType);
+        if ( propertyType == PropertyType.REFERENCE ) {
+            String propPath = node.getPath() + "/" + name;
+
+            boolean hasAll = true;
+            String[] uuids = new String[values.length];
+            String[] uuidOrPaths = new String[values.length];
+            for (int i = 0; i < values.length; i++) {
+                uuids[i] = getUUID(node.getSession(), propPath, getAbsPath(node, values[i]));
+                uuidOrPaths[i] = uuids[i] != null ? uuids[i] : getAbsPath(node, values[i]);
+                if (uuids[i] == null) hasAll = false;
+            }
+
+            node.setProperty(name, uuids, propertyType);
+
+            if (!hasAll) {
+                delayedMultipleReferences.put(propPath, uuidOrPaths);
+            }
+        } else {
+            node.setProperty(name, values, propertyType);
+        }
     }
 
     protected Value createValue(final ValueFactory factory, Object value) {
@@ -343,6 +363,21 @@ public class ContentLoader implements ContentCreator {
         resolveReferences(node);
     }
 
+    private String getAbsPath(Node node, String path) throws RepositoryException {
+        if (path.startsWith("/")) return path;
+
+        while (path.startsWith("../")) {
+            path = path.substring(3);
+            node = node.getParent();
+        }
+
+        while (path.startsWith("./")) {
+            path = path.substring(2);
+        }
+
+        return node.getPath() + "/" + path;
+    }
+
     private String getUUID(Session session, String propPath,
                           String referencePath)
     throws RepositoryException {
@@ -386,7 +421,32 @@ public class ContentLoader implements ContentCreator {
             String name = getName(property);
             Node parentNode = getParentNode(session, property);
             if (parentNode != null) {
-                parentNode.setProperty(name, uuid, PropertyType.REFERENCE);
+                if (parentNode.hasProperty(name) && parentNode.getProperty(name).getDefinition().isMultiple()) {
+                    boolean hasAll = true;
+                    String[] uuidOrPaths = delayedMultipleReferences.get(property);
+                    String[] uuids = new String[uuidOrPaths.length];
+                    for (int i = 0; i < uuidOrPaths.length; i++) {
+                        // is the reference still a path
+                        if (uuidOrPaths[i].startsWith("/")) {
+                            if (uuidOrPaths[i].equals(node.getPath())) {
+                                uuidOrPaths[i] = uuid;
+                                uuids[i] = uuid;
+                            } else {
+                                uuids[i] = null;
+                                hasAll = false;
+                            }
+                        } else {
+                            uuids[i] = uuidOrPaths[i];
+                        }
+                    }
+                    parentNode.setProperty(name, uuids, PropertyType.REFERENCE);
+
+                    if (hasAll) {
+                        delayedMultipleReferences.remove(property);
+                    }
+                } else {
+                    parentNode.setProperty(name, uuid, PropertyType.REFERENCE);
+                }
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
index 26fe520..78a7f3e 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
@@ -38,6 +38,9 @@ import org.apache.sling.commons.json.JSONObject;
  */
 class JsonReader implements ContentReader {
 
+    private static final String REFERENCE = "jcr:reference:";
+    private static final String PATH = "jcr:path:";
+
     private static final Set<String> ignoredNames = new HashSet<String>();
     static {
         ignoredNames.add("jcr:primaryType");
@@ -128,32 +131,41 @@ class JsonReader implements ContentReader {
                 for (int i = 0; i < array.length(); i++) {
                     values[i] = array.get(i).toString();
                 }
-                final int propertyType = getType(values[0]);
-                contentCreator.createProperty(name, propertyType, values);
+                final int propertyType = getType(name, values[0]);
+                contentCreator.createProperty(getName(name), propertyType, values);
             } else {
-                contentCreator.createProperty(name, PropertyType.STRING, new String[0]);
+                contentCreator.createProperty(getName(name), PropertyType.STRING, new String[0]);
             }
 
         } else {
             // single value
-            final int propertyType = getType(value);
-            contentCreator.createProperty(name, propertyType, String.valueOf(value));
+            final int propertyType = getType(name, value);
+            contentCreator.createProperty(getName(name), propertyType, value.toString());
         }
     }
 
-    protected int getType(Object object) {
+    protected int getType(String name, Object object) {
         if (object instanceof Double || object instanceof Float) {
             return PropertyType.DOUBLE;
         } else if (object instanceof Number) {
             return PropertyType.LONG;
         } else if (object instanceof Boolean) {
             return PropertyType.BOOLEAN;
+        } else if (object instanceof String) {
+            if (name.startsWith(REFERENCE)) return PropertyType.REFERENCE;
+            if (name.startsWith(PATH)) return PropertyType.PATH;
         }
 
         // fall back to default
         return PropertyType.STRING;
     }
 
+    protected String getName(String name) {
+        if (name.startsWith(REFERENCE)) return name.substring(REFERENCE.length());
+        if (name.startsWith(PATH)) return name.substring(PATH.length());
+        return name;
+    }
+
     private String toString(InputStream ins) throws IOException {
         if (!ins.markSupported()) {
             ins = new BufferedInputStream(ins);

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

[sling-org-apache-sling-jcr-contentloader] 10/36: SLING-548: Add methods for setting properties from objects.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit d05e8d207572e691e886985c993fef26362a9543
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 12:37:55 2008 +0000

    SLING-548: Add methods for setting properties from objects.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670545 13f79535-47bb-0310-9956-ffa450edef68
---
 .../jcr/contentloader/internal/ContentCreator.java | 56 +++++++++++++++++
 .../jcr/contentloader/internal/ContentLoader.java  | 73 ++++++++++++++++++++++
 2 files changed, 129 insertions(+)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index ade7562..bd72de7 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -20,23 +20,79 @@ package org.apache.sling.jcr.contentloader.internal;
 
 import javax.jcr.RepositoryException;
 
+/**
+ * The <code>ContentCreator</code>
+ * is used by the {@link ContentReader} to create the actual content.
+ */
 interface ContentCreator {
 
+    /**
+     * Create a new node.
+     * To add properties to this node, one of the createProperty() methods
+     * should be called.
+     * To add child nodes, this method should be called to create a new child node.
+     * If all properties and child nodes have been added {@link #finishNode()} must be called.
+     *
+     * @param name The name of the node.
+     * @param primaryNodeType The primary node type or null.
+     * @param mixinNodeTypes The mixin node types or null.
+     * @throws RepositoryException If anything goes wrong.
+     */
     void createNode(String name,
                     String primaryNodeType,
                     String[] mixinNodeTypes)
     throws RepositoryException;
 
+    /**
+     * Indicates that a node is finished.
+     * The parent node of the current node becomes the current node.
+     * @throws RepositoryException
+     */
     void finishNode()
     throws RepositoryException;
 
+    /**
+     * Create a new property to the current node.
+     * @param name The property name.
+     * @param propertyType The type of the property.
+     * @param value The string value.
+     * @throws RepositoryException
+     */
     void createProperty(String name,
                         int    propertyType,
                         String value)
     throws RepositoryException;
 
+    /**
+     * Create a new multi value property to the current node.
+     * @param name The property name.
+     * @param propertyType The type of the property.
+     * @param values The string values.
+     * @throws RepositoryException
+     */
     void createProperty(String name,
                         int    propertyType,
                         String[] values)
     throws RepositoryException;
+
+    /**
+     * Add a new property to the current node.
+     * @param name The property name.
+     * @param value The value.
+     * @throws RepositoryException
+     */
+    void createProperty(String name,
+                        Object value)
+    throws RepositoryException;
+
+    /**
+     * Add a new multi value property to the current node.
+     * @param name The property name.
+     * @param propertyType The type of the property.
+     * @param values The values.
+     * @throws RepositoryException
+     */
+    void createProperty(String name,
+                        Object[] values)
+    throws RepositoryException;
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
index 3e0a629..415b5a7 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -18,7 +18,10 @@
  */
 package org.apache.sling.jcr.contentloader.internal;
 
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -29,6 +32,8 @@ import javax.jcr.Node;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
 
 /**
  * The <code>ContentLoader</code> creates the nodes and properties.
@@ -194,6 +199,74 @@ public class ContentLoader implements ContentCreator {
         node.setProperty(name, values, propertyType);
     }
 
+    protected Value createValue(final ValueFactory factory, Object value) {
+        if ( value == null ) {
+            return null;
+        }
+        if ( value instanceof Long ) {
+            return factory.createValue((Long)value);
+        } else if ( value instanceof Date ) {
+            final Calendar c = Calendar.getInstance();
+            c.setTime((Date)value);
+            return factory.createValue(c);
+        } else if ( value instanceof Calendar ) {
+            return factory.createValue((Calendar)value);
+        } else if ( value instanceof Double ) {
+            return factory.createValue((Double)value);
+        } else if ( value instanceof Boolean ) {
+            return factory.createValue((Boolean)value);
+        } else if ( value instanceof InputStream ) {
+            return factory.createValue((InputStream)value);
+        } else {
+            return factory.createValue(value.toString());
+        }
+
+    }
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, java.lang.Object)
+     */
+    public void createProperty(String name, Object value)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+        if ( value == null ) {
+            if ( node.hasProperty(name) ) {
+                node.getProperty(name).remove();
+            }
+        } else {
+            final Value jcrValue = this.createValue(node.getSession().getValueFactory(), value);
+            node.setProperty(name, jcrValue);
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, java.lang.Object[])
+     */
+    public void createProperty(String name, Object[] values)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+        if ( values == null || values.length == 0 ) {
+            if ( node.hasProperty(name) ) {
+                node.getProperty(name).remove();
+            }
+        } else {
+            final Value[] jcrValues = new Value[values.length];
+            for(int i = 0; i < values.length; i++) {
+                jcrValues[i] = this.createValue(node.getSession().getValueFactory(), values[i]);
+            }
+            node.setProperty(name, jcrValues);
+        }
+    }
+
     /**
      * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#finishNode()
      */

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

[sling-org-apache-sling-jcr-contentloader] 22/36: SLING-808 : Increase version to 5-incubator-SNAPSHOT

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 921136cb73bb91cf3a7ad8ceda6ae20894f0fb11
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jan 5 11:07:35 2009 +0000

    SLING-808 : Increase version to 5-incubator-SNAPSHOT
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@731526 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 1ad41c0..7da9926 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>4-incubator-SNAPSHOT</version>
+        <version>5-incubator-SNAPSHOT</version>
         <relativePath>../../parent/pom.xml</relativePath>
     </parent>
 

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

[sling-org-apache-sling-jcr-contentloader] 18/36: Create sling:Folder as intermediate node.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 7fe9fc0d5b32d1a509e1278f31ef3cf179dfa5bd
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jul 22 17:04:48 2008 +0000

    Create sling:Folder as intermediate node.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@678806 13f79535-47bb-0310-9956-ffa450edef68
---
 src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 46af1f8..96e771e 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -567,7 +567,7 @@ public class Loader {
             while ( st.hasMoreTokens() ) {
                 final String name = st.nextToken();
                 if ( !currentNode.hasNode(name) ) {
-                    currentNode.addNode(name, "nt:folder");
+                    currentNode.addNode(name, "sling:Folder");
                 }
                 currentNode = currentNode.getNode(name);
             }

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

[sling-org-apache-sling-jcr-contentloader] 28/36: SLING-865 : Move to bundles

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 01f89906e7dd8b897c3256d59e3c8718fc97f510
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sun Feb 22 13:43:19 2009 +0000

    SLING-865 : Move to bundles
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader@746677 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-contentloader] 29/36: SLING-865 : Adjust reactor pom and svn information in each module.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 4146903d56cdda83cc3c3b91bd32c975541c9f1b
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Sun Feb 22 14:22:36 2009 +0000

    SLING-865 : Adjust reactor pom and svn information in each module.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader@746707 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index 810642f..a9521f4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,9 +36,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader</developerConnection>
-        <url>http://svn.apache.org/viewvc/incubator/sling/trunk/jcr/contentloader</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader</developerConnection>
+        <url>http://svn.apache.org/viewvc/incubator/sling/trunk/bundles/jcr/contentloader</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-contentloader] 06/36: SLING-548: Rewrite of content loading from creating mock node/property objects to event based loading. This mechanism allows to create nodes and properties on the fly while reading from the input stream. (ViP) Enable tests again (they were in the wrong directory)

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 899bdff16a60cf4201e3c3e17c79b9c6fe653121
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 09:16:03 2008 +0000

    SLING-548: Rewrite of content loading from creating mock node/property objects to event based loading. This mechanism allows to create nodes and properties on the fly while reading from the input stream. (ViP)
    Enable tests again (they were in the wrong directory)
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670488 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   6 +-
 .../{NodeReader.java => ContentCreator.java}       |  25 +-
 .../jcr/contentloader/internal/ContentLoader.java  | 294 ++++++++++++++++
 .../{NodeReader.java => ContentReader.java}        |  17 +-
 .../jcr/contentloader/internal/ImportProvider.java |   2 +-
 .../jcr/contentloader/internal/JsonReader.java     |  80 +++--
 .../sling/jcr/contentloader/internal/Loader.java   | 356 +++++---------------
 .../contentloader/internal/NodeDescription.java    | 153 ---------
 .../jcr/contentloader/internal/PathEntry.java      |  50 ++-
 .../internal/PropertyDescription.java              | 119 -------
 .../jcr/contentloader/internal/XmlReader.java      | 202 ++++++++---
 .../jcr/contentloader/internal/JsonReaderTest.java | 374 ---------------------
 .../jcr/contentloader/internal/JsonReaderTest.java | 252 ++++++++++++++
 13 files changed, 913 insertions(+), 1017 deletions(-)

diff --git a/pom.xml b/pom.xml
index 16fdfc8..3705a01 100644
--- a/pom.xml
+++ b/pom.xml
@@ -123,10 +123,14 @@
             <artifactId>kxml2</artifactId>
             <scope>provided</scope>
         </dependency>
-        
+    <!-- Testing -->        
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.jmock</groupId>
+            <artifactId>jmock-junit4</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
similarity index 59%
copy from src/main/java/org/apache/sling/jcr/contentloader/internal/NodeReader.java
copy to src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
index 2653114..ade7562 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentCreator.java
@@ -18,14 +18,25 @@
  */
 package org.apache.sling.jcr.contentloader.internal;
 
-import java.io.IOException;
-import java.io.InputStream;
+import javax.jcr.RepositoryException;
 
-/**
- * The <code>NodeReader</code> TODO
- */
-interface NodeReader {
+interface ContentCreator {
+
+    void createNode(String name,
+                    String primaryNodeType,
+                    String[] mixinNodeTypes)
+    throws RepositoryException;
+
+    void finishNode()
+    throws RepositoryException;
 
-    NodeDescription parse(InputStream ins) throws IOException;
+    void createProperty(String name,
+                        int    propertyType,
+                        String value)
+    throws RepositoryException;
 
+    void createProperty(String name,
+                        int    propertyType,
+                        String[] values)
+    throws RepositoryException;
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
new file mode 100644
index 0000000..3e0a629
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentLoader.java
@@ -0,0 +1,294 @@
+/*
+ * 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.contentloader.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * The <code>ContentLoader</code> creates the nodes and properties.
+ */
+public class ContentLoader implements ContentCreator {
+
+    private PathEntry configuration;
+
+    private final Stack<Node> parentNodeStack = new Stack<Node>();
+
+    /** The list of versionables. */
+    private final List<Node> versionables = new ArrayList<Node>();
+
+    /** Delayed references during content loading for the reference property. */
+    private final Map<String, List<String>> delayedReferences = new HashMap<String, List<String>>();
+
+    private String defaultRootName;
+
+    private Node rootNode;
+
+    private boolean isRootNodeImport;
+
+    /**
+     * Initialize this component.
+     * If the defaultRootName is null, we are in ROOT_NODE import mode.
+     * @param pathEntry
+     * @param parentNode
+     * @param defaultRootName
+     */
+    public void init(final PathEntry pathEntry,
+                     final Node parentNode,
+                     final String defaultRootName) {
+        this.configuration = pathEntry;
+        this.parentNodeStack.clear();
+        this.parentNodeStack.push(parentNode);
+        this.defaultRootName = defaultRootName;
+        this.rootNode = null;
+        isRootNodeImport = defaultRootName == null;
+    }
+
+    public List<Node> getVersionables() {
+        return this.versionables;
+    }
+
+    public void clear() {
+        this.versionables.clear();
+    }
+
+    public Node getRootNode() {
+        return this.rootNode;
+    }
+
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createNode(java.lang.String, java.lang.String, java.lang.String[])
+     */
+    public void createNode(String name,
+                           String primaryNodeType,
+                           String[] mixinNodeTypes)
+    throws RepositoryException {
+        final Node parentNode = this.parentNodeStack.peek();
+        if ( name == null ) {
+            if ( this.parentNodeStack.size() > 1 ) {
+                throw new RepositoryException("Node needs to have a name.");
+            }
+            name = this.defaultRootName;
+        }
+
+        // if we are in root node import mode, we don't create the root top level node!
+        if ( !isRootNodeImport || this.parentNodeStack.size() > 1 ) {
+            // if node already exists but should be overwritten, delete it
+            if (this.configuration.isOverwrite() && parentNode.hasNode(name)) {
+                parentNode.getNode(name).remove();
+            }
+
+            // ensure repository node
+            Node node;
+            if (parentNode.hasNode(name)) {
+
+                // use existing node
+                node = parentNode.getNode(name);
+
+            } else if (primaryNodeType == null) {
+
+                // node explicit node type, use repository default
+                node = parentNode.addNode(name);
+
+            } else {
+
+                // explicit primary node type
+                node = parentNode.addNode(name, primaryNodeType);
+            }
+
+            // ammend mixin node types
+            if (mixinNodeTypes != null) {
+                for (final String mixin : mixinNodeTypes) {
+                    if (!node.isNodeType(mixin)) {
+                        node.addMixin(mixin);
+                    }
+                }
+            }
+
+            // check if node is versionable
+            final boolean addToVersionables = this.configuration.isCheckin()
+                                        && node.isNodeType("mix:versionable");
+            if ( addToVersionables ) {
+                this.versionables.add(node);
+            }
+
+            this.parentNodeStack.push(node);
+            if ( this.rootNode == null ) {
+                this.rootNode = node;
+            }
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, int, java.lang.String)
+     */
+    public void createProperty(String name, int propertyType, String value)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+
+        if ( propertyType == PropertyType.REFERENCE ) {
+            // need to resolve the reference
+            String propPath = node.getPath() + "/" + name;
+            String uuid = getUUID(node.getSession(), propPath, value);
+            if (uuid != null) {
+                node.setProperty(name, uuid, propertyType);
+            }
+
+        } else if ("jcr:isCheckedOut".equals(name)) {
+
+            // don't try to write the property but record its state
+            // for later checkin if set to false
+            final boolean checkedout = Boolean.valueOf(value);
+            if (!checkedout) {
+                if ( !this.versionables.contains(node) ) {
+                    this.versionables.add(node);
+                }
+            }
+        } else {
+            node.setProperty(name, value, propertyType);
+        }
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#createProperty(java.lang.String, int, java.lang.String[])
+     */
+    public void createProperty(String name, int propertyType, String[] values)
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.peek();
+        // check if the property already exists, don't overwrite it in this case
+        if (node.hasProperty(name)
+            && !node.getProperty(name).isNew()) {
+            return;
+        }
+        node.setProperty(name, values, propertyType);
+    }
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentCreator#finishNode()
+     */
+    public void finishNode()
+    throws RepositoryException {
+        final Node node = this.parentNodeStack.pop();
+        // resolve REFERENCE property values pointing to this node
+        resolveReferences(node);
+    }
+
+    private String getUUID(Session session, String propPath,
+                          String referencePath)
+    throws RepositoryException {
+        if (session.itemExists(referencePath)) {
+            Item item = session.getItem(referencePath);
+            if (item.isNode()) {
+                Node refNode = (Node) item;
+                if (refNode.isNodeType("mix:referenceable")) {
+                    return refNode.getUUID();
+                }
+            }
+        } else {
+            // not existing yet, keep for delayed setting
+            List<String> current = delayedReferences.get(referencePath);
+            if (current == null) {
+                current = new ArrayList<String>();
+                delayedReferences.put(referencePath, current);
+            }
+            current.add(propPath);
+        }
+
+        // no UUID found
+        return null;
+    }
+
+    private void resolveReferences(Node node) throws RepositoryException {
+        List<String> props = delayedReferences.remove(node.getPath());
+        if (props == null || props.size() == 0) {
+            return;
+        }
+
+        // check whether we can set at all
+        if (!node.isNodeType("mix:referenceable")) {
+            return;
+        }
+
+        Session session = node.getSession();
+        String uuid = node.getUUID();
+
+        for (String property : props) {
+            String name = getName(property);
+            Node parentNode = getParentNode(session, property);
+            if (parentNode != null) {
+                parentNode.setProperty(name, uuid, PropertyType.REFERENCE);
+            }
+        }
+    }
+
+    /**
+     * Gets the name part of the <code>path</code>. The name is
+     * the part of the path after the last slash (or the complete path if no
+     * slash is contained).
+     *
+     * @param path The path from which to extract the name part.
+     * @return The name part.
+     */
+    private String getName(String path) {
+        int lastSlash = path.lastIndexOf('/');
+        String name = (lastSlash < 0) ? path : path.substring(lastSlash + 1);
+
+        return name;
+    }
+
+    private Node getParentNode(Session session, String path)
+            throws RepositoryException {
+        int lastSlash = path.lastIndexOf('/');
+
+        // not an absolute path, cannot find parent
+        if (lastSlash < 0) {
+            return null;
+        }
+
+        // node below root
+        if (lastSlash == 0) {
+            return session.getRootNode();
+        }
+
+        // item in the hierarchy
+        path = path.substring(0, lastSlash);
+        if (!session.itemExists(path)) {
+            return null;
+        }
+
+        Item item = session.getItem(path);
+        return (item.isNode()) ? (Node) item : null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
similarity index 62%
rename from src/main/java/org/apache/sling/jcr/contentloader/internal/NodeReader.java
rename to src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
index 2653114..b36fa7d 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ContentReader.java
@@ -21,11 +21,22 @@ package org.apache.sling.jcr.contentloader.internal;
 import java.io.IOException;
 import java.io.InputStream;
 
+import javax.jcr.RepositoryException;
+
 /**
- * The <code>NodeReader</code> TODO
+ * The <code>ContentReader</code>
+ * A content reader is provided by an {@link ImportProvider}.
  */
-interface NodeReader {
+interface ContentReader {
 
-    NodeDescription parse(InputStream ins) throws IOException;
+    /**
+     * Read the content from the input stream and create the
+     * content throught the provided content creator.
+     * The content reader should not close the input stream, this is
+     * done by the calling component!
+     * @param ins The input stream.
+     * @throws IOException
+     */
+    void parse(InputStream ins, ContentCreator creator) throws IOException, RepositoryException;
 
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
index e28fd55..2cf8340 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ImportProvider.java
@@ -22,6 +22,6 @@ import java.io.IOException;
 
 interface ImportProvider {
 
-    NodeReader getReader() throws IOException;
+    ContentReader getReader() throws IOException;
     
 }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
index ef5c4b4..26fe520 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/JsonReader.java
@@ -26,6 +26,7 @@ import java.util.HashSet;
 import java.util.Set;
 
 import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
 
 import org.apache.sling.commons.json.JSONArray;
 import org.apache.sling.commons.json.JSONException;
@@ -35,7 +36,7 @@ import org.apache.sling.commons.json.JSONObject;
 /**
  * The <code>JsonReader</code> TODO
  */
-class JsonReader implements NodeReader {
+class JsonReader implements ContentReader {
 
     private static final Set<String> ignoredNames = new HashSet<String>();
     static {
@@ -52,7 +53,7 @@ class JsonReader implements NodeReader {
     static final ImportProvider PROVIDER = new ImportProvider() {
         private JsonReader jsonReader;
 
-        public NodeReader getReader() {
+        public ContentReader getReader() {
             if (jsonReader == null) {
                 jsonReader = new JsonReader();
             }
@@ -60,7 +61,11 @@ class JsonReader implements NodeReader {
         }
     };
 
-    public NodeDescription parse(InputStream ins) throws IOException {
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.io.InputStream, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     */
+    public void parse(InputStream ins, ContentCreator contentCreator)
+    throws IOException, RepositoryException {
         try {
             String jsonString = toString(ins).trim();
             if (!jsonString.startsWith("{")) {
@@ -68,92 +73,85 @@ class JsonReader implements NodeReader {
             }
 
             JSONObject json = new JSONObject(jsonString);
-            return this.createNode(null, json);
+            this.createNode(null, json, contentCreator);
 
         } catch (JSONException je) {
             throw (IOException) new IOException(je.getMessage()).initCause(je);
         }
     }
 
-    protected NodeDescription createNode(String name, JSONObject obj) throws JSONException {
-        NodeDescription node = new NodeDescription();
-        node.setName(name);
-
-        Object primaryType = obj.opt("jcr:primaryType");
-        if (primaryType != null) {
-            node.setPrimaryNodeType(String.valueOf(primaryType));
+    protected void createNode(String name, JSONObject obj, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
+        Object primaryTypeObj = obj.opt("jcr:primaryType");
+        String primaryType = null;
+        if (primaryTypeObj != null) {
+            primaryType = String.valueOf(primaryTypeObj);
         }
 
+        String[] mixinTypes = null;
         Object mixinsObject = obj.opt("jcr:mixinTypes");
         if (mixinsObject instanceof JSONArray) {
             JSONArray mixins = (JSONArray) mixinsObject;
+            mixinTypes = new String[mixins.length()];
             for (int i = 0; i < mixins.length(); i++) {
-                node.addMixinNodeType(mixins.getString(i));
+                mixinTypes[i] = mixins.getString(i);
             }
         }
 
+        contentCreator.createNode(name, primaryType, mixinTypes);
+
         // add properties and nodes
         JSONArray names = obj.names();
         for (int i = 0; names != null && i < names.length(); i++) {
-            String n = names.getString(i);
+            final String n = names.getString(i);
             // skip well known objects
             if (!ignoredNames.contains(n)) {
                 Object o = obj.get(n);
                 if (o instanceof JSONObject) {
-                    NodeDescription child = this.createNode(n, (JSONObject) o);
-                    node.addChild(child);
-                } else if (o instanceof JSONArray) {
-                    PropertyDescription prop = createProperty(n, o);
-                    node.addProperty(prop);
+                    this.createNode(n, (JSONObject) o, contentCreator);
                 } else {
-                    PropertyDescription prop = createProperty(n, o);
-                    node.addProperty(prop);
+                    this.createProperty(n, o, contentCreator);
                 }
             }
         }
-        return node;
+        contentCreator.finishNode();
     }
 
-    protected PropertyDescription createProperty(String name, Object value)
-            throws JSONException {
-        PropertyDescription property = new PropertyDescription();
-        property.setName(name);
-
+    protected void createProperty(String name, Object value, ContentCreator contentCreator)
+    throws JSONException, RepositoryException {
         // assume simple value
         if (value instanceof JSONArray) {
             // multivalue
-            JSONArray array = (JSONArray) value;
+            final JSONArray array = (JSONArray) value;
             if (array.length() > 0) {
+                final String values[] = new String[array.length()];
                 for (int i = 0; i < array.length(); i++) {
-                    property.addValue(array.get(i));
+                    values[i] = array.get(i).toString();
                 }
-                value = array.opt(0);
+                final int propertyType = getType(values[0]);
+                contentCreator.createProperty(name, propertyType, values);
             } else {
-                property.addValue(null);
-                value = null;
+                contentCreator.createProperty(name, PropertyType.STRING, new String[0]);
             }
 
         } else {
             // single value
-            property.setValue(String.valueOf(value));
+            final int propertyType = getType(value);
+            contentCreator.createProperty(name, propertyType, String.valueOf(value));
         }
-        // set type
-        property.setType(getType(value));
-
-        return property;
     }
 
-    protected String getType(Object object) {
+    protected int getType(Object object) {
         if (object instanceof Double || object instanceof Float) {
-            return PropertyType.TYPENAME_DOUBLE;
+            return PropertyType.DOUBLE;
         } else if (object instanceof Number) {
-            return PropertyType.TYPENAME_LONG;
+            return PropertyType.LONG;
         } else if (object instanceof Boolean) {
-            return PropertyType.TYPENAME_BOOLEAN;
+            return PropertyType.BOOLEAN;
         }
 
         // fall back to default
-        return PropertyType.TYPENAME_STRING;
+        return PropertyType.STRING;
     }
 
     private String toString(InputStream ins) throws IOException {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index 53f9d2f..9e5efe8 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -26,7 +26,6 @@ import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLDecoder;
-import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -40,7 +39,6 @@ import java.util.Set;
 import javax.jcr.InvalidSerializedDataException;
 import javax.jcr.Item;
 import javax.jcr.Node;
-import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
@@ -71,14 +69,13 @@ public class Loader {
 
     private Map<String, ImportProvider> importProviders;
 
-    private Map<String, List<String>> delayedReferences;
+    private ContentLoader contentCreator = new ContentLoader();
 
     // bundles whose registration failed and should be retried
     private List<Bundle> delayedBundles;
 
     public Loader(ContentLoaderService jcrContentHelper) {
         this.jcrContentHelper = jcrContentHelper;
-        this.delayedReferences = new HashMap<String, List<String>>();
         this.delayedBundles = new LinkedList<Bundle>();
 
         importProviders = new LinkedHashMap<String, ImportProvider>();
@@ -88,7 +85,6 @@ public class Loader {
     }
 
     public void dispose() {
-        delayedReferences = null;
         if (delayedBundles != null) {
             delayedBundles.clear();
             delayedBundles = null;
@@ -99,12 +95,13 @@ public class Loader {
 
     /**
      * Register a bundle and install its content.
-     * 
+     *
      * @param session
      * @param bundle
      */
-    public void registerBundle(final Session session, final Bundle bundle,
-            final boolean isUpdate) {
+    public void registerBundle(final Session session,
+                               final Bundle bundle,
+                               final boolean isUpdate) {
 
         log.debug("Registering bundle {} for content loading.",
             bundle.getSymbolicName());
@@ -147,11 +144,11 @@ public class Loader {
         }
 
         try {
-            
+
             // check if the content has already been loaded
             final Map<String, Object> bundleContentInfo = jcrContentHelper.getBundleContentInfo(
                 session, bundle);
-            
+
             // if we don't get an info, someone else is currently loading
             if (bundleContentInfo == null) {
                 return false;
@@ -159,36 +156,36 @@ public class Loader {
 
             boolean success = false;
             try {
-                
+
                 final boolean contentAlreadyLoaded = ((Boolean) bundleContentInfo.get(ContentLoaderService.PROPERTY_CONTENT_LOADED)).booleanValue();
-                
+
                 if (!isUpdate && contentAlreadyLoaded) {
-                    
+
                     log.info("Content of bundle already loaded {}.",
                         bundle.getSymbolicName());
-                    
+
                 } else {
-                    
+
                     installContent(session, bundle, pathIter,
                         contentAlreadyLoaded);
-                    
+
                     if (isRetry) {
                         // log success of retry
                         log.info(
                             "Retrytring to load initial content for bundle {} succeeded.",
                             bundle.getSymbolicName());
                     }
-                    
+
                 }
-                
+
                 success = true;
                 return true;
-                
+
             } finally {
                 jcrContentHelper.unlockBundleContentInfo(session, bundle,
                     success);
             }
-            
+
         } catch (RepositoryException re) {
             // if we are retrying we already logged this message once, so we
             // won't log it again
@@ -203,49 +200,46 @@ public class Loader {
 
     /**
      * Unregister a bundle. Remove installed content.
-     * 
+     *
      * @param bundle The bundle.
      */
     public void unregisterBundle(final Session session, final Bundle bundle) {
-        
+
         // check if bundle has initial content
         final Iterator<PathEntry> pathIter = PathEntry.getContentPaths(bundle);
         if (delayedBundles.contains(bundle)) {
-            
+
             delayedBundles.remove(bundle);
-            
+
         } else {
-            
+
             if (pathIter != null) {
                 uninstallContent(session, bundle, pathIter);
                 jcrContentHelper.contentIsUninstalled(session, bundle);
             }
-            
+
         }
     }
 
     // ---------- internal -----------------------------------------------------
 
-    private void installContent(final Session session, final Bundle bundle,
-            final Iterator<PathEntry> pathIter,
-            final boolean contentAlreadyLoaded) throws RepositoryException {
+    private void installContent(final Session session,
+                                final Bundle bundle,
+                                final Iterator<PathEntry> pathIter,
+                                final boolean contentAlreadyLoaded)
+    throws RepositoryException {
         log.debug("Installing initial content from bundle {}",
             bundle.getSymbolicName());
         try {
 
-            // the nodes marked to be checked-in after import
-            List<Node> versionables = new ArrayList<Node>();
-
             while (pathIter.hasNext()) {
                 final PathEntry entry = pathIter.next();
                 if (!contentAlreadyLoaded || entry.isOverwrite()) {
-                    
-                    Node targetNode = getTargetNode(session, entry.getTarget());
+
+                    final Node targetNode = getTargetNode(session, entry.getTarget());
 
                     if (targetNode != null) {
-                        installFromPath(bundle, entry.getPath(),
-                            entry.isOverwrite(), versionables,
-                            entry.isCheckin(), targetNode);
+                        installFromPath(bundle, entry.getPath(), entry, targetNode);
                     }
                 }
             }
@@ -254,7 +248,7 @@ public class Loader {
             session.save();
 
             // finally checkin versionable nodes
-            for (Node versionable : versionables) {
+            for (final Node versionable : this.contentCreator.getVersionables()) {
                 versionable.checkin();
             }
 
@@ -268,6 +262,7 @@ public class Loader {
                     "Failure to rollback partial initial content for bundle {}",
                     bundle.getSymbolicName(), re);
             }
+            this.contentCreator.clear();
         }
         log.debug("Done installing initial content from bundle {}",
             bundle.getSymbolicName());
@@ -276,18 +271,19 @@ public class Loader {
 
     /**
      * Handle content installation for a single path.
-     * 
+     *
      * @param bundle The bundle containing the content.
      * @param path The path
      * @param overwrite Should the content be overwritten.
      * @param parent The parent node.
      * @throws RepositoryException
      */
-    private void installFromPath(final Bundle bundle, final String path,
-            final boolean overwrite, List<Node> versionables,
-            final boolean checkin, final Node parent)
-            throws RepositoryException {
-        
+    private void installFromPath(final Bundle bundle,
+                                 final String path,
+                                 final PathEntry configuration,
+                                 final Node parent)
+    throws RepositoryException {
+
         @SuppressWarnings("unchecked")
         Enumeration<String> entries = bundle.getEntryPaths(path);
         if (entries == null) {
@@ -298,8 +294,7 @@ public class Loader {
         Map<URL, Node> processedEntries = new HashMap<URL, Node>();
 
         // potential root node import/extension
-        URL rootNodeDescriptor = importRootNode(parent.getSession(), bundle,
-            path, versionables, checkin);
+        URL rootNodeDescriptor = importRootNode(parent.getSession(), bundle, path, configuration);
         if (rootNodeDescriptor != null) {
             processedEntries.put(rootNodeDescriptor,
                 parent.getSession().getRootNode());
@@ -309,7 +304,7 @@ public class Loader {
             final String entry = entries.nextElement();
             log.debug("Processing initial content entry {}", entry);
             if (entry.endsWith("/")) {
-                
+
                 // dir, check for node descriptor , else create dir
                 String base = entry.substring(0, entry.length() - 1);
                 String name = getName(base);
@@ -330,21 +325,20 @@ public class Loader {
                     node = processedEntries.get(nodeDescriptor);
                     if (node == null) {
                         node = createNode(parent, name, nodeDescriptor,
-                            overwrite, versionables, checkin);
+                            configuration);
                         processedEntries.put(nodeDescriptor, node);
                     }
                 } else {
-                    node = createFolder(parent, name, overwrite);
+                    node = createFolder(parent, name, configuration.isOverwrite());
                 }
 
                 // walk down the line
                 if (node != null) {
-                    installFromPath(bundle, entry, overwrite, versionables,
-                        checkin, node);
+                    installFromPath(bundle, entry, configuration, node);
                 }
 
             } else {
-                
+
                 // file => create file
                 URL file = bundle.getEntry(entry);
                 if (processedEntries.containsKey(file)) {
@@ -363,8 +357,7 @@ public class Loader {
                 }
                 if (foundProvider) {
                     Node node = null;
-                    if ((node = createNode(parent, getName(entry), file, overwrite,
-                        versionables, checkin)) != null) {
+                    if ((node = createNode(parent, getName(entry), file, configuration)) != null) {
                         processedEntries.put(file, node);
                         continue;
                     }
@@ -380,20 +373,34 @@ public class Loader {
         }
     }
 
-    private Node createNode(Node parent, String name, URL nodeXML,
-            boolean overwrite, List<Node> versionables, boolean checkin)
-            throws RepositoryException {
-
+    /**
+     * Create a new node from a content resource found in the bundle.
+     * @param parent The parent node
+     * @param name   The name of the new content node
+     * @param resourceUrl The resource url.
+     * @param overwrite Should the content be overwritten?
+     * @param versionables
+     * @param checkin
+     * @return
+     * @throws RepositoryException
+     */
+    private Node createNode(Node parent,
+                            String name,
+                            URL resourceUrl,
+                            PathEntry configuration)
+    throws RepositoryException {
+        final String resourcePath = resourceUrl.getPath().toLowerCase();
         InputStream ins = null;
         try {
             // special treatment for system view imports
-            if (nodeXML.getPath().toLowerCase().endsWith(EXT_JCR_XML)) {
-                return importSystemView(parent, name, nodeXML);
+            if (resourcePath.endsWith(EXT_JCR_XML)) {
+                return importSystemView(parent, name, resourceUrl);
             }
 
-            NodeReader nodeReader = null;
+            // get the node reader for this resource
+            ContentReader nodeReader = null;
             for (Map.Entry<String, ImportProvider> e : importProviders.entrySet()) {
-                if (nodeXML.getPath().toLowerCase().endsWith(e.getKey())) {
+                if (resourcePath.endsWith(e.getKey())) {
                     nodeReader = e.getValue().getReader();
                     break;
                 }
@@ -404,20 +411,11 @@ public class Loader {
                 return null;
             }
 
-            ins = nodeXML.openStream();
-            NodeDescription clNode = nodeReader.parse(ins);
+            this.contentCreator.init(configuration, parent, toPlainName(name));
+            ins = resourceUrl.openStream();
+            nodeReader.parse(ins, this.contentCreator);
 
-            // nothing has been parsed
-            if (clNode == null) {
-                return null;
-            }
-
-            if (clNode.getName() == null) {
-                // set the name without the [last] extension (xml or json)
-                clNode.setName(toPlainName(name));
-            }
-
-            return createNode(parent, clNode, overwrite, versionables, checkin);
+            return this.contentCreator.getRootNode();
         } catch (RepositoryException re) {
             throw re;
         } catch (Throwable t) {
@@ -434,7 +432,7 @@ public class Loader {
 
     /**
      * Delete the node from the initial content.
-     * 
+     *
      * @param parent
      * @param name
      * @param nodeXML
@@ -447,118 +445,9 @@ public class Loader {
         }
     }
 
-    private Node createNode(Node parentNode, NodeDescription clNode,
-            final boolean overwrite, List<Node> versionables, boolean checkin)
-            throws RepositoryException {
-        
-        // if node already exists but should be overwritten, delete it
-        if (overwrite && parentNode.hasNode(clNode.getName())) {
-            parentNode.getNode(clNode.getName()).remove();
-        }
-
-        // ensure repository node
-        Node node;
-        if (parentNode.hasNode(clNode.getName())) {
-
-            // use existing node
-            node = parentNode.getNode(clNode.getName());
-
-        } else if (clNode.getPrimaryNodeType() == null) {
-
-            // node explicit node type, use repository default
-            node = parentNode.addNode(clNode.getName());
-
-        } else {
-
-            // explicit primary node type
-            node = parentNode.addNode(clNode.getName(),
-                clNode.getPrimaryNodeType());
-        }
-
-        return setupNode(node, clNode, versionables, checkin);
-    }
-
-    private Node setupNode(Node node, NodeDescription clNode,
-            List<Node> versionables, boolean checkin)
-            throws RepositoryException {
-
-        // ammend mixin node types
-        if (clNode.getMixinNodeTypes() != null) {
-            for (String mixin : clNode.getMixinNodeTypes()) {
-                if (!node.isNodeType(mixin)) {
-                    node.addMixin(mixin);
-                }
-            }
-        }
-
-        // check if node is versionable
-        boolean addToVersionables = checkin
-            && node.isNodeType("mix:versionable");
-
-        if (clNode.getProperties() != null) {
-            for (PropertyDescription prop : clNode.getProperties()) {
-                if (node.hasProperty(prop.getName())
-                    && !node.getProperty(prop.getName()).isNew()) {
-                    continue;
-                }
-
-                int type = PropertyType.valueFromName(prop.getType());
-                if (prop.isMultiValue()) {
-
-                    String[] values = prop.getValues().toArray(
-                        new String[prop.getValues().size()]);
-                    node.setProperty(prop.getName(), values, type);
-
-                } else if (type == PropertyType.REFERENCE) {
-
-                    // need to resolve the reference
-                    String propPath = node.getPath() + "/" + prop.getName();
-                    String uuid = getUUID(node.getSession(), propPath,
-                        prop.getValue());
-                    if (uuid != null) {
-                        node.setProperty(prop.getName(), uuid, type);
-                    }
-
-                } else if ("jcr:isCheckedOut".equals(prop.getName())) {
-
-                    // don't try to write the property but record its state
-                    // for later checkin if set to false
-                    boolean checkedout = Boolean.valueOf(prop.getValue());
-                    if (!checkedout) {
-                        addToVersionables = true;
-                    }
-
-                } else {
-
-                    node.setProperty(prop.getName(), prop.getValue(), type);
-
-                }
-            }
-        }
-
-        // add the current node to the list of versionables to be checked
-        // in at the end. This is done if checkin is true and the node is
-        // versionable or if the jcr:isCheckedOut property is false
-        if (addToVersionables) {
-            versionables.add(node);
-        }
-
-        // create child nodes from the descriptor
-        if (clNode.getChildren() != null) {
-            for (NodeDescription child : clNode.getChildren()) {
-                createNode(node, child, false, versionables, checkin);
-            }
-        }
-
-        // resolve REFERENCE property values pointing to this node
-        resolveReferences(node);
-
-        return node;
-    }
-
     /**
      * Create a folder
-     * 
+     *
      * @param parent The parent node.
      * @param name The name of the folder
      * @param overwrite If set to true, an existing folder is removed first.
@@ -580,7 +469,7 @@ public class Loader {
 
     /**
      * Create a file from the given url.
-     * 
+     *
      * @param parent
      * @param source
      * @throws IOException
@@ -623,7 +512,7 @@ public class Loader {
 
     /**
      * Delete the file from the given url.
-     * 
+     *
      * @param parent
      * @param source
      * @throws IOException
@@ -637,53 +526,6 @@ public class Loader {
         }
     }
 
-    private String getUUID(Session session, String propPath,
-            String referencePath) throws RepositoryException {
-        if (session.itemExists(referencePath)) {
-            Item item = session.getItem(referencePath);
-            if (item.isNode()) {
-                Node refNode = (Node) item;
-                if (refNode.isNodeType("mix:referenceable")) {
-                    return refNode.getUUID();
-                }
-            }
-        } else {
-            // not existing yet, keep for delayed setting
-            List<String> current = delayedReferences.get(referencePath);
-            if (current == null) {
-                current = new ArrayList<String>();
-                delayedReferences.put(referencePath, current);
-            }
-            current.add(propPath);
-        }
-
-        // no UUID found
-        return null;
-    }
-
-    private void resolveReferences(Node node) throws RepositoryException {
-        List<String> props = delayedReferences.remove(node.getPath());
-        if (props == null || props.size() == 0) {
-            return;
-        }
-
-        // check whether we can set at all
-        if (!node.isNodeType("mix:referenceable")) {
-            return;
-        }
-
-        Session session = node.getSession();
-        String uuid = node.getUUID();
-
-        for (String property : props) {
-            String name = getName(property);
-            Node parentNode = getParentNode(session, property);
-            if (parentNode != null) {
-                parentNode.setProperty(name, uuid, PropertyType.REFERENCE);
-            }
-        }
-    }
-
     /**
      * Gets and decods the name part of the <code>path</code>. The name is
      * the part of the path after the last slash (or the complete path if no
@@ -693,7 +535,7 @@ public class Loader {
      * encoding. In this case, this method decodes the name using the
      * <code>java.netURLDecoder</code> class with the <i>UTF-8</i> character
      * encoding.
-     * 
+     *
      * @param path The path from which to extract the name part.
      * @return The URL decoded name part.
      */
@@ -721,30 +563,6 @@ public class Loader {
         return name;
     }
 
-    private Node getParentNode(Session session, String path)
-            throws RepositoryException {
-        int lastSlash = path.lastIndexOf('/');
-
-        // not an absolute path, cannot find parent
-        if (lastSlash < 0) {
-            return null;
-        }
-
-        // node below root
-        if (lastSlash == 0) {
-            return session.getRootNode();
-        }
-
-        // item in the hierarchy
-        path = path.substring(0, lastSlash);
-        if (!session.itemExists(path)) {
-            return null;
-        }
-
-        Item item = session.getItem(path);
-        return (item.isNode()) ? (Node) item : null;
-    }
-
     private Node getTargetNode(Session session, String path)
             throws RepositoryException {
 
@@ -800,7 +618,7 @@ public class Loader {
 
     /**
      * Handle content uninstallation for a single path.
-     * 
+     *
      * @param bundle The bundle containing the content.
      * @param path The path
      * @param parent The parent node.
@@ -897,7 +715,7 @@ public class Loader {
      * Import the XML file as JCR system or document view import. If the XML
      * file is not a valid system or document view export/import file,
      * <code>false</code> is returned.
-     * 
+     *
      * @param parent The parent node below which to import
      * @param nodeXML The URL to the XML file to import
      * @return <code>true</code> if the import succeeds, <code>false</code>
@@ -958,14 +776,14 @@ public class Loader {
     protected static final class Descriptor {
         public URL rootNodeDescriptor;
 
-        public NodeReader nodeReader;
+        public ContentReader nodeReader;
     }
 
     /**
      * Return the root node descriptor.
      */
     private Descriptor getRootNodeDescriptor(final Bundle bundle,
-            final String path) {
+                                             final String path) {
         URL rootNodeDescriptor = null;
 
         for (Map.Entry<String, ImportProvider> e : importProviders.entrySet()) {
@@ -993,9 +811,8 @@ public class Loader {
      * Imports mixin nodes and properties (and optionally child nodes) of the
      * root node.
      */
-    private URL importRootNode(Session session, Bundle bundle, String path,
-            List<Node> versionables, boolean checkin)
-            throws RepositoryException {
+    private URL importRootNode(Session session, Bundle bundle, String path, PathEntry configuration)
+    throws RepositoryException {
         final Descriptor descriptor = getRootNodeDescriptor(bundle, path);
         // no root descriptor found
         if (descriptor == null) {
@@ -1006,9 +823,8 @@ public class Loader {
         try {
 
             ins = descriptor.rootNodeDescriptor.openStream();
-            NodeDescription clNode = descriptor.nodeReader.parse(ins);
-
-            setupNode(session.getRootNode(), clNode, versionables, checkin);
+            this.contentCreator.init(configuration, session.getRootNode(), null);
+            descriptor.nodeReader.parse(ins, this.contentCreator);
 
             return descriptor.rootNodeDescriptor;
         } catch (RepositoryException re) {
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeDescription.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeDescription.java
deleted file mode 100644
index 043da37..0000000
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/NodeDescription.java
+++ /dev/null
@@ -1,153 +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.contentloader.internal;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-class NodeDescription {
-
-    private String name;
-    private String primaryNodeType;
-    private Set<String> mixinNodeTypes;
-    private List<PropertyDescription> properties;
-    private List<NodeDescription> children;
-
-    /**
-     * @return the children
-     */
-    List<NodeDescription> getChildren() {
-        return children;
-    }
-
-    /**
-     * @param children the children to set
-     */
-    void addChild(NodeDescription child) {
-        if (child != null) {
-            if (children == null) {
-                children = new ArrayList<NodeDescription>();
-            }
-
-            children.add(child);
-        }
-    }
-
-    /**
-     * @return the mixinNodeTypes
-     */
-    Set<String> getMixinNodeTypes() {
-        return mixinNodeTypes;
-    }
-
-    /**
-     * @param mixinNodeTypes the mixinNodeTypes to set
-     */
-    void addMixinNodeType(String mixinNodeType) {
-        if (mixinNodeType != null && mixinNodeType.length() > 0) {
-            if (mixinNodeTypes == null) {
-                mixinNodeTypes = new HashSet<String>();
-            }
-
-            mixinNodeTypes.add(mixinNodeType);
-        }
-    }
-
-    /**
-     * @return the name
-     */
-    String getName() {
-        return name;
-    }
-
-    /**
-     * @param name the name to set
-     */
-    void setName(String name) {
-        this.name = name;
-    }
-
-    /**
-     * @return the primaryNodeType
-     */
-    String getPrimaryNodeType() {
-        return primaryNodeType;
-    }
-
-    /**
-     * @param primaryNodeType the primaryNodeType to set
-     */
-    void setPrimaryNodeType(String primaryNodeType) {
-        this.primaryNodeType = primaryNodeType;
-    }
-
-    /**
-     * @return the properties
-     */
-    List<PropertyDescription> getProperties() {
-        return properties;
-    }
-
-    /**
-     * @param properties the properties to set
-     */
-    void addProperty(PropertyDescription property) {
-        if (property != null) {
-            if (properties == null) {
-                properties = new ArrayList<PropertyDescription>();
-            }
-
-            properties.add(property);
-        }
-    }
-
-    public int hashCode() {
-        int code = getName().hashCode() * 17;
-        if (getPrimaryNodeType() != null) {
-            code += getPrimaryNodeType().hashCode();
-        }
-        return code;
-    }
-
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        } else if (!(obj instanceof NodeDescription)) {
-            return false;
-        }
-
-        NodeDescription other = (NodeDescription) obj;
-        return getName().equals(other.getName())
-            && equals(getPrimaryNodeType(), other.getPrimaryNodeType())
-            && equals(getMixinNodeTypes(), other.getMixinNodeTypes())
-            && equals(getProperties(), other.getProperties())
-            && equals(getChildren(), other.getChildren());
-    }
-
-    public String toString() {
-        return "Node " + getName() + ", primary=" + getPrimaryNodeType()
-            + ", mixins=" + getMixinNodeTypes();
-    }
-
-    private boolean equals(Object o1, Object o2) {
-        return (o1 == null) ? o2 == null : o1.equals(o2);
-    }
-}
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
index cfb8c24..937462b 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/PathEntry.java
@@ -54,6 +54,14 @@ public class PathEntry {
      */
     public static final String CHECKIN_DIRECTIVE = "checkin";
 
+    /**
+     * The expand directive specifying whether the available {@link ImportProvider}s
+     * should be used during content loading. This is a boolean value that
+     * defaults to true.
+     * @since 2.0.4
+     */
+    public static final String EXPAND_DIRECTIVE = "expand";
+
     /** The path for the initial content. */
     private final String path;
 
@@ -62,10 +70,13 @@ public class PathEntry {
 
     /** Should existing content be uninstalled? */
     private final boolean uninstall;
-    
+
     /** Should versionable nodes be checked in? */
     private final boolean checkin;
 
+    /** Should archives be expanded? @since 2.0.4 */
+    private final boolean expand;
+
     /**
      * Target path where initial content will be loaded. If it´s null then
      * target node is the root node
@@ -90,32 +101,49 @@ public class PathEntry {
     }
 
     public PathEntry(ManifestHeader.Entry entry) {
+        this.path = entry.getValue();
+
         // check for directives
+
+        // overwrite directive
         final String overwriteValue = entry.getDirectiveValue(OVERWRITE_DIRECTIVE);
-        final String uninstallValue = entry.getDirectiveValue(UNINSTALL_DIRECTIVE);
-        final String pathValue = entry.getDirectiveValue(PATH_DIRECTIVE);
-        final String checkinValue = entry.getDirectiveValue(CHECKIN_DIRECTIVE);
-        boolean overwriteFlag = false;
         if (overwriteValue != null) {
-            overwriteFlag = Boolean.valueOf(overwriteValue);
+            this.overwrite = Boolean.valueOf(overwriteValue);
+        } else {
+            this.overwrite = false;
         }
-        this.path = entry.getValue();
-        this.overwrite = overwriteFlag;
+
+        // uninstall directive
+        final String uninstallValue = entry.getDirectiveValue(UNINSTALL_DIRECTIVE);
         if (uninstallValue != null) {
             this.uninstall = Boolean.valueOf(uninstallValue);
         } else {
             this.uninstall = this.overwrite;
         }
+
+        // path directive
+        final String pathValue = entry.getDirectiveValue(PATH_DIRECTIVE);
         if (pathValue != null) {
             this.target = pathValue;
         } else {
             this.target = null;
         }
+
+        // checkin directive
+        final String checkinValue = entry.getDirectiveValue(CHECKIN_DIRECTIVE);
         if (checkinValue != null) {
             this.checkin = Boolean.valueOf(checkinValue);
         } else {
             this.checkin = false;
         }
+
+        // expand directive
+        final String expandValue = entry.getDirectiveValue(EXPAND_DIRECTIVE);
+        if ( expandValue != null ) {
+            this.expand = Boolean.valueOf(expandValue);
+        } else {
+            this.expand = true;
+        }
     }
 
     public String getPath() {
@@ -133,7 +161,11 @@ public class PathEntry {
     public boolean isCheckin() {
         return this.checkin;
     }
-    
+
+    public boolean isExpand() {
+        return this.expand;
+    }
+
     public String getTarget() {
         return target;
     }
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/PropertyDescription.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/PropertyDescription.java
deleted file mode 100644
index 4fd7823..0000000
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/PropertyDescription.java
+++ /dev/null
@@ -1,119 +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.contentloader.internal;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.PropertyType;
-
-class PropertyDescription {
-    private String name;
-    private String value;
-    private List<String> values;
-    private String type = PropertyType.TYPENAME_STRING; // default type to string
-
-    /**
-     * @return the name
-     */
-    String getName() {
-        return this.name;
-    }
-
-    /**
-     * @param name the name to set
-     */
-    void setName(String name) {
-        this.name = name;
-    }
-
-    /**
-     * @return the type
-     */
-    String getType() {
-        return this.type;
-    }
-
-    /**
-     * @param type the type to set
-     */
-    void setType(String type) {
-        this.type = type;
-    }
-
-    /**
-     * @return the value
-     */
-    String getValue() {
-        return this.value;
-    }
-
-    /**
-     * @param value the value to set
-     */
-    void setValue(String value) {
-        this.value = value;
-    }
-
-    /**
-     * @return the values
-     */
-    List<String> getValues() {
-        return this.values;
-    }
-
-    /**
-     * @param values the values to set
-     */
-    void addValue(Object value) {
-        if (this.values == null) {
-            this.values = new ArrayList<String>();
-        }
-
-        if (value != null) {
-            this.values.add(value.toString());
-        }
-    }
-
-    boolean isMultiValue() {
-        return this.values != null;
-    }
-
-    public int hashCode() {
-        return this.getName().hashCode() * 17 + this.getType().hashCode();
-    }
-
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        } else if (!(obj instanceof PropertyDescription)) {
-            return false;
-        }
-
-        PropertyDescription other = (PropertyDescription) obj;
-        return this.getName().equals(other.getName())
-            && this.getType().equals(other.getType())
-            && this.equals(this.getValues(), other.getValues())
-            && this.equals(this.getValue(), other.getValue());
-    }
-
-    private boolean equals(Object o1, Object o2) {
-        return (o1 == null) ? o2 == null : o1.equals(o2);
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java
index a96155e..98dd4a6 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/XmlReader.java
@@ -20,13 +20,47 @@ package org.apache.sling.jcr.contentloader.internal;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
 
 import org.kxml2.io.KXmlParser;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-public class XmlReader implements NodeReader {
+/**
+ * This reader reads an xml file defining the content.
+ * The xml format should have this format:
+ * <node>
+ *   <name>the name of the node</name>
+ *   <primaryNodeType>type</primaryNodeType>
+ *   <mixinNodeTypes>
+ *     <mixinNodeType>mixtype1</mixinNodeType>
+ *     <mixinNodeType>mixtype2</mixinNodeType>
+ *   </mixingNodeTypes>
+ *   <properties>
+ *     <property>
+ *       <name>propName</name>
+ *       <value>propValue</value>
+ *           or
+ *       <values>
+ *         <value/> for multi value properties
+ *       </values>
+ *       <type>propType</type>
+ *     </property>
+ *     <!-- more properties -->
+ *   </properties>
+ *   <nodes>
+ *     <!-- child nodes -->
+ *     <node>
+ *       ..
+ *     </node>
+ *   </nodes>
+ * </node>
+ */
+public class XmlReader implements ContentReader {
 
     /*
      * <node> <primaryNodeType>type</primaryNodeType> <mixinNodeTypes>
@@ -55,7 +89,7 @@ public class XmlReader implements NodeReader {
     static final ImportProvider PROVIDER = new ImportProvider() {
         private XmlReader xmlReader;
 
-        public NodeReader getReader() throws IOException {
+        public ContentReader getReader() throws IOException {
             if (xmlReader == null) {
                 try {
                     xmlReader = new XmlReader();
@@ -75,39 +109,46 @@ public class XmlReader implements NodeReader {
 
     // ---------- XML content access -------------------------------------------
 
-    public synchronized NodeDescription parse(InputStream ins) throws IOException {
+
+    /**
+     * @see org.apache.sling.jcr.contentloader.internal.ContentReader#parse(java.io.InputStream, org.apache.sling.jcr.contentloader.internal.ContentCreator)
+     */
+    public synchronized void parse(InputStream ins, ContentCreator creator)
+    throws IOException, RepositoryException {
         try {
-            return this.parseInternal(ins);
+            this.parseInternal(ins, creator);
         } catch (XmlPullParserException xppe) {
             throw (IOException) new IOException(xppe.getMessage()).initCause(xppe);
         }
     }
 
-    private NodeDescription parseInternal(InputStream ins) throws IOException,
-            XmlPullParserException {
-        String currentElement = "<root>";
-        LinkedList<String> elements = new LinkedList<String>();
-        NodeDescription currentNode = null;
-        LinkedList<NodeDescription> nodes = new LinkedList<NodeDescription>();
-        StringBuffer contentBuffer = new StringBuffer();
-        PropertyDescription currentProperty = null;
+    private void parseInternal(InputStream ins, ContentCreator creator)
+    throws IOException, XmlPullParserException, RepositoryException {
+        final StringBuffer contentBuffer = new StringBuffer();
 
         // set the parser input, use null encoding to force detection with
         // <?xml?>
         this.xmlParser.setInput(ins, null);
 
+        NodeDescription.SHARED.clear();
+        PropertyDescription.SHARED.clear();
+
+        NodeDescription currentNode = null;
+        PropertyDescription currentProperty = null;
+        String currentElement;
+
         int eventType = this.xmlParser.getEventType();
         while (eventType != XmlPullParser.END_DOCUMENT) {
             if (eventType == XmlPullParser.START_TAG) {
 
-                elements.add(currentElement);
                 currentElement = this.xmlParser.getName();
 
                 if (ELEM_PROPERTY.equals(currentElement)) {
-                    currentProperty = new PropertyDescription();
+                    currentNode = NodeDescription.create(currentNode, creator);
+                    currentProperty = PropertyDescription.SHARED;
                 } else if (ELEM_NODE.equals(currentElement)) {
-                    if (currentNode != null) nodes.add(currentNode);
-                    currentNode = new NodeDescription();
+                    currentNode = NodeDescription.create(currentNode, creator);
+                    currentNode = NodeDescription.SHARED;
                 }
 
             } else if (eventType == XmlPullParser.END_TAG) {
@@ -117,53 +158,136 @@ public class XmlReader implements NodeReader {
                 contentBuffer.delete(0, contentBuffer.length());
 
                 if (ELEM_PROPERTY.equals(qName)) {
-                    currentNode.addProperty(currentProperty);
-                    currentProperty = null;
+                    currentProperty = PropertyDescription.create(currentProperty, creator);
 
                 } else if (ELEM_NAME.equals(qName)) {
                     if (currentProperty != null) {
-                        currentProperty.setName(content);
+                        currentProperty.name = content;
                     } else if (currentNode != null) {
-                        currentNode.setName(content);
+                        currentNode.name = content;
                     }
 
                 } else if (ELEM_VALUE.equals(qName)) {
-                    if (currentProperty.isMultiValue()) {
-                        currentProperty.addValue(content);
-                    } else {
-                        currentProperty.setValue(content);
-                    }
+                    currentProperty.addValue(content);
 
                 } else if (ELEM_VALUES.equals(qName)) {
-                    currentProperty.addValue(null);
-                    currentProperty.setValue(null);
+                    currentProperty.isMultiValue = true;
 
                 } else if (ELEM_TYPE.equals(qName)) {
-                    currentProperty.setType(content);
+                    currentProperty.type = content;
 
                 } else if (ELEM_NODE.equals(qName)) {
-                    if (!nodes.isEmpty()) {
-                        NodeDescription parent = nodes.removeLast();
-                        parent.addChild(currentNode);
-                        currentNode = parent;
-                    }
+                    currentNode = NodeDescription.create(currentNode, creator);
+                    creator.finishNode();
 
                 } else if (ELEM_PRIMARY_NODE_TYPE.equals(qName)) {
-                    currentNode.setPrimaryNodeType(content);
+                    if ( currentNode == null ) {
+                        throw new IOException("Element is not allowed at this location: " + qName);
+                    }
+                    currentNode.primaryNodeType = content;
 
                 } else if (ELEM_MIXIN_NODE_TYPE.equals(qName)) {
-                    currentNode.addMixinNodeType(content);
+                    if ( currentNode == null ) {
+                        throw new IOException("Element is not allowed at this location: " + qName);
+                    }
+                    currentNode.addMixinType(content);
                 }
 
-                currentElement = elements.removeLast();
-
             } else if (eventType == XmlPullParser.TEXT) {
                 contentBuffer.append(this.xmlParser.getText());
             }
 
             eventType = this.xmlParser.next();
         }
+    }
+
+    protected static final class NodeDescription {
+
+        public static NodeDescription SHARED = new NodeDescription();
+
+        public String name;
+        public String primaryNodeType;
+        public List<String> mixinTypes;
+
+        public static NodeDescription create(NodeDescription desc, ContentCreator creator)
+        throws RepositoryException {
+            if ( desc != null ) {
+                creator.createNode(desc.name, desc.primaryNodeType, desc.getMixinTypes());
+                desc.clear();
+            }
+            return null;
+        }
+
+        public void addMixinType(String v) {
+            if ( this.mixinTypes == null ) {
+                this.mixinTypes = new ArrayList<String>();
+            }
+            this.mixinTypes.add(v);
+        }
+
+
+        private String[] getMixinTypes() {
+            if ( this.mixinTypes == null || this.mixinTypes.size() == 0) {
+                return null;
+            }
+            return mixinTypes.toArray(new String[this.mixinTypes.size()]);
+        }
+
+        private void clear() {
+            this.name = null;
+            this.primaryNodeType = null;
+            if ( this.mixinTypes != null ) {
+                this.mixinTypes.clear();
+            }
+        }
+    }
+
+    protected static final class PropertyDescription {
+
+        public static PropertyDescription SHARED = new PropertyDescription();
+
+        public static PropertyDescription create(PropertyDescription desc, ContentCreator creator)
+        throws RepositoryException {
+            int type = (desc.type == null ? PropertyType.STRING : PropertyType.valueFromName(desc.type));
+            if ( desc.isMultiValue ) {
+                creator.createProperty(desc.name, type, desc.getPropertyValues());
+            } else {
+                String value = null;
+                if ( desc.values != null && desc.values.size() == 1 ) {
+                    value = desc.values.get(0);
+                }
+                creator.createProperty(desc.name, type, value);
+            }
+            desc.clear();
+            return null;
+        }
+
+        public String name;
+        public String type;
+        public List<String> values;
+        public boolean isMultiValue;
+
+        public void addValue(String v) {
+            if ( this.values == null ) {
+                this.values = new ArrayList<String>();
+            }
+            this.values.add(v);
+        }
+
+        private String[] getPropertyValues() {
+            if ( this.values == null || this.values.size() == 0) {
+                return null;
+            }
+            return values.toArray(new String[this.values.size()]);
+        }
 
-        return currentNode;
+        private void clear() {
+            this.name = null;
+            this.type = null;
+            if ( this.values != null ) {
+                this.values.clear();
+            }
+            this.isMultiValue = false;
+        }
     }
 }
diff --git a/src/main/test/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java b/src/main/test/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
deleted file mode 100644
index a975d3e..0000000
--- a/src/main/test/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
+++ /dev/null
@@ -1,374 +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.contentloader.internal;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.jcr.PropertyType;
-
-import junit.framework.TestCase;
-
-import org.apache.sling.commons.json.JSONArray;
-import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.JSONObject;
-import org.apache.sling.jcr.contentloader.internal.JsonReader;
-import org.apache.sling.jcr.contentloader.internal.NodeDescription;
-import org.apache.sling.jcr.contentloader.internal.PropertyDescription;
-
-public class JsonReaderTest extends TestCase {
-
-    JsonReader jsonReader;
-
-    protected void setUp() throws Exception {
-        super.setUp();
-        this.jsonReader = new JsonReader();
-    }
-
-    protected void tearDown() throws Exception {
-        this.jsonReader = null;
-        super.tearDown();
-    }
-
-    public void testEmptyObject() {
-        try {
-            this.parse("");
-        } catch (IOException ioe) {
-            fail("Expected IOException from empty JSON");
-        }
-    }
-
-    public void testEmpty() throws IOException {
-        NodeDescription node = this.parse("{}");
-        assertNotNull("Expecting node", node);
-        assertNull("No name expected", node.getName());
-    }
-
-    public void testDefaultPrimaryNodeType() throws IOException {
-        String json = "{}";
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertNull(node.getPrimaryNodeType());
-        assertNull("No mixins expected", node.getMixinNodeTypes());
-        assertNull("No properties expected", node.getProperties());
-        assertNull("No children expected", node.getChildren());
-    }
-
-    public void testDefaultPrimaryNodeTypeWithSurroundWhitespace() throws IOException {
-        String json = "     {  }     ";
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertNull(node.getPrimaryNodeType());
-        assertNull("No mixins expected", node.getMixinNodeTypes());
-        assertNull("No properties expected", node.getProperties());
-        assertNull("No children expected", node.getChildren());
-    }
-
-    public void testDefaultPrimaryNodeTypeWithoutEnclosingBraces() throws IOException {
-        String json = "";
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertNull(node.getPrimaryNodeType());
-        assertNull("No mixins expected", node.getMixinNodeTypes());
-        assertNull("No properties expected", node.getProperties());
-        assertNull("No children expected", node.getChildren());
-    }
-
-    public void testDefaultPrimaryNodeTypeWithoutEnclosingBracesWithSurroundWhitespace() throws IOException {
-        String json = "             ";
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertNull(node.getPrimaryNodeType());
-        assertNull("No mixins expected", node.getMixinNodeTypes());
-        assertNull("No properties expected", node.getProperties());
-        assertNull("No children expected", node.getChildren());
-    }
-
-    public void testExplicitePrimaryNodeType() throws IOException {
-        String type = "xyz:testType";
-        String json = "{ \"jcr:primaryType\": \"" + type + "\" }";
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(type, node.getPrimaryNodeType());
-    }
-
-    public void testMixinNodeTypes1() throws JSONException, IOException {
-        Set<Object> mixins = this.toSet(new Object[]{ "xyz:mix1" });
-        String json = "{ \"jcr:mixinTypes\": " + this.toJsonArray(mixins) + "}";
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(mixins, node.getMixinNodeTypes());
-    }
-
-    public void testMixinNodeTypes2() throws JSONException, IOException {
-        Set<Object> mixins = this.toSet(new Object[]{ "xyz:mix1", "abc:mix2" });
-        String json = "{ \"jcr:mixinTypes\": " + this.toJsonArray(mixins) + "}";
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(mixins, node.getMixinNodeTypes());
-    }
-
-    public void testPropertiesNone() throws IOException, JSONException {
-        List<PropertyDescription> properties = null;
-        String json = "{ \"properties\": " + this.toJsonObject(properties) + "}";
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(properties, node.getProperties());
-    }
-
-    public void testPropertiesSingleValue() throws IOException, JSONException {
-        List<PropertyDescription> properties = new ArrayList<PropertyDescription>();
-        PropertyDescription prop = new PropertyDescription();
-        prop.setName("p1");
-        prop.setValue("v1");
-        properties.add(prop);
-        
-        String json = this.toJsonObject(properties).toString();
-        
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(new HashSet<PropertyDescription>(properties), new HashSet<PropertyDescription>(node.getProperties()));
-    }
-    
-    public void testPropertiesTwoSingleValue() throws IOException, JSONException {
-        List<PropertyDescription> properties = new ArrayList<PropertyDescription>();
-        PropertyDescription prop = new PropertyDescription();
-        prop.setName("p1");
-        prop.setValue("v1");
-        properties.add(prop);
-        prop = new PropertyDescription();
-        prop.setName("p2");
-        prop.setValue("v2");
-        properties.add(prop);
-
-        String json = this.toJsonObject(properties).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(new HashSet<PropertyDescription>(properties), new HashSet<PropertyDescription>(node.getProperties()));
-    }
-
-    public void testPropertiesMultiValue() throws IOException, JSONException {
-        List<PropertyDescription> properties = new ArrayList<PropertyDescription>();
-        PropertyDescription prop = new PropertyDescription();
-        prop.setName("p1");
-        prop.addValue("v1");
-        properties.add(prop);
-
-        String json = this.toJsonObject(properties).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(new HashSet<PropertyDescription>(properties), new HashSet<PropertyDescription>(node.getProperties()));
-    }
-
-    public void testPropertiesMultiValueEmpty() throws IOException, JSONException {
-        List<PropertyDescription> properties = new ArrayList<PropertyDescription>();
-        PropertyDescription prop = new PropertyDescription();
-        prop.setName("p1");
-        prop.addValue(null); // empty multivalue property
-        properties.add(prop);
-
-        String json = this.toJsonObject(properties).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(new HashSet<PropertyDescription>(properties), new HashSet<PropertyDescription>(node.getProperties()));
-    }
-
-    public void testChildrenNone() throws IOException, JSONException {
-        List<NodeDescription> nodes = null;
-        String json = this.toJsonObject(nodes).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(nodes, node.getChildren());
-    }
-
-    public void testChild() throws IOException, JSONException {
-        List<NodeDescription> nodes = new ArrayList<NodeDescription>();
-        NodeDescription child = new NodeDescription();
-        child.setName("p1");
-        nodes.add(child);
-
-        String json = this.toJsonObject(nodes).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(nodes, node.getChildren());
-    }
-
-    public void testChildWithMixin() throws IOException, JSONException {
-        List<NodeDescription> nodes = new ArrayList<NodeDescription>();
-        NodeDescription child = new NodeDescription();
-        child.setName("p1");
-        child.addMixinNodeType("p1:mix");
-        nodes.add(child);
-
-        String json = this.toJsonObject(nodes).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(nodes, node.getChildren());
-    }
-
-    public void testTwoChildren() throws IOException, JSONException {
-        List<NodeDescription> nodes = new ArrayList<NodeDescription>();
-        NodeDescription child = new NodeDescription();
-        child.setName("p1");
-        nodes.add(child);
-        child = new NodeDescription();
-        child.setName("p2");
-        nodes.add(child);
-
-        String json = this.toJsonObject(nodes).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(nodes, node.getChildren());
-    }
-
-    public void testChildWithProperty() throws IOException, JSONException {
-        List<NodeDescription> nodes = new ArrayList<NodeDescription>();
-        NodeDescription child = new NodeDescription();
-        child.setName("c1");
-        PropertyDescription prop = new PropertyDescription();
-        prop.setName("c1p1");
-        prop.setValue("c1v1");
-        child.addProperty(prop);
-        nodes.add(child);
-
-        String json = this.toJsonObject(nodes).toString();
-
-        NodeDescription node = this.parse(json);
-        assertNotNull("Expecting node", node);
-        assertEquals(nodes, node.getChildren());
-    }
-
-    //---------- internal helper ----------------------------------------------
-
-    private NodeDescription parse(String json) throws IOException {
-        String charSet = "ISO-8859-1";
-        json = "#" + charSet + "\r\n" + json;
-        InputStream ins = new ByteArrayInputStream(json.getBytes(charSet));
-        return this.jsonReader.parse(ins);
-    }
-
-    private Set<Object> toSet(Object[] content) {
-        Set<Object> set = new HashSet<Object>();
-        for (int i=0; content != null && i < content.length; i++) {
-            set.add(content[i]);
-        }
-
-        return set;
-    }
-
-    private JSONArray toJsonArray(Collection<?> set) throws JSONException {
-        List<Object> list = new ArrayList<Object>();
-        for (Object item : set) {
-            if (item instanceof NodeDescription) {
-                list.add(this.toJsonObject((NodeDescription) item));
-            } else {
-                list.add(item);
-            }
-        }
-        return new JSONArray(list);
-    }
-
-    private JSONObject toJsonObject(Collection<?> set) throws JSONException {
-        JSONObject obj = new JSONObject();
-        if (set != null) {
-            for (Object next: set) {
-                String name = this.getName(next);
-                obj.putOpt(name, this.toJsonObject(next));
-            }
-        }
-        return obj;
-    }
-
-    private Object toJsonObject(Object object) throws JSONException {
-        if (object instanceof NodeDescription) {
-            return this.toJsonObject((NodeDescription) object);
-        } else if (object instanceof PropertyDescription) {
-            return this.toJsonObject((PropertyDescription) object);
-        }
-
-        // fall back to string representation
-        return String.valueOf(object);
-    }
-
-    private JSONObject toJsonObject(NodeDescription node) throws JSONException {
-        JSONObject obj = new JSONObject();
-
-        if (node.getPrimaryNodeType() != null) {
-            obj.putOpt("jcr:primaryType", node.getPrimaryNodeType());
-        }
-
-        if (node.getMixinNodeTypes() != null) {
-            obj.putOpt("jcr:mixinTypes", this.toJsonArray(node.getMixinNodeTypes()));
-        }
-
-        if (node.getProperties() != null) {
-            for (PropertyDescription prop : node.getProperties()) {
-                obj.put(prop.getName(), toJsonObject(prop));
-            }
-        }
-
-        if (node.getChildren() != null) {
-            for (NodeDescription child : node.getChildren()) {
-                obj.put(child.getName(), toJsonObject(child));
-            }
-        }
-
-        return obj;
-    }
-
-    private Object toJsonObject(PropertyDescription property) throws JSONException {
-        if (!property.isMultiValue() && PropertyType.TYPENAME_STRING.equals(property.getType())) {
-            return this.toJsonObject(property.getValue());
-        }
-        Object obj;
-        if (property.isMultiValue()) {
-            obj = this.toJsonArray(property.getValues());
-        } else {
-            obj = this.toJsonObject(property.getValue());
-        }
-
-        return obj;
-    }
-
-    private String getName(Object object) {
-        if (object instanceof NodeDescription) {
-            return ((NodeDescription) object).getName();
-        } else if (object instanceof PropertyDescription) {
-            return ((PropertyDescription) object).getName();
-        }
-
-        // fall back to string representation
-        return String.valueOf(object);
-    }
-}
diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
new file mode 100644
index 0000000..5b3463f
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.runner.RunWith;
+
+@RunWith(JMock.class)
+public class JsonReaderTest {
+
+    JsonReader jsonReader;
+
+    Mockery mockery = new JUnit4Mockery();
+
+    ContentCreator creator;
+
+    Sequence mySequence;
+
+    @org.junit.Before public void setUp() throws Exception {
+        this.jsonReader = new JsonReader();
+        this.creator = this.mockery.mock(ContentCreator.class);
+        this.mySequence = this.mockery.sequence("my-sequence");
+    }
+
+    @org.junit.After public void tearDown() throws Exception {
+        this.jsonReader = null;
+    }
+
+    @org.junit.Test public void testEmptyObject() throws Exception {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse("");
+    }
+
+    @org.junit.Test public void testEmpty() throws IOException, RepositoryException {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse("{}");
+    }
+
+    @org.junit.Test public void testDefaultPrimaryNodeTypeWithSurroundWhitespace() throws Exception {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        String json = "     {  }     ";
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testDefaultPrimaryNodeTypeWithoutEnclosingBracesWithSurroundWhitespace() throws Exception {
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        String json = "             ";
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testExplicitePrimaryNodeType() throws Exception {
+        final String type = "xyz:testType";
+        String json = "{ \"jcr:primaryType\": \"" + type + "\" }";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, type, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testMixinNodeTypes1() throws Exception {
+        final String[] mixins = new String[]{ "xyz:mix1" };
+        String json = "{ \"jcr:mixinTypes\": " + this.toJsonArray(mixins) + "}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, mixins); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testMixinNodeTypes2() throws Exception {
+        final String[] mixins = new String[]{ "xyz:mix1", "abc:mix2" };
+        String json = "{ \"jcr:mixinTypes\": " + this.toJsonArray(mixins) + "}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, mixins); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesEmpty() throws Exception {
+        String json = "{ \"property\": \"\"}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("property", PropertyType.STRING, ""); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesSingleValue() throws Exception {
+        String json = "{ \"p1\": \"v1\"}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, "v1"); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesTwoSingleValue() throws Exception {
+        String json = "{ \"p1\": \"v1\", \"p2\": \"v2\"}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, "v1"); inSequence(mySequence);
+            allowing(creator).createProperty("p2", PropertyType.STRING, "v2"); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesMultiValue() throws Exception {
+        String json = "{ \"p1\": [\"v1\"]}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, new String[] {"v1"}); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testPropertiesMultiValueEmpty() throws Exception {
+        String json = "{ \"p1\": []}";
+
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createProperty("p1", PropertyType.STRING, new String[0]); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testChild() throws Exception {
+        String json = "{ " +
+                      " c1 : {}" +
+                      "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testChildWithMixin() throws Exception {
+        String json = "{ " +
+        " c1 : {" +
+              "\"jcr:mixinTypes\" : [\"xyz:TestType\"]" +
+              "}" +
+        "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, new String[] {"xyz:TestType"}); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testTwoChildren() throws Exception {
+        String json = "{ " +
+        " c1 : {}," +
+        " c2 : {}" +
+        "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).createNode("c2", null, null); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    @org.junit.Test public void testChildWithProperty() throws Exception {
+        String json = "{ " +
+        " c1 : {" +
+        "      c1p1 : \"v1\"" +
+              "}" +
+        "}";
+        this.mockery.checking(new Expectations() {{
+            allowing(creator).createNode(null, null, null); inSequence(mySequence);
+            allowing(creator).createNode("c1", null, null); inSequence(mySequence);
+            allowing(creator).createProperty("c1p1", PropertyType.STRING, "v1");
+            allowing(creator).finishNode(); inSequence(mySequence);
+            allowing(creator).finishNode(); inSequence(mySequence);
+        }});
+        this.parse(json);
+    }
+
+    //---------- internal helper ----------------------------------------------
+
+    private void parse(String json) throws IOException, RepositoryException {
+        String charSet = "ISO-8859-1";
+        json = "#" + charSet + "\r\n" + json;
+        InputStream ins = new ByteArrayInputStream(json.getBytes(charSet));
+        this.jsonReader.parse(ins, this.creator);
+    }
+
+    private JSONArray toJsonArray(String[] array) throws JSONException {
+        return new JSONArray(Arrays.asList(array));
+    }
+}

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

[sling-org-apache-sling-jcr-contentloader] 09/36: Use released parent pom where possible.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 551076782a69cb5684dded04b62c2a1992fcbd08
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 12:07:27 2008 +0000

    Use released parent pom where possible.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670529 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index cc17851..7da5dc6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>4-incubator-SNAPSHOT</version>
+        <version>3-incubator</version>
         <relativePath>../../parent/pom.xml</relativePath>
     </parent>
 

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

[sling-org-apache-sling-jcr-contentloader] 32/36: SLING-857 - test case for XmlReader using an XSLT transform contributed by Vidar S. Ramdal, thanks!

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit e0a5ce483c1f37c0c52d20d04e64f41a6d9d9c1c
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Mon Mar 2 10:46:25 2009 +0000

    SLING-857 - test case for XmlReader using an XSLT transform contributed by Vidar S. Ramdal, thanks!
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader@749266 13f79535-47bb-0310-9956-ffa450edef68
---
 .../internal/readers/XmlReaderTest.java            | 76 ++++++++++++++++++++++
 src/test/resources/reader/sample.xml               | 32 +++++++++
 src/test/resources/reader/sample.xsl               | 58 +++++++++++++++++
 3 files changed, 166 insertions(+)

diff --git a/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java b/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
new file mode 100644
index 0000000..554c5ea
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/internal/readers/XmlReaderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.contentloader.internal.readers;
+
+import junit.framework.TestCase;
+import org.apache.sling.jcr.contentloader.internal.ContentCreator;
+
+import javax.jcr.RepositoryException;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+
+/**
+ * 	Test the XmlReader with an XSLT transform
+ */
+public class XmlReaderTest extends TestCase {
+
+    public void testXmlReader() throws Exception {
+        XmlReader reader = new XmlReader();
+        File file = new File("src/test/resources/reader/sample.xml");
+        final URL testdata  = file.toURI().toURL();
+        final MockContentCreator creator = new MockContentCreator();
+        reader.parse(testdata, creator);
+        assertEquals("Did not create expected number of nodes", 1, creator.size());
+    }
+
+    @SuppressWarnings("serial")
+	private static class MockContentCreator extends ArrayList<String> implements ContentCreator {
+
+        public MockContentCreator() {
+        }
+
+        public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+            this.add(name);
+        }
+
+        public void finishNode() throws RepositoryException {
+        }
+
+        public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        }
+
+        public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        }
+
+        public void createProperty(String name, Object value) throws RepositoryException {
+        }
+
+        public void createProperty(String name, Object[] values) throws RepositoryException {
+        }
+
+        public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        }
+
+        public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+            return true;
+        }
+    }
+}
diff --git a/src/test/resources/reader/sample.xml b/src/test/resources/reader/sample.xml
new file mode 100644
index 0000000..b0374d1
--- /dev/null
+++ b/src/test/resources/reader/sample.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+ * 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.
+-->
+
+
+<!-- 
+    This needs to be transformed by sample.xsl
+    to create valid node/propertie structures
+    that the standard XML importer can accept.
+-->
+<?xml-stylesheet type="text/xsl" href="sample.xsl"?>
+<pages>
+    <page name="firstpage">
+        <content>some text</content>
+    </page>
+</pages>
\ No newline at end of file
diff --git a/src/test/resources/reader/sample.xsl b/src/test/resources/reader/sample.xsl
new file mode 100644
index 0000000..e9072cc
--- /dev/null
+++ b/src/test/resources/reader/sample.xsl
@@ -0,0 +1,58 @@
+<?xml version="1.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.
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+    <xsl:output indent="yes" />
+
+    <xsl:template match="/">
+        <xsl:apply-templates select="pages/page"/>
+    </xsl:template>
+
+    <xsl:template match="page">
+        <node>
+            <name><xsl:value-of select="@name"/></name>
+            <primaryNodeType>nt:unstructured</primaryNodeType>
+            <mixinNodeTypes/>
+            <properties>
+                <xsl:call-template name="property">
+                    <xsl:with-param name="name">content</xsl:with-param>
+                    <xsl:with-param name="value"><xsl:value-of select="content"/></xsl:with-param>
+                </xsl:call-template>
+                <xsl:call-template name="property">
+                    <xsl:with-param name="name">sling:resourceType</xsl:with-param>
+                    <xsl:with-param name="value">page</xsl:with-param>
+                </xsl:call-template>
+            </properties>
+        </node>
+    </xsl:template>
+
+    <xsl:template name="property">
+        <xsl:param name="name"/>
+        <xsl:param name="value"/>
+        <property>
+            <name><xsl:value-of select="$name"/></name>
+            <type>String</type>
+            <value><xsl:value-of select="$value"/></value>
+        </property>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file

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

[sling-org-apache-sling-jcr-contentloader] 24/36: SLING-811 Reduce DependencyManagement in the parent pom and add explicit versions to the modules

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit f72f256474ba8e2ea690cdfff1009772f3a98149
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Wed Jan 7 14:41:02 2009 +0000

    SLING-811 Reduce DependencyManagement in the parent pom and add explicit versions to the modules
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@732352 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/pom.xml b/pom.xml
index 7da9926..4f724f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -121,11 +121,19 @@
         <dependency>
             <groupId>net.sf.kxml</groupId>
             <artifactId>kxml2</artifactId>
+            <version>2.2.2</version>
             <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>xmlpull</groupId>
+                    <artifactId>xmlpull</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
+            <version>1.4</version>
         </dependency>
     <!-- Testing -->        
         <dependency>

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

[sling-org-apache-sling-jcr-contentloader] 04/36: Use latest snapshots again to make project buildable.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 9f2975dcd4671cabd37c8b2342221a2527268e3d
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue Jun 17 08:55:12 2008 +0000

    Use latest snapshots again to make project buildable.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@668571 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index d46c665..16fdfc8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>3-incubator</version>
+        <version>4-incubator-SNAPSHOT</version>
         <relativePath>../../parent/pom.xml</relativePath>
     </parent>
 
@@ -92,27 +92,27 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.api</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.3-incubator-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.3-incubator-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.mime</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.3-incubator-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.3-incubator-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.engine</artifactId>
-            <version>2.0.2-incubator</version>
+            <version>2.0.3-incubator-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>

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

[sling-org-apache-sling-jcr-contentloader] 23/36: Update notice files to include 2009 in copyright statement.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 43b65164d61dc93d2170eb981b7fbcac47bec01d
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jan 7 09:07:02 2009 +0000

    Update notice files to include 2009 in copyright statement.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@732280 13f79535-47bb-0310-9956-ffa450edef68
---
 NOTICE                             | 2 +-
 src/main/resources/META-INF/NOTICE | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/NOTICE b/NOTICE
index f31ecff..4543b0a 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
 Apache Sling Initial Content Loader
-Copyright 2008 The Apache Software Foundation
+Copyright 2008-2009 The Apache Software Foundation
 
 Apache Sling is based on source code originally developed 
 by Day Software (http://www.day.com/).
diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE
index fdc2970..da7213c 100644
--- a/src/main/resources/META-INF/NOTICE
+++ b/src/main/resources/META-INF/NOTICE
@@ -1,5 +1,5 @@
 Apache Sling Initial Content Loader
-Copyright 2008 The Apache Software Foundation
+Copyright 2008-2009 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-contentloader] 14/36: Create path if it is not existend

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit bb27019a34f4e478488f93dd791e8b8fe3d95116
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Jun 25 15:52:27 2008 +0000

    Create path if it is not existend
    
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@671589 13f79535-47bb-0310-9956-ffa450edef68
---
 .../apache/sling/jcr/contentloader/internal/Loader.java   | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
index fa7c191..46af1f8 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/Loader.java
@@ -35,6 +35,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.StringTokenizer;
 
 import javax.jcr.InvalidSerializedDataException;
 import javax.jcr.Item;
@@ -557,9 +558,21 @@ public class Loader {
 
         int firstSlash = path.indexOf("/");
 
-        // it´s a relative path
+        // it's a relative path
         if (firstSlash != 0) path = "/" + path;
 
+        if ( !session.itemExists(path) ) {
+            Node currentNode = session.getRootNode();
+            final StringTokenizer st = new StringTokenizer(path.substring(1), "/");
+            while ( st.hasMoreTokens() ) {
+                final String name = st.nextToken();
+                if ( !currentNode.hasNode(name) ) {
+                    currentNode.addNode(name, "nt:folder");
+                }
+                currentNode = currentNode.getNode(name);
+            }
+            return currentNode;
+        }
         Item item = session.getItem(path);
         return (item.isNode()) ? (Node) item : null;
     }

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

[sling-org-apache-sling-jcr-contentloader] 36/36: Move Sling to new TLP location

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 79cb02b7ec99f57840822ff3bb9201edfe00cfaf
Merge: d153db5 295304a
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Thu Jun 18 09:21:02 2009 +0000

    Move Sling to new TLP location
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator@785979 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-contentloader] 08/36: Use released versions.

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 9fbe62f32cd2695b7c66c76f06aa839cc073d51a
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Jun 23 11:56:25 2008 +0000

    Use released versions.
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/jcr/contentloader@670527 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            | 10 +--
 .../jcr/contentloader/internal/ZipReader.java      | 76 ++++++++++++++++++++++
 2 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index 3705a01..cc17851 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,27 +92,27 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.api</artifactId>
-            <version>2.0.3-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.3-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.mime</artifactId>
-            <version>2.0.3-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.0.3-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.engine</artifactId>
-            <version>2.0.3-incubator-SNAPSHOT</version>
+            <version>2.0.2-incubator</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
new file mode 100644
index 0000000..176c9bb
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/internal/ZipReader.java
@@ -0,0 +1,76 @@
+/*
+ * 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.contentloader.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.jcr.RepositoryException;
+
+
+/**
+ * The <code>JsonReader</code> TODO
+ */
+class ZipReader implements ContentReader {
+
+    static final ImportProvider ZIP_PROVIDER = new ImportProvider() {
+        private ZipReader zipReader;
+
+        public ContentReader getReader() {
+            if (zipReader == null) {
+                zipReader = new ZipReader(false);
+            }
+            return zipReader;
+        }
+    };
+
+    static final ImportProvider JAR_PROVIDER = new ImportProvider() {
+        private ZipReader zipReader;
+
+        public ContentReader getReader() {
+            if (zipReader == null) {
+                zipReader = new ZipReader(true);
+            }
+            return zipReader;
+        }
+    };
+
+    /** Is this a jar reader? */
+    private final boolean jarReader;
+
+    public ZipReader(boolean jarReader) {
+        this.jarReader = jarReader;
+    }
+
+    public void parse(InputStream ins, ContentCreator creator)
+    throws IOException, RepositoryException {
+        final ZipInputStream zis = new ZipInputStream(ins);
+        ZipEntry entry;
+        do {
+            entry = zis.getNextEntry();
+            if ( entry != null ) {
+                entry.getName();
+            }
+
+        } while ( entry != null );
+    }
+
+}

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

[sling-org-apache-sling-jcr-contentloader] 34/36: [maven-release-plugin] copy for tag org.apache.sling.jcr.contentloader-2.0.4-incubator

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.contentloader-2.0.4-incubator
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git

commit 295304a53ca6a4d609502d24150da441f9872f70
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Tue May 5 15:36:17 2009 +0000

    [maven-release-plugin]  copy for tag org.apache.sling.jcr.contentloader-2.0.4-incubator
    
    git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator@771819 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index dda6027..88a7753 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,12 +22,12 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>5-incubator-SNAPSHOT</version>
+        <version>5-incubator</version>
         <relativePath>../../../parent/pom.xml</relativePath>
     </parent>
 
     <artifactId>org.apache.sling.jcr.contentloader</artifactId>
-    <version>2.0.3-incubator-SNAPSHOT</version>
+    <version>2.0.4-incubator</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Initial Content Loader</name>
@@ -36,9 +36,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/contentloader</developerConnection>
-        <url>http://svn.apache.org/viewvc/incubator/sling/trunk/bundles/jcr/contentloader</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator</developerConnection>
+        <url>http://svn.apache.org/viewvc/incubator/sling/tags/org.apache.sling.jcr.contentloader-2.0.4-incubator</url>
     </scm>
 
     <build>

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