You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sling.apache.org by GitBox <gi...@apache.org> on 2021/12/14 11:14:29 UTC

[GitHub] [sling-org-apache-sling-feature-cpconverter] anchela commented on a change in pull request #122: Sling 10931

anchela commented on a change in pull request #122:
URL: https://github.com/apache/sling-org-apache-sling-feature-cpconverter/pull/122#discussion_r768553597



##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {

Review comment:
       what is the order param for? repo-init doesn't allow to specify the order/index of a given entry. if some order is defined in the initial content you would need to order the granted/denied entries accordingly and then pray that it's going to be ok...... alternatively: abort because the order cannot be reflected as repo-init statement.

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            String path = this.currentNode.getPath();
+            
+            repoInitTextSB.append("set ACL for " + principal +"\n\n");
+         
+            for(String privilege: grantedPrivileges){
+                repoInitTextSB.append("allow " + privilege + " on " + path + "\n");
+            }
+            for(String privilege: deniedPrivileges){
+                repoInitTextSB.append("deny " + privilege + " on " + path + "\n");
+            }
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principalId, String[] grantedPrivilegeNames, String[] deniedPrivilegeNames,
+                          String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions,
+                          Set<String> removedRestrictionNames) throws RepositoryException {

Review comment:
       hm.... not sure how the 'removedRestrictionNames' is supposed to be used :(

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/RestrictionProvider.java
##########
@@ -0,0 +1,45 @@
+/*
+ * 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.feature.cpconverter.accesscontrol;
+
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinitionImpl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.*;
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.REP_ITEM_NAMES;
+
+public class RestrictionProvider {

Review comment:
       hi @niekraaijmakers , the set of restrictions defined in a given oak repository setup is configurable and cp-to-featuremodel-converter should not make any assumptions about what restrictions may or may not be used.
   
   maybe you can elaborate, why this is needed? IMHO this class should not be required at all.....

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/accesscontrol/RestrictionProvider.java
##########
@@ -0,0 +1,45 @@
+/*
+ * 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.feature.cpconverter.accesscontrol;
+
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinitionImpl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants.*;

Review comment:
       IMHO * imports should be avoided

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {

Review comment:
       i would stronly recommend to use @NotNull and @Nullable annotations to clarify the contract (that applies to all methods you added)

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)

Review comment:
       IMHO repoinit will not work properly if 'members' is null or an empty array. so, you should check if there are any members to be added (and again nullability annotations would help)

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);

Review comment:
       again about the nulllability annotations: if nothing is specified 'extraProperties' can be null. so you need to check in order to avoid NPE. if you define it to be @NotNull -> check for map being empty to avoid redundant set properties statements

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");

Review comment:
       IMHO the set property part should be conditional and only present if the specified map is not null and not empty. (see comment about nullability annotations to clarify what kind of input can be expected

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            String path = this.currentNode.getPath();
+            
+            repoInitTextSB.append("set ACL for " + principal +"\n\n");
+         
+            for(String privilege: grantedPrivileges){
+                repoInitTextSB.append("allow " + privilege + " on " + path + "\n");

Review comment:
       what about restrictions that might be defined for a given entry?

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2

Review comment:
       this comment is misleading. 'set principal ACL' can only be used for service users. since sling-initial content only knows about regular users/groups 'set ACL' needs to be used. 

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");

Review comment:
       same as comment regarding user properties above.

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")

Review comment:
       couldn't the password be null? in this case the 'with password' clause should be omitted.

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            String path = this.currentNode.getPath();
+            
+            repoInitTextSB.append("set ACL for " + principal +"\n\n");
+         
+            for(String privilege: grantedPrivileges){
+                repoInitTextSB.append("allow " + privilege + " on " + path + "\n");
+            }
+            for(String privilege: deniedPrivileges){
+                repoInitTextSB.append("deny " + privilege + " on " + path + "\n");

Review comment:
       what about restrictions that might be defined for a given entry

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            String path = this.currentNode.getPath();
+            
+            repoInitTextSB.append("set ACL for " + principal +"\n\n");
+         
+            for(String privilege: grantedPrivileges){
+                repoInitTextSB.append("allow " + privilege + " on " + path + "\n");
+            }
+            for(String privilege: deniedPrivileges){
+                repoInitTextSB.append("deny " + privilege + " on " + path + "\n");
+            }
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principalId, String[] grantedPrivilegeNames, String[] deniedPrivilegeNames,

Review comment:
       - a principal only has a name and never an ID. please use 'principalName' or 'principal' (but keep it consistent with the other 'createAce' method
   - order: same as above
   - not sure i understand how this method is expected to work.... restrictions are always associated with a given single entry..... it doesn't make sense to pass separate maps of single/mv-restrictions and have an array of granted/denied privs..... it should be 'privileges', boolean isAllow plus the restrictions.

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            String path = this.currentNode.getPath();
+            
+            repoInitTextSB.append("set ACL for " + principal +"\n\n");
+         
+            for(String privilege: grantedPrivileges){
+                repoInitTextSB.append("allow " + privilege + " on " + path + "\n");
+            }
+            for(String privilege: deniedPrivileges){
+                repoInitTextSB.append("deny " + privilege + " on " + path + "\n");
+            }
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principalId, String[] grantedPrivilegeNames, String[] deniedPrivilegeNames,
+                          String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions,
+                          Set<String> removedRestrictionNames) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB.append("set principal ACL for " + principalId +"\n\n");
+            String path = this.currentNode.getPath();
+            if(grantedPrivilegeNames != null){

Review comment:
       - please reformat the code that whitespace is consistent (here and below is an extra space missing)
   - this whole block should IMHO be removed.... it doesn't make sense if you pass restrictions 

##########
File path: src/main/java/org/apache/sling/feature/cpconverter/handlers/slinginitialcontent/VaultContentXMLContentCreator.java
##########
@@ -0,0 +1,366 @@
+/*
+ * 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.feature.cpconverter.handlers.slinginitialcontent;
+
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.feature.cpconverter.ConverterException;
+import org.apache.sling.feature.cpconverter.handlers.slinginitialcontent.xmlbuffer.XMLNode;
+import org.apache.sling.feature.cpconverter.shared.CheckedConsumer;
+import org.apache.sling.feature.cpconverter.vltpkg.JcrNamespaceRegistry;
+import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
+import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.xml.stream.XMLStreamException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * ContentCreator substitute to create valid XML files to be packaged into a VaultPackage to be installed later
+ */
+public class VaultContentXMLContentCreator implements ContentCreator {
+    
+    private final static Pattern DATE_PATTERN = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}(.*)");
+    private final static Logger logger = LoggerFactory.getLogger(BundleSlingInitialContentExtractor.class);
+
+    private final String repositoryPath;
+    private final OutputStream targetOutputStream;
+    private final VaultPackageAssembler packageAssembler;
+    private final LinkedList<XMLNode> parentNodePathStack = new LinkedList<>();
+    private final JcrNamespaceRegistry namespaceRegistry;
+    private final CheckedConsumer<String> repoInitTextExtensionConsumer;
+    private boolean isFirstElement = true;
+    private boolean finished = false;
+    private boolean xmlProcessed = false;
+    private String primaryNodeName;
+    private XMLNode currentNode;
+
+    public VaultContentXMLContentCreator(String repositoryPath, OutputStream targetOutputStream, JcrNamespaceRegistry namespaceRegistry, VaultPackageAssembler packageAssembler, CheckedConsumer<String> repoInitTextExtensionConsumer) throws XMLStreamException, RepositoryException {
+        this.repositoryPath = repositoryPath;
+        this.targetOutputStream = targetOutputStream;
+        this.packageAssembler = packageAssembler;
+        this.namespaceRegistry = namespaceRegistry;
+        this.repoInitTextExtensionConsumer = repoInitTextExtensionConsumer;
+    } 
+    
+    public void setIsXmlProcessed(){
+        this.xmlProcessed = true;
+    }
+    
+    @Override
+    public void createNode(String name, String primaryNodeType, String[] mixinNodeTypes) throws RepositoryException {
+        
+        final String elementName;
+        final String jcrNodeName; 
+        if(xmlProcessed && isFirstElement){
+            elementName = "jcr:root";
+            primaryNodeName = name;
+            jcrNodeName = name;
+            isFirstElement = false;
+        }else if(StringUtils.isNotBlank(name)){
+            elementName = name;
+            jcrNodeName = name;
+        }else{
+            elementName = "jcr:root";
+            jcrNodeName = null;
+        }
+
+        
+        final String basePath;
+        if(parentNodePathStack.isEmpty()){
+            basePath = repositoryPath;
+        }else{
+            StringBuilder basePathBuilder = new StringBuilder(repositoryPath);
+            for(Iterator<XMLNode> xmlNodeIterator =  parentNodePathStack.descendingIterator();xmlNodeIterator.hasNext();){
+                XMLNode parent = xmlNodeIterator.next();
+                String parentJcrNodeName = parent.getJcrNodeName();
+                if(StringUtils.isNotBlank(parentJcrNodeName)){
+                    basePathBuilder.append("/");
+                    basePathBuilder.append(parentJcrNodeName);
+                }
+            }
+            basePath = basePathBuilder.toString();
+        }
+       
+        XMLNode intermediateNode = new XMLNode(packageAssembler,basePath,elementName,jcrNodeName, primaryNodeType, mixinNodeTypes);
+        //add the created node to the correct parent if present
+        if(currentNode != null){
+            currentNode.addChildNode(elementName, intermediateNode);
+        }
+        //switch the current node 
+        currentNode = intermediateNode;
+        
+        currentNode.addProperty(JcrConstants.JCR_PRIMARYTYPE, StringUtils.isNotBlank(primaryNodeType) ? primaryNodeType : JcrConstants.NT_UNSTRUCTURED);
+        
+        if(ArrayUtils.isNotEmpty(mixinNodeTypes)){
+            currentNode.addProperty(JcrConstants.JCR_MIXINTYPES, "[" + String.join(",", mixinNodeTypes) + "]");
+        }
+
+        parentNodePathStack.push(currentNode);
+    }
+
+    public String getPrimaryNodeName() {
+        return primaryNodeName;
+    }
+
+    @Override
+    public void finishNode() throws RepositoryException {
+        if(parentNodePathStack.size() > 1){
+            this.parentNodePathStack.pop();
+        }
+        this.currentNode = this.parentNodePathStack.peek();
+    }
+
+    @Override
+    public void finish() throws RepositoryException {
+        
+        if(finished){
+            return;
+        }
+        try {
+            XMLNodeToXMLFileWriter writer = new XMLNodeToXMLFileWriter(currentNode, targetOutputStream, namespaceRegistry);
+            writer.write();
+            finished = true;
+        } catch (XMLStreamException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+  
+
+    @Override
+    public void createProperty(String name, int propertyType, String value) throws RepositoryException {
+        currentNode.addProperty(name,propertyType,value);
+    }
+
+   
+    @Override
+    public void createProperty(String name, int propertyType, String[] values) throws RepositoryException {
+        currentNode.addProperty(name, propertyType, values);
+    }
+    
+   
+    @Override
+    public void createProperty(String name, Object value)  throws RepositoryException {
+        currentNode.addProperty(name, value);
+    }
+    
+    
+    @Override
+    public void createProperty(String name, Object[] values) throws RepositoryException {
+        currentNode.addProperty(name, values);
+    }
+
+    @Override
+    public void createFileAndResourceNode(String name, InputStream data, String mimeType, long lastModified) throws RepositoryException {
+        this.createNode(name, JcrConstants.NT_FILE, null);
+        this.createNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE, null);
+
+        // ensure sensible last modification date
+        if (lastModified <= 0) {
+            lastModified = System.currentTimeMillis();
+        }
+        this.createProperty(JcrConstants.JCR_MIMETYPE, mimeType);
+        this.createProperty(JcrConstants.JCR_LASTMODIFIED, lastModified);
+        this.createProperty(JcrConstants.JCR_DATA, data);
+    }
+    
+
+    @Override
+    public boolean switchCurrentNode(String subPath, String newNodeType) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUser(String name, String password, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create user " + name + " with password " + password + " \n\n")
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createGroup(String name, String[] members, Map<String, Object> extraProperties) throws RepositoryException {
+        try {
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB  .append("create group " + name + " \n\n")
+                            .append("add " + String.join(",", members) + " to group " + name)
+                            .append("set properties on authorizable(" + name + ")\n");
+            extraProperties.entrySet().stream().map(this::getSetPropertyString).forEach(repoInitTextSB::append);
+            repoInitTextSB.append("end");
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principal, String[] grantedPrivileges, String[] deniedPrivileges, String order) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            String path = this.currentNode.getPath();
+            
+            repoInitTextSB.append("set ACL for " + principal +"\n\n");
+         
+            for(String privilege: grantedPrivileges){
+                repoInitTextSB.append("allow " + privilege + " on " + path + "\n");
+            }
+            for(String privilege: deniedPrivileges){
+                repoInitTextSB.append("deny " + privilege + " on " + path + "\n");
+            }
+            repoInitTextSB.append("end");
+            
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    @Override
+    public void createAce(String principalId, String[] grantedPrivilegeNames, String[] deniedPrivilegeNames,
+                          String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions,
+                          Set<String> removedRestrictionNames) throws RepositoryException {
+        try {
+            //set principal ACL for principal1,principal2
+            //allow jcr:read
+            StringBuilder repoInitTextSB = new StringBuilder();
+            repoInitTextSB.append("set principal ACL for " + principalId +"\n\n");
+            String path = this.currentNode.getPath();
+            if(grantedPrivilegeNames != null){
+                for(String privilege: grantedPrivilegeNames){
+                    repoInitTextSB.append("allow " + privilege + " on " + path);
+                }
+            }else if(deniedPrivilegeNames != null){
+                for(String privilege: deniedPrivilegeNames){
+                    repoInitTextSB.append("deny " + privilege + " on " + path);
+                }
+            }
+           
+
+            
+            if (((grantedPrivilegeNames != null) || (deniedPrivilegeNames != null)) && (MapUtils.isNotEmpty(restrictions) || MapUtils.isNotEmpty(mvRestrictions))) {
+              
+                if(MapUtils.isNotEmpty(mvRestrictions)){
+                   
+                    Iterator<Map.Entry<String,Value[]>> iterator = mvRestrictions.entrySet().iterator();
+                    while(iterator.hasNext()){
+                        Map.Entry<String,Value[]> entry = iterator.next();
+                        repoInitTextSB.append(appendRestrictionString(entry.getKey(), entry.getValue()));
+                    }
+                    
+                    
+                }else{
+                    Iterator<Map.Entry<String,Value>> iterator = restrictions.entrySet().iterator();
+                    while(iterator.hasNext()){
+                        Map.Entry<String,Value> entry = iterator.next();
+                        repoInitTextSB.append(appendRestrictionString(entry.getKey(), entry.getValue()));
+                    }
+                }
+                
+            }
+            repoInitTextSB.append("\nend");
+
+            repoInitTextExtensionConsumer.accept(repoInitTextSB.toString());
+        } catch (IOException | ConverterException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    private String appendRestrictionString(String key, Value[] values){
+        Collection<String> stringValues = new ArrayList<>();
+        stringValues.add(key);
+        for(Value value: values){
+            try {
+                String valueAsString = value.getString();
+                stringValues.add(valueAsString);
+            } catch (RepositoryException e) {
+                logger.error("error appending restriction", e);
+            }
+        }
+
+        return " restriction(" +
+                stringValues.stream()
+                .map( Object::toString )
+                .collect( Collectors.joining( "," ) )
+        + ")";
+    }
+
+    private String appendRestrictionString(String key, Value value){

Review comment:
       duplicated code with appendRestrictionString(String key, Value[] values).
   if you want to keep 2 methods, there is no need to build a collection of strings and joing them. you have just 1 value :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@sling.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org