You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2021/08/23 15:35:05 UTC

[jackrabbit-filevault] 01/01: JCRVLT-551 try to set UUID from package

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

kwin pushed a commit to branch feature/JCRVLT-551-keep-jcr-uuid
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit 0506000fcc5a3213fc26a1047a05159d6b309896
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Mon Aug 23 17:34:48 2021 +0200

    JCRVLT-551 try to set UUID from package
    
    add additional import options to tweak UUID collision behavior
---
 .../jackrabbit/vault/fs/api/IdConflictPolicy.java  |  29 ++
 .../jackrabbit/vault/fs/api/package-info.java      |   2 +-
 .../vault/fs/impl/io/AbstractArtifactHandler.java  |   9 +-
 .../vault/fs/impl/io/DocViewSAXImporter.java       | 334 +++++++++++----------
 .../vault/fs/impl/io/FileArtifactHandler.java      |  12 +-
 .../vault/fs/impl/io/FolderArtifactHandler.java    |   5 +-
 .../vault/fs/impl/io/GenericArtifactHandler.java   |   5 +-
 .../vault/fs/impl/io/JcrSysViewTransformer.java    |   7 +-
 .../jackrabbit/vault/fs/impl/io/NodeStash.java     |  76 +++--
 .../apache/jackrabbit/vault/fs/io/AutoSave.java    |   2 +-
 .../jackrabbit/vault/fs/io/ImportOptions.java      |  30 +-
 .../apache/jackrabbit/vault/fs/io/Importer.java    |  11 +-
 .../vault/packaging/impl/ZipVaultPackage.java      |   6 +-
 .../apache/jackrabbit/vault/util/DocViewNode.java  |   1 +
 .../vault/packaging/integration/ImportIT.java      |   3 +-
 .../ReferenceableIdentifiersImportIT.java          | 283 +++++++++++++++++
 .../referenceable.zip/META-INF/vault/config.xml    |  93 ++++++
 .../META-INF/vault/definition/.content.xml         |  25 ++
 .../referenceable.zip/META-INF/vault/filter.xml    |   4 +
 .../referenceable.zip/META-INF/vault/nodetypes.cnd |  17 ++
 .../META-INF/vault/properties.xml                  |  18 ++
 .../referenceable.zip/jcr_root/.content.xml        |   6 +
 .../referenceable.zip/jcr_root/tmp/.content.xml    |   4 +
 .../jcr_root/tmp/referenceable.xml                 |  12 +
 24 files changed, 786 insertions(+), 208 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/IdConflictPolicy.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/IdConflictPolicy.java
new file mode 100644
index 0000000..83abb3a
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/IdConflictPolicy.java
@@ -0,0 +1,29 @@
+/*
+ * 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.jackrabbit.vault.fs.api;
+
+import javax.jcr.ImportUUIDBehavior;
+
+public enum IdConflictPolicy {
+    /** Default handling, fail in case of unresolvable conflicts. Conflicts are automatically resolved in case the conflicting UUID and all its references are inside the package filter. */
+    FAIL,
+    /** Create a new ID for the imported node (for all referenceable nodes), this may break existing references in the package. This fails if a referenced node is overwritten by a node with a different id */
+    CREATE_NEW_ID,
+    /** Remove the node with the conflicting id along with its references (even if outside the filters). This goes beyond {@link ImportUUIDBehavior#IMPORT_UUID_COLLISION_REMOVE_EXISTING}, as it also does not only resolve UUID collisions but also replacements of referenceable nodes with different ids.
+     * Use with care, as this may remove references outside the filter. */
+    FORCE_REMOVE_CONFLICTING_ID
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java
index 38c4265..48709de 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/api/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.9.0")
+@Version("2.10.0")
 package org.apache.jackrabbit.vault.fs.api;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/AbstractArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/AbstractArtifactHandler.java
index 4cf7f94..40eab88 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/AbstractArtifactHandler.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/AbstractArtifactHandler.java
@@ -36,8 +36,10 @@ import org.apache.jackrabbit.vault.fs.api.ImportInfo;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl;
 import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
 import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
 import org.apache.jackrabbit.vault.fs.spi.ServiceProviderFactory;
+import org.jetbrains.annotations.NotNull;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
@@ -119,7 +121,7 @@ public abstract class AbstractArtifactHandler implements ArtifactHandler, Dumpab
             throws RepositoryException, IOException {
         Node node = file.getNode();
         String name = node.getName();
-        return accept(file.getManager().getWorkspaceFilter(),
+        return accept(new ImportOptions(), file.getManager().getWorkspaceFilter(),
                 name.length() == 0 ? node : node.getParent(),
                 name, (ArtifactSetImpl) artifacts);
     }
@@ -131,13 +133,14 @@ public abstract class AbstractArtifactHandler implements ArtifactHandler, Dumpab
                              ArtifactSet artifacts)
             throws RepositoryException, IOException {
         Node node = parent.getNode();
-        return accept(parent.getManager().getWorkspaceFilter(),
+        return accept(new ImportOptions(), parent.getManager().getWorkspaceFilter(),
                 node, name, (ArtifactSetImpl) artifacts);
     }
 
     /**
      * Imports an artifact set below the node.
      *
+     * @param option the import options
      * @param wspFilter the workspace filter
      * @param parent the parent node
      * @param name the name of the (new) import
@@ -146,7 +149,7 @@ public abstract class AbstractArtifactHandler implements ArtifactHandler, Dumpab
      * @throws RepositoryException if an error occurs.
      * @throws IOException if an I/O error occurs.
      */
-    protected abstract ImportInfoImpl accept(WorkspaceFilter wspFilter, Node parent,
+    protected abstract ImportInfoImpl accept(@NotNull ImportOptions options, WorkspaceFilter wspFilter, Node parent,
                                          String name, ArtifactSetImpl artifacts)
             throws RepositoryException, IOException;
 
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java
index e2aa8a2..f9405c8 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewSAXImporter.java
@@ -20,8 +20,8 @@ package org.apache.jackrabbit.vault.fs.impl.io;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -29,8 +29,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 import javax.jcr.ImportUUIDBehavior;
 import javax.jcr.Item;
@@ -41,6 +39,7 @@ import javax.jcr.NodeIterator;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
+import javax.jcr.ReferentialIntegrityException;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -49,13 +48,16 @@ import javax.jcr.ValueFactory;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.PropertyDefinition;
+
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.util.ISO9075;
+import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.vault.fs.api.Artifact;
 import org.apache.jackrabbit.vault.fs.api.ArtifactType;
+import org.apache.jackrabbit.vault.fs.api.IdConflictPolicy;
 import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.api.ItemFilterSet;
 import org.apache.jackrabbit.vault.fs.api.NodeNameList;
@@ -74,7 +76,6 @@ import org.apache.jackrabbit.vault.util.EffectiveNodeType;
 import org.apache.jackrabbit.vault.util.JcrConstants;
 import org.apache.jackrabbit.vault.util.MimeTypes;
 import org.apache.jackrabbit.vault.util.RejectingEntityDefaultHandler;
-import org.apache.jackrabbit.util.Text;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -240,6 +241,8 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
      */
     private final JcrNamespaceHelper nsHelper;
 
+    private final IdConflictPolicy idConflictPolicy;
+
     /**
      * Creates a new importer that will receive SAX events and imports the
      * items below the given root.
@@ -251,7 +254,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
      * @throws RepositoryException if an error occurs.
      */
     public DocViewSAXImporter(Node parentNode, String rootNodeName,
-                              ArtifactSetImpl artifacts, WorkspaceFilter wspFilter)
+                              ArtifactSetImpl artifacts, WorkspaceFilter wspFilter, IdConflictPolicy idConflictPolicy)
             throws RepositoryException {
         this.filter = artifacts.getCoverage();
         this.wspFilter = wspFilter;
@@ -264,6 +267,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
         this.snsSupported = session.getRepository().
                 getDescriptorValue(Repository.NODE_TYPE_MANAGEMENT_SAME_NAME_SIBLINGS_SUPPORTED).getBoolean();
         this.nsHelper = new JcrNamespaceHelper(session, null);
+        this.idConflictPolicy = idConflictPolicy;
 
         String rootPath = parentNode.getPath();
         if (!rootPath.equals("/")) {
@@ -809,81 +813,39 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
     private StackElement addNode(DocViewNode ni) throws RepositoryException, IOException {
         final Node currentNode = stack.getNode();
 
-        // find old node
-        Node oldNode = null;
-        Node node = null;
+        Node existingNode = null;
         if ("".equals(ni.label)) {
             // special case for root node update
-            node = currentNode;
-        } else if (ni.uuid == null) {
-            if (stack.checkForNode() && currentNode.hasNode(ni.label)) {
-                node = currentNode.getNode(ni.label);
-            }
+            existingNode = currentNode;
         } else {
-            try {
-                node = session.getNodeByUUID(ni.uuid);
-                if (!node.getParent().isSame(currentNode)) {
-                    log.warn("Packaged node at {} is referenceable and collides with existing node at {}. Will create new UUID.",
-                            currentNode.getPath() + "/" + ni.label,
-                            node.getPath());
-                    ni.uuid = null;
-                    ni.props.remove(JcrConstants.JCR_UUID);
-                    ni.props.remove(JcrConstants.JCR_BASEVERSION);
-                    ni.props.remove(JcrConstants.JCR_PREDECESSORS);
-                    ni.props.remove(JcrConstants.JCR_SUCCESSORS);
-                    ni.props.remove(JcrConstants.JCR_VERSIONHISTORY);
-                    node = null;
-                }
-            } catch (ItemNotFoundException e) {
-                // ignore
-            }
-            if (node == null) {
-                if (stack.checkForNode() && currentNode.hasNode(ni.label)) {
-                    node = currentNode.getNode(ni.label);
-                }
-            } else {
-                if (!node.getName().equals(ni.name)) {
-                    // if names mismatches => replace
-                    oldNode = node;
-                    node = null;
-                }
-
+            if (stack.checkForNode() && currentNode.hasNode(ni.label)) {
+                existingNode = currentNode.getNode(ni.label);
             }
-        }
-        // if old node is not included in the package, ignore rewrite
-        if (oldNode != null && !isIncluded(oldNode, oldNode.getDepth() - rootDepth)) {
-            node = oldNode;
-            oldNode = null;
-        }
-
-        // TODO: under which condition do we end up here?
-        if (oldNode != null) {
-            // check versionable
-            new VersioningState(stack, oldNode).ensureCheckedOut();
-
-            String oldNodePath = oldNode.getPath();
-            ImportMode importMode = wspFilter.getImportMode(oldNodePath);
-            NodeStash recovery = new NodeStash(session, oldNodePath, importMode);
-            recovery.stash();
-
-            // ensure that existing binaries are not sourced from a property
-            // that is about to be removed
-            Map<String, DocViewSAXImporter.BlobInfo> blobs = binaries.get(oldNode.getPath());
-            if (blobs != null) {
-                for (DocViewSAXImporter.BlobInfo info : blobs.values()) {
-                    info.detach();
+            if (ni.uuid != null) {
+                if (idConflictPolicy == IdConflictPolicy.FAIL) {
+                    try {
+                        // does uuid already exist in the repo?
+                        Node sameIdNode = session.getNodeByIdentifier(ni.uuid);
+                        // edge-case: same node path -> uuid is kept
+                        if (existingNode != null && existingNode.getPath().equals(sameIdNode.getPath())) {
+                            log.debug("Node with existing identifier is being updated without modifying its uuid");
+                        } else {
+                            // uuid found in path covered by filter
+                            if (isIncluded(sameIdNode, 0)) {
+                                log.warn("Node identifier {} for to-be imported node {} already taken by {}, trying to release it.", ni.uuid, currentNode.getPath() + '/' + ni.label, sameIdNode.getPath());
+                                removeReferences(sameIdNode);
+                                session.removeItem(sameIdNode.getPath());
+                                existingNode = null;
+                            } else {
+                                // uuid found in path not-covered by filter
+                                throw new ReferentialIntegrityException("UUID already taken by node " + sameIdNode.getPath());
+                            }
+                        }
+                    } catch (ItemNotFoundException e) {
+                        // ignore
+                    }
                 }
             }
-
-            oldNode.remove();
-            // now create the new node
-            node = createNewNode(currentNode, ni);
-
-            // move the children back
-            recovery.recover(importInfo);
-
-            importInfo.onReplaced(node.getPath());
-            return new StackElement(node, false);
         }
 
         // check if new node needs to be checked in
@@ -891,7 +853,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
         boolean isCheckedIn = coProp != null && "false".equals(coProp.values[0]);
 
         // create or update node
-        boolean isNew = node == null;
+        boolean isNew = existingNode == null;
         if (isNew) {
             // workaround for bug in jcr2spi if mixins are empty
             if (!ni.props.containsKey(JcrConstants.JCR_MIXINTYPES)) {
@@ -900,126 +862,178 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             }
 
             stack.ensureCheckedOut();
-            node = createNewNode(currentNode, ni);
-            if (node.getDefinition() == null) {
+            existingNode = createNewNode(currentNode, ni);
+            if (existingNode.getDefinition() == null) {
                 throw new RepositoryException("Child node not allowed.");
             }
-            if (node.isNodeType(JcrConstants.NT_RESOURCE)) {
-                if (!node.hasProperty(JcrConstants.JCR_DATA)) {
-                    importInfo.onMissing(node.getPath() + "/" + JcrConstants.JCR_DATA);
+            if (existingNode.isNodeType(JcrConstants.NT_RESOURCE)) {
+                if (!existingNode.hasProperty(JcrConstants.JCR_DATA)) {
+                    importInfo.onMissing(existingNode.getPath() + "/" + JcrConstants.JCR_DATA);
                 }
             } else if (isCheckedIn) {
                 // don't rely on isVersionable here, since SPI might not have this info yet
-                importInfo.registerToVersion(node.getPath());
+                importInfo.registerToVersion(existingNode.getPath());
             }
-            importInfo.onCreated(node.getPath());
+            importInfo.onCreated(existingNode.getPath());
 
-        } else if (isIncluded(node, node.getDepth() - rootDepth)) {
+        } else if (isIncluded(existingNode, existingNode.getDepth() - rootDepth)) {
             if (isCheckedIn) {
                 // don't rely on isVersionable here, since SPI might not have this info yet
-                importInfo.registerToVersion(node.getPath());
+                importInfo.registerToVersion(existingNode.getPath());
             }
-            ImportMode importMode = wspFilter.getImportMode(node.getPath());
-            if (updateExistingNode(node, ni, importMode)) {
-                if (node.isNodeType(JcrConstants.NT_RESOURCE)) {
-                    if (!node.hasProperty(JcrConstants.JCR_DATA)) {
-                        importInfo.onMissing(node.getPath() + "/" + JcrConstants.JCR_DATA);
+            ImportMode importMode = wspFilter.getImportMode(existingNode.getPath());
+            Node updatedNode = updateExistingNode(existingNode, ni, importMode);
+            if (updatedNode != null) {
+                if (updatedNode.isNodeType(JcrConstants.NT_RESOURCE)) {
+                    if (!updatedNode.hasProperty(JcrConstants.JCR_DATA)) {
+                        importInfo.onMissing(existingNode.getPath() + "/" + JcrConstants.JCR_DATA);
                     }
                 }
-                importInfo.onModified(node.getPath());
+                importInfo.onModified(updatedNode.getPath());
+                existingNode = updatedNode;
             } else {
-                importInfo.onNop(node.getPath());
+                importInfo.onNop(existingNode.getPath());
             }
         } else {
             // remove registered binaries outside of the filter (JCR-126)
-            binaries.remove(node.getPath());
+            binaries.remove(existingNode.getPath());
         }
-        return new StackElement(node, isNew);
+        return new StackElement(existingNode, isNew);
     }
 
-    private boolean updateExistingNode(@NotNull Node node, @NotNull DocViewNode ni, @NotNull ImportMode importMode) throws RepositoryException {
-        VersioningState vs = new VersioningState(stack, node);
-        boolean modified = false;
-        // set new primary type (but never set rep:root)
-        if (importMode == ImportMode.REPLACE && !"rep:root".equals(ni.primary)) {
-            if (!node.getPrimaryNodeType().getName().equals(ni.primary)) {
-                vs.ensureCheckedOut();
-                node.setPrimaryType(ni.primary);
-                modified = true;
+    /**
+     * Tries to remove references to the given node but only in case they are included in the filters.
+     * @param node the referenced node
+     * @throws ReferentialIntegrityException in case some references can not be removed (outside filters)
+     * @throws RepositoryException in case some other error occurs
+     */
+    public void removeReferences(@NotNull Node node) throws ReferentialIntegrityException, RepositoryException {
+        Collection<String> removableReferencePaths = new ArrayList<>();
+        PropertyIterator pIter = node.getReferences();
+        while (pIter.hasNext()) {
+            Property referenceProperty = pIter.nextProperty();
+            if (isIncluded(referenceProperty, 0) || idConflictPolicy == IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID) {
+                removableReferencePaths.add(referenceProperty.getPath());
+            } else {
+                throw new ReferentialIntegrityException("Found non-removable reference for conflicting UUID " + node.getIdentifier() + " (" + node.getPath() + ") at " + referenceProperty.getPath());
             }
         }
+        for (String referencePath : removableReferencePaths) {
+            log.info("Remove reference towards {} at {}", node.getIdentifier(), referencePath);
+            session.removeItem(referencePath);
+        }
+    }
 
-        // calculate mixins
-        Set<String> newMixins = new HashSet<>();
-        AccessControlHandling acHandling = getAcHandling(ni.name);
-        if (ni.mixins != null) {
-            for (String mixin : ni.mixins) {
-                // omit name if mix:AccessControllable and CLEAR
-                if (!aclManagement.isAccessControllableMixin(mixin)
-                        || acHandling != AccessControlHandling.CLEAR) {
-                    newMixins.add(mixin);
+    private @Nullable Node updateExistingNode(@NotNull Node node, @NotNull DocViewNode ni, @NotNull ImportMode importMode) throws RepositoryException {
+        VersioningState vs = new VersioningState(stack, node);
+        Node updatedNode = null;
+        // try to set uuid via sysview import if it differs from existing one
+        if (ni.uuid != null && !node.getIdentifier().equals(ni.uuid)) {
+            NodeStash stash = new NodeStash(session, node.getPath());
+            stash.stash();
+            Node parent = node.getParent();
+            removeReferences(node);
+            node.remove();
+            updatedNode = createNewNode(parent, ni);
+            stash.recover(importMode, importInfo);
+        } else {
+            // TODO: is this faster than using sysview import?
+            // set new primary type (but never set rep:root)
+            if (importMode == ImportMode.REPLACE && !"rep:root".equals(ni.primary)) {
+                if (!node.getPrimaryNodeType().getName().equals(ni.primary)) {
+                    vs.ensureCheckedOut();
+                    node.setPrimaryType(ni.primary);
+                    updatedNode = node;
                 }
             }
-        }
-        // remove mixin not in package
-        if (importMode == ImportMode.REPLACE) {
-            for (NodeType mix : node.getMixinNodeTypes()) {
-                String name = mix.getName();
-                if (!newMixins.remove(name)) {
-                    // special check for mix:AccessControllable
-                    if (!aclManagement.isAccessControllableMixin(name)
-                            || acHandling == AccessControlHandling.CLEAR
-                            || acHandling == AccessControlHandling.OVERWRITE) {
-                        vs.ensureCheckedOut();
-                        node.removeMixin(name);
-                        modified = true;
+            // calculate mixins to be added
+            Set<String> newMixins = new HashSet<>();
+            AccessControlHandling acHandling = getAcHandling(ni.name);
+            if (ni.mixins != null) {
+                for (String mixin : ni.mixins) {
+                    // omit name if mix:AccessControllable and CLEAR
+                    if (!aclManagement.isAccessControllableMixin(mixin)
+                            || acHandling != AccessControlHandling.CLEAR) {
+                        newMixins.add(mixin);
                     }
                 }
             }
-        }
-        
-        // add remaining mixins (for all import modes)
-        for (String mixin : newMixins) {
-            vs.ensureCheckedOut();
-            node.addMixin(mixin);
-            modified = true;
-        }
-
-        // remove properties not in package (which are not protected)
-        if (importMode == ImportMode.REPLACE) {
-            PropertyIterator pIter = node.getProperties();
-            while (pIter.hasNext()) {
-                Property p = pIter.nextProperty();
-                String propName = p.getName();
-                if (!p.getDefinition().isProtected()
-                        && !ni.props.containsKey(propName)
-                        && !preserveProperties.contains(p.getPath())
-                        && wspFilter.includesProperty(p.getPath())) {
-                    vs.ensureCheckedOut();
-                    p.remove();
-                    modified = true;
+            // remove mixins not in package (only for mode = replace)
+            if (importMode == ImportMode.REPLACE) {
+                for (NodeType mix : node.getMixinNodeTypes()) {
+                    String name = mix.getName();
+                    if (!newMixins.remove(name)) {
+                        // special check for mix:AccessControllable
+                        if (!aclManagement.isAccessControllableMixin(name)
+                                || acHandling == AccessControlHandling.CLEAR
+                                || acHandling == AccessControlHandling.OVERWRITE) {
+                            vs.ensureCheckedOut();
+                            node.removeMixin(name);
+                            updatedNode = node;
+                        }
+                    }
+                }
+            }
+            // add remaining mixins (for all import modes)
+            for (String mixin : newMixins) {
+                vs.ensureCheckedOut();
+                node.addMixin(mixin);
+                updatedNode = node;
+            }
+    
+            // remove unprotected properties not in package (only for mode = replace)
+            if (importMode == ImportMode.REPLACE) {
+                PropertyIterator pIter = node.getProperties();
+                while (pIter.hasNext()) {
+                    Property p = pIter.nextProperty();
+                    String propName = p.getName();
+                    if (!p.getDefinition().isProtected()
+                            && !ni.props.containsKey(propName)
+                            && !preserveProperties.contains(p.getPath())
+                            && wspFilter.includesProperty(p.getPath())) {
+                        vs.ensureCheckedOut();
+                        p.remove();
+                        updatedNode = node;
+                    }
                 }
             }
+            
+            // add/modify properties contained in package
+            if (setUnprotectedProperties(node, ni, importMode == ImportMode.REPLACE|| importMode == ImportMode.UPDATE || importMode == ImportMode.UPDATE_PROPERTIES, vs)) {
+                updatedNode = node;
+            }
         }
-        // add/modify properties contained in package
-        modified |= setUnprotectedProperties(node, ni, importMode == ImportMode.REPLACE|| importMode == ImportMode.UPDATE || importMode == ImportMode.UPDATE_PROPERTIES, vs);
-        return modified;
+        return updatedNode;
     }
+
     /**
      * Creates a new node via system view XML and {@link Session#importXML(String, InputStream, int)} to be able to set protected properties. 
-     * Afterwards uses regular JCR API to set unprotected properties.
-     * @param currentNode
-     * @param ni
-     * @return
+     * Afterwards uses regular JCR API to set unprotected properties (on a best-effort basis as this depends on the repo implementation).
+     * @param parentNode the parent node below which the new node should be created
+     * @param ni the information about the new node to be created
+     * @return the newly created node
      * @throws RepositoryException
      */
-    private @NotNull Node createNewNode(Node currentNode, DocViewNode ni)
+    private @NotNull Node createNewNode(Node parentNode, DocViewNode ni)
             throws RepositoryException {
+        final int importUuidBehavior;
+        switch(idConflictPolicy) {
+            case CREATE_NEW_ID:
+                // what happens to references?
+                importUuidBehavior = ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW;
+                break;
+            case FORCE_REMOVE_CONFLICTING_ID:
+                importUuidBehavior = ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING;
+                break;
+            default:
+                importUuidBehavior = ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW;
+                break;
+        }
         try {
-            String parentPath = currentNode.getPath();
+            String parentPath = parentNode.getPath();
             final ContentHandler handler = session.getImportContentHandler(
                     parentPath,
-                    ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
+                    importUuidBehavior);
             // first define the current namespaces
             String[] prefixes = session.getNamespacePrefixes();
             handler.startDocument();
@@ -1080,7 +1094,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
             handler.endDocument();
 
             // retrieve newly created node either by uuid, label or name
-            Node node = getNodeByUUIDLabelOrName(currentNode, ni);
+            Node node = getNodeByUUIDLabelOrName(parentNode, ni, importUuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
             setUnprotectedProperties(node, ni, true, null);
             // remove mix referenceable if it was temporarily added
             if (addMixRef) {
@@ -1094,7 +1108,7 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
                 if (root instanceof ConstraintViolationException) {
                     // potentially rollback changes in the transient space (only relevant for Oak, https://issues.apache.org/jira/browse/OAK-9436), as otherwise the same exception is thrown again at Session.save()
                     try {
-                        Node node = getNodeByUUIDLabelOrName(currentNode, ni);
+                        Node node = getNodeByUUIDLabelOrName(parentNode, ni, importUuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
                         node.remove();
                     } catch (RepositoryException re) {
                         // ignore as no node found when the transient space is clean already
@@ -1121,9 +1135,9 @@ public class DocViewSAXImporter extends RejectingEntityDefaultHandler implements
         return effectiveNodeType.getApplicablePropertyDefinition(docViewProperty.name, docViewProperty.isMulti, docViewProperty.type).map(PropertyDefinition::isProtected).orElse(false);
     }
 
-    private Node getNodeByUUIDLabelOrName(@NotNull Node currentNode, @NotNull DocViewNode ni) throws RepositoryException {
+    private Node getNodeByUUIDLabelOrName(@NotNull Node currentNode, @NotNull DocViewNode ni, boolean isUuidNewlyAssigned) throws RepositoryException {
         Node node = null;
-        if (ni.uuid != null) {
+        if (ni.uuid != null && !isUuidNewlyAssigned) {
             try {
                 node = currentNode.getSession().getNodeByUUID(ni.uuid);
             } catch (RepositoryException e) {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java
index 6baaa84..7486ad7 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FileArtifactHandler.java
@@ -40,9 +40,11 @@ import org.apache.jackrabbit.vault.fs.api.ItemFilterSet;
 import org.apache.jackrabbit.vault.fs.api.SerializationType;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
 import org.apache.jackrabbit.vault.util.JcrConstants;
 import org.apache.jackrabbit.vault.util.MimeTypes;
 import org.apache.jackrabbit.vault.util.PathUtil;
+import org.jetbrains.annotations.NotNull;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
@@ -111,7 +113,7 @@ public class FileArtifactHandler extends AbstractArtifactHandler  {
      * <p>
      * Handles generic artifact sets
      */
-    public ImportInfoImpl accept(WorkspaceFilter wspFilter, Node parent,
+    public ImportInfoImpl accept(@NotNull ImportOptions options, WorkspaceFilter wspFilter, Node parent,
                                 String name, ArtifactSetImpl artifacts)
             throws RepositoryException, IOException {
         // check if any file artifacts was removed
@@ -155,7 +157,7 @@ public class FileArtifactHandler extends AbstractArtifactHandler  {
                 if (mode != ImportMode.MERGE) {
                     InputSource source = primary.getInputSource();
                     if (source != null) {
-                        info.merge(importDocView(parent, source, artifacts, wspFilter));
+                        info.merge(importDocView(parent, source, artifacts, wspFilter, options));
                     }
                 } else {
                     info.onNop(path);
@@ -226,7 +228,7 @@ public class FileArtifactHandler extends AbstractArtifactHandler  {
                     }
                     if (mode != ImportMode.MERGE) {
                         try {
-                            DocViewSAXImporter handler = new DocViewSAXImporter(newParent, newName, newSet, wspFilter);
+                            DocViewSAXImporter handler = new DocViewSAXImporter(newParent, newName, newSet, wspFilter, options.getIdConflictPolicy());
                             handler.setAclHandling(getAcHandling());
                             handler.setCugHandling(getCugHandling());
                             parseXmlWithSaxHandler(file.getInputSource(), handler);
@@ -313,14 +315,14 @@ public class FileArtifactHandler extends AbstractArtifactHandler  {
     }
 
     private ImportInfoImpl importDocView(Node parent, InputSource source,
-                                     ArtifactSetImpl artifacts, WorkspaceFilter wspFilter)
+                                     ArtifactSetImpl artifacts, WorkspaceFilter wspFilter, ImportOptions options)
             throws RepositoryException, IOException {
         String rootName = artifacts.getPrimaryData().getRelativePath();
         int idx = rootName.indexOf('/');
         if (idx > 0) {
             rootName = rootName.substring(0, idx);
         }
-        DocViewSAXImporter handler = new DocViewSAXImporter(parent, rootName, artifacts, wspFilter);
+        DocViewSAXImporter handler = new DocViewSAXImporter(parent, rootName, artifacts, wspFilter, options.getIdConflictPolicy());
         handler.setAclHandling(getAcHandling());
         handler.setCugHandling(getCugHandling());
         try {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java
index c0a9119..f64cc0c 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/FolderArtifactHandler.java
@@ -36,8 +36,10 @@ import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl;
 import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
 import org.apache.jackrabbit.vault.util.EffectiveNodeType;
 import org.apache.jackrabbit.vault.util.JcrConstants;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Handles artifact sets with just a directory.
@@ -88,7 +90,8 @@ public class FolderArtifactHandler extends AbstractArtifactHandler {
      *
      * Handles generic artifact sets
      */
-    public ImportInfoImpl accept(WorkspaceFilter wspFilter, Node parent, String name,
+    @Override
+    public ImportInfoImpl accept(@NotNull ImportOptions options, WorkspaceFilter wspFilter, Node parent, String name,
                              ArtifactSetImpl artifacts)
             throws RepositoryException, IOException {
         Artifact dir = artifacts.getDirectory();
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/GenericArtifactHandler.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/GenericArtifactHandler.java
index b8763b9..518bbac 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/GenericArtifactHandler.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/GenericArtifactHandler.java
@@ -28,6 +28,7 @@ import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.api.SerializationType;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.fs.impl.ArtifactSetImpl;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
 import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
 import org.apache.jackrabbit.vault.fs.spi.ServiceProviderFactory;
 import org.apache.jackrabbit.vault.fs.spi.UserManagement;
@@ -57,7 +58,7 @@ public class GenericArtifactHandler extends AbstractArtifactHandler {
      *
      * Handles generic artifact sets
      */
-    public ImportInfoImpl accept(WorkspaceFilter wspFilter, Node parent,
+    public ImportInfoImpl accept(ImportOptions options, WorkspaceFilter wspFilter, Node parent,
                                  String name, ArtifactSetImpl artifacts)
             throws RepositoryException, IOException {
         Artifact primary = artifacts.getPrimaryData();
@@ -88,7 +89,7 @@ public class GenericArtifactHandler extends AbstractArtifactHandler {
                 }
             }
             try {
-                DocViewSAXImporter handler = new DocViewSAXImporter(parent, name, artifacts, wspFilter);
+                DocViewSAXImporter handler = new DocViewSAXImporter(parent, name, artifacts, wspFilter, options.getIdConflictPolicy());
                 handler.setAclHandling(getAcHandling());
                 handler.setCugHandling(getCugHandling());
                 parseXmlWithSaxHandler(source, handler);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
index 0fce44e..11dea93 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
@@ -70,6 +70,8 @@ public class JcrSysViewTransformer implements DocViewAdapter {
     private final String existingPath;
 
     private final Set<String> excludedNodeNames = new HashSet<String>();
+    
+    private final @NotNull ImportMode importMode;
 
     private long ignoreLevel = 0;
 
@@ -96,10 +98,11 @@ public class JcrSysViewTransformer implements DocViewAdapter {
         this.existingPath = existingPath;
         if (existingPath != null) {
             // check if there is an existing node with the name
-            recovery = new NodeStash(session, existingPath, importMode).excludeName("rep:cache");
+            recovery = new NodeStash(session, existingPath).excludeName("rep:cache");
             recovery.stash();
         }
         excludeNode("rep:cache");
+        this.importMode = importMode;
     }
 
     public List<String> close() throws SAXException {
@@ -120,7 +123,7 @@ public class JcrSysViewTransformer implements DocViewAdapter {
         // check for rescued child nodes
         if (recovery != null) {
             try {
-                recovery.recover(null);
+                recovery.recover(importMode, null);
             } catch (RepositoryException e) {
                 log.error("Error while processing rescued child nodes");
             } finally {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java
index b44c7c0..2028c9b 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/NodeStash.java
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.vault.fs.impl.io;
 
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import javax.jcr.Node;
@@ -25,10 +27,12 @@ import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.Value;
 
 import org.apache.jackrabbit.vault.fs.api.ImportInfo;
 import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.util.JcrConstants;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,8 +51,6 @@ public class NodeStash {
 
     private final String path;
 
-    private final ImportMode importMode;
-
     private Node tmpNode;
 
     private final Set<String> excludedNodeName = new HashSet<>();
@@ -60,14 +62,18 @@ public class NodeStash {
     private static final String[] ROOTS = {"/", "/tmp", "/var", "/etc", "/content"};
 
 
+    /** The property names of those protected properties which should be stashed (and later restored) */
+    private static final List<String> PROTECTED_PROPERTIES_TO_STASH = Arrays.asList(JcrConstants.JCR_MIXINTYPES);
+
+    private static final String PROTECTED_PROPERTIES_SUFFIX = "-stashed";
+
     /**
      * Creates a new stash utility class which takes care of child nodes and properties in {@code path}
      * @param session session to operate on
      */
-    public NodeStash(Session session, String path, ImportMode importMode) {
+    public NodeStash(Session session, String path) {
         this.session = session;
         this.path = path;
-        this.importMode  = importMode;
     }
 
     /**
@@ -91,7 +97,7 @@ public class NodeStash {
     }
 
     /**
-     * Adds the given name to the set of excluded child node names. the nodes that are excluded are not saved
+     * Adds the given name to the set of excluded child node names. The nodes that are excluded are not saved
      * in the stash.
      * @param name The name of the node to exclude
      * @return "this" suitable for chaining.
@@ -103,11 +109,9 @@ public class NodeStash {
 
     /**
      * Moves the child nodes and optionally properties of the path to a temporary location.
+     * @return the stashed node's primary type (if it needs to be kept)
      */
-    public void stash() {
-        if (importMode == ImportMode.REPLACE) {
-            return;
-        }
+    public @Nullable String stash() {
         try {
             Node parent = session.getNode(path);
             Node tmp = getOrCreateTemporaryNode();
@@ -129,26 +133,38 @@ public class NodeStash {
             PropertyIterator propIterator = parent.getProperties();
             while (propIterator.hasNext()) {
                 Property property = propIterator.nextProperty();
+                // all unprotected and some protected properties are relevant
+                String stashPropertyName;
                 if (!property.getDefinition().isProtected()) {
+                    stashPropertyName = property.getName();
+                } else if (PROTECTED_PROPERTIES_TO_STASH.contains(property.getName())) {
+                    stashPropertyName = property.getName() + PROTECTED_PROPERTIES_SUFFIX;
+                } else {
+                    stashPropertyName = null;
+                }
+                if (stashPropertyName != null) {
                     if (property.isMultiple()) {
-                        tmp.setProperty(property.getName(), property.getValues(), property.getType());
+                        tmp.setProperty(stashPropertyName, property.getValues(), property.getType());
                     } else {
-                        tmp.setProperty(property.getName(), property.getValue(), property.getType());
+                        tmp.setProperty(stashPropertyName, property.getValue(), property.getType());
                     }
-                }
+                } 
             }
+            return parent.getPrimaryNodeType().getName();
         } catch (RepositoryException e) {
             log.warn("error while moving child nodes (ignored)", e);
+            return null;
         }
     }
 
     /**
      * Moves the stashed nodes/properties back below the original path.
+     * @param importMode the import mode for the node this stash refers to
      * @param importInfo the import info to record the changes
      * @throws RepositoryException if an error occurs
      */
-    public void recover(@Nullable ImportInfo importInfo) throws RepositoryException {
-        // move the old child nodes back
+    public void recover(@NotNull ImportMode importMode, @Nullable ImportInfo importInfo) throws RepositoryException {
+        // move the old child nodes back (independent of importMode)
         if (tmpNode != null) {
             Node parent = session.getNode(path);
             NodeIterator iter = tmpNode.getNodes();
@@ -171,14 +187,16 @@ public class NodeStash {
                     }
                 }
             }
-            try {
-                recoverProperties(importMode==ImportMode.MERGE || importMode == ImportMode.MERGE_PROPERTIES);
-            } catch (RepositoryException e) {
-                log.warn("Unable to restore properties at {} due to: {}. Properties will remain in temporary location: {}",
-                        path, e.getMessage(), tmpNode.getPath());
-                if (importInfo != null) {
-                    importInfo.onError(path, e);
-                    hasErrors = true;
+            if (importMode != ImportMode.REPLACE) {
+                try {
+                    recoverProperties(importMode==ImportMode.MERGE || importMode == ImportMode.MERGE_PROPERTIES);
+                } catch (RepositoryException e) {
+                    log.warn("Unable to restore properties at {} due to: {}. Properties will remain in temporary location: {}",
+                            path, e.getMessage(), tmpNode.getPath());
+                    if (importInfo != null) {
+                        importInfo.onError(path, e);
+                        hasErrors = true;
+                    }
                 }
             }
             if (!hasErrors) {
@@ -188,11 +206,19 @@ public class NodeStash {
     }
     
     private void recoverProperties(boolean overwriteNewOnes) throws RepositoryException {
-        PropertyIterator propIterator = tmpNode.getProperties();
         Node destNode = session.getNode(path);
+
+        // restore mixins
+        Property mixinProperty = tmpNode.hasProperty(JcrConstants.JCR_MIXINTYPES + PROTECTED_PROPERTIES_SUFFIX) ? tmpNode.getProperty(JcrConstants.JCR_MIXINTYPES + PROTECTED_PROPERTIES_SUFFIX) : null;
+        if (mixinProperty != null) {
+            for (Value value : mixinProperty.getValues()) {
+                tmpNode.addMixin(value.getString());
+            }
+        }
+        PropertyIterator propIterator = tmpNode.getProperties();
         while (propIterator.hasNext()) {
             Property property = propIterator.nextProperty();
-            if (!property.getDefinition().isProtected()) {
+            if (!property.getDefinition().isProtected() && !property.getName().endsWith(PROTECTED_PROPERTIES_SUFFIX)) {
                 if (!overwriteNewOnes && destNode.hasProperty(property.getName())) {
                     log.debug("Skipping restore property {} as it has been updated", property.getPath());
                     continue;
@@ -202,7 +228,7 @@ public class NodeStash {
                 } else {
                     destNode.setProperty(property.getName(), property.getValue(), property.getType());
                 }
-            }
+            } 
         }
     }
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/AutoSave.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/AutoSave.java
index c2bf40b..6c7d23a 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/AutoSave.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/AutoSave.java
@@ -194,7 +194,7 @@ public class AutoSave {
                 try {
                     session.save();
                 } catch (RepositoryException e) {
-                    log.error("error during auto save - retrying after refresh...");
+                    log.error("error during auto save: {} - retrying after refresh...", e.getMessage());
                     session.refresh(true);
                     session.save();
                 }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java
index 73edd7d..54f3acd 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java
@@ -22,11 +22,13 @@ import java.io.IOException;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
+import org.apache.jackrabbit.vault.fs.api.IdConflictPolicy;
 import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.api.PathMapping;
 import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
 import org.apache.jackrabbit.vault.packaging.DependencyHandling;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -66,6 +68,8 @@ public class ImportOptions {
 
     private DependencyHandling dependencyHandling = null;
 
+    private @NotNull IdConflictPolicy idConflictPolicy = IdConflictPolicy.FAIL;
+
     /**
      * Default constructor.
      */
@@ -96,6 +100,7 @@ public class ImportOptions {
             hookClassLoader = base.hookClassLoader;
             pathMapping = base.pathMapping;
             dependencyHandling = base.dependencyHandling;
+            idConflictPolicy = base.idConflictPolicy;
         }
     }
 
@@ -121,6 +126,7 @@ public class ImportOptions {
         ret.hookClassLoader = hookClassLoader;
         ret.pathMapping = pathMapping;
         ret.dependencyHandling = dependencyHandling;
+        ret.idConflictPolicy = idConflictPolicy;
         return ret;
     }
 
@@ -421,6 +427,24 @@ public class ImportOptions {
         this.dependencyHandling = dependencyHandling;
     }
 
+    /**
+     * 
+     * @return the id conflict policy
+     * @since 3.5.1
+     */
+    public @NotNull IdConflictPolicy getIdConflictPolicy() {
+        return idConflictPolicy;
+    }
+
+    /**
+     * Sets the id conflict policy (in case of unresolveable conflicts).
+     * @param idConflictPolicy the conflict policy
+     * @since 3.5.1
+     */
+    public void setIdConflictPolicy(@NotNull IdConflictPolicy idConflictPolicy) {
+        this.idConflictPolicy = idConflictPolicy;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -440,6 +464,7 @@ public class ImportOptions {
         result = prime * result + (patchKeepInRepo ? 1231 : 1237);
         result = prime * result + ((patchParentPath == null) ? 0 : patchParentPath.hashCode());
         result = prime * result + ((pathMapping == null) ? 0 : pathMapping.hashCode());
+        result = prime * result + ((idConflictPolicy == null) ? 0 : idConflictPolicy.hashCode());
         result = prime * result + (strict ? 1231 : 1237);
         return result;
     }
@@ -506,6 +531,8 @@ public class ImportOptions {
             return false;
         if (strict != other.strict)
             return false;
+        if (!idConflictPolicy.equals(other.idConflictPolicy))
+            return false;
         return true;
     }
 
@@ -521,7 +548,8 @@ public class ImportOptions {
                 + (cndPattern != null ? "cndPattern=" + cndPattern + ", " : "") + (filter != null ? "filter=" + filter + ", " : "")
                 + (hookClassLoader != null ? "hookClassLoader=" + hookClassLoader + ", " : "")
                 + (pathMapping != null ? "pathMapping=" + pathMapping + ", " : "")
-                + (dependencyHandling != null ? "dependencyHandling=" + dependencyHandling : "") + "]";
+                + (dependencyHandling != null ? "dependencyHandling=" + dependencyHandling + ", " : "")
+                + "idConflictPolicy=" + idConflictPolicy + "]";
     }
     
     
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
index 2e4f533..4777004 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
@@ -78,6 +78,7 @@ import org.apache.jackrabbit.vault.fs.spi.PrivilegeInstaller;
 import org.apache.jackrabbit.vault.fs.spi.ProgressTracker;
 import org.apache.jackrabbit.vault.fs.spi.ServiceProviderFactory;
 import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.apache.jackrabbit.vault.packaging.PackageException;
 import org.apache.jackrabbit.vault.packaging.impl.ActivityLog;
 import org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry;
 import org.apache.jackrabbit.vault.util.Constants;
@@ -915,7 +916,7 @@ public class Importer {
                 imp = new ImportInfoImpl();
                 imp.onError(info.path, new IllegalStateException("Parent node not found."));
             } else {
-                imp = genericHandler.accept(filter, node, info.artifacts.getPrimaryData().getRelativePath(), info.artifacts);
+                imp = genericHandler.accept(opts, filter, node, info.artifacts.getPrimaryData().getRelativePath(), info.artifacts);
                 if (imp == null) {
                     throw new IllegalStateException("generic handler did not accept " + info.path);
                 }
@@ -946,12 +947,12 @@ public class Importer {
                     log.trace("skipping intermediate node at {}", info.path);
                 } else if (info.artifacts.getPrimaryData() == null) {
                     // create nt:folder node if not exists
-                    imp = folderHandler.accept(filter, node, info.name,  info.artifacts);
+                    imp = folderHandler.accept(opts, filter, node, info.name,  info.artifacts);
                     if (imp == null) {
                         throw new IllegalStateException("folder handler did not accept " + info.path);
                     }
                 } else {
-                    imp = genericHandler.accept(filter, node, info.artifacts.getDirectory().getRelativePath(), info.artifacts);
+                    imp = genericHandler.accept(opts, filter, node, info.artifacts.getDirectory().getRelativePath(), info.artifacts);
                     if (imp == null) {
                         throw new IllegalStateException("generic handler did not accept " + info.path);
                     }
@@ -963,7 +964,7 @@ public class Importer {
                 imp = new ImportInfoImpl();
                 imp.onError(info.path, new IllegalStateException("Parent node not found."));
             } else {
-                imp = fileHandler.accept(filter, node, info.name,  info.artifacts);
+                imp = fileHandler.accept(opts, filter, node, info.name,  info.artifacts);
                 if (imp == null) {
                     throw new IllegalStateException("file handler did not accept " + info.path);
                 }
@@ -1013,7 +1014,7 @@ public class Importer {
                         }
                         hasErrors = true;
                         if (firstException == null) {
-                            firstException = error;
+                            firstException = new PackageException("Error creating/updating node " + path, error);
                         }
                         break;
                 }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
index 610c0ca..11642d5 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/packaging/impl/ZipVaultPackage.java
@@ -279,7 +279,11 @@ public class ZipVaultPackage extends PackagePropertiesImpl implements VaultPacka
                 log.error("Error during install.", e);
                 ctx.setPhase(InstallContext.Phase.INSTALL_FAILED);
                 hooks.execute(ctx);
-                throw new PackageException(e);
+                if (e instanceof RepositoryException) {
+                    throw (RepositoryException)e;
+                } else {
+                    throw new PackageException(e);
+                }
             }
             ctx.setPhase(InstallContext.Phase.INSTALLED);
             if (!hooks.execute(ctx)) {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java
index a7a7b90..09c0771 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewNode.java
@@ -38,6 +38,7 @@ import org.xml.sax.Attributes;
 public class DocViewNode {
 
     public final @NotNull String name;
+    /** usually equal to {@link #name} except when this node has a same name sibling, in that case label has format {@code <name>[index]}, https://docs.adobe.com/content/docs/en/spec/jcr/2.0/22_Same-Name_Siblings.html#22.2%20Addressing%20Same-Name%20Siblings%20by%20Path */
     public final @NotNull String label;
     public final @NotNull Map<String, DocViewProperty> props = new HashMap<>();
     public @Nullable String uuid;
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportIT.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportIT.java
index 9a66bbf..42ffe08 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportIT.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ImportIT.java
@@ -44,6 +44,7 @@ import org.apache.jackrabbit.vault.fs.io.Importer;
 import org.apache.jackrabbit.vault.fs.io.JcrArchive;
 import org.apache.jackrabbit.vault.fs.io.ZipArchive;
 import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.codehaus.plexus.util.ExceptionUtils;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
@@ -377,7 +378,7 @@ public class ImportIT extends IntegrationTestBase {
             archive.open(true);
             // we don't care whether constraint is immediately enforced or only on save() as both is valid according to JCR spec
             RepositoryException e = Assert.assertThrows(RepositoryException.class, () -> { importer.run(archive, rootNode); admin.save(); });
-            assertEquals(ConstraintViolationException.class, e.getCause().getClass());
+            assertEquals(ConstraintViolationException.class, ExceptionUtils.getRootCause(e).getClass());
         }
     }
 }
\ No newline at end of file
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java
new file mode 100644
index 0000000..14469c9
--- /dev/null
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java
@@ -0,0 +1,283 @@
+/*
+ * 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.jackrabbit.vault.packaging.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyIterator;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.vault.fs.api.IdConflictPolicy;
+import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.packaging.PackageException;
+import org.codehaus.plexus.util.ExceptionUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Installs a package with the filter: "/tmp/referenceable", mode="replace"
+ * The package contains two referenceable nodes:
+ * {@code /tmp/referenceable} and
+ * {@code /tmp/referenceable/child-referenceable}.
+ * Both are setting property {@code someproperty="somevalue"}.
+ */
+public class ReferenceableIdentifiersImportIT extends IntegrationTestBase {
+    
+    private static final String PROPERTY_NAME = "someproperty";
+    private static final String PROPERTY_VALUE = "somevalue";
+    private static final String UUID_REFERENCEABLE = "352c89a4-304f-4b87-9bed-e09275597df1";
+    private static final String UUID_REFERENCEABLE_CHILD = "a201bd6b-25b9-4255-b7db-6fc4c3ddb32d";
+
+    @Test
+    public void testOverwriteIdentifierOfReplacedNode() throws RepositoryException, IOException, PackageException {
+        // create referenceable node manually 
+        Node referenceableNode = JcrUtils.getOrCreateByPath("/tmp/referenceable", null, JcrConstants.NT_UNSTRUCTURED, admin, true);
+        referenceableNode.addMixin(JcrConstants.MIX_REFERENCEABLE);
+        
+        // create (non-referenceable) child node
+        JcrUtils.getOrCreateByPath("/tmp/referenceable/child", null, JcrConstants.NT_UNSTRUCTURED, admin, true);
+        admin.save();
+        
+        // check UUID prior package installation
+        assertNotEquals(UUID_REFERENCEABLE, referenceableNode.getIdentifier());
+        
+        // overwrite with new referenceable node (different UUID)
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+
+        assertNodeExists("/tmp/referenceable");
+        assertProperty("/tmp/referenceable/" + PROPERTY_NAME, PROPERTY_VALUE);
+        // check its UUID
+        Node node = admin.getNode("/tmp/referenceable");
+        //assertEquals(UUID_REFERENCEABLE, node.getIdentifier());
+        node = admin.getNode("/tmp/referenceable/child");
+        assertEquals(UUID_REFERENCEABLE_CHILD, node.getIdentifier());
+    }
+
+    @Test
+    public void testIdentifierCollisionOutsideFilter() throws RepositoryException, IOException, PackageException {
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+
+        assertNodeExists("/tmp/referenceable");
+        // check its UUID
+        Node node = admin.getNode("/tmp/referenceable");
+        assertEquals(UUID_REFERENCEABLE, node.getIdentifier());
+
+        // now move node (it keeps its old ID)
+        admin.move("/tmp/referenceable", "/tmp/referenceable-old");
+        admin.save();
+
+        // install package again (with default policy IdConflictPolicy.FAIL)
+        Exception e = assertThrows(Exception.class, () -> { extractVaultPackageStrict("/test-packages/referenceable.zip"); });
+        assertEquals(ReferentialIntegrityException.class, ExceptionUtils.getRootCause(e).getClass());
+        admin.refresh(false);
+
+        // now try to remove the referenced node (with policy IdConflictPolicy.CREATE_NEW_ID)
+        ImportOptions options = getDefaultOptions();
+        options.setStrict(true);
+        options.setIdConflictPolicy(IdConflictPolicy.CREATE_NEW_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+        // check its UUID
+        node = admin.getNode("/tmp/referenceable");
+        assertNotEquals(UUID_REFERENCEABLE, node.getIdentifier());
+
+        // now try to remove the referenced node (with default policy IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID)
+        options.setIdConflictPolicy(IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+    }
+
+    @Test
+    public void testIdentifierCollisionInsideFilter() throws RepositoryException, IOException, PackageException {
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+
+        assertNodeExists("/tmp/referenceable");
+        // check its UUID
+        Node node = admin.getNode("/tmp/referenceable/child");
+        assertEquals(UUID_REFERENCEABLE_CHILD, node.getIdentifier());
+
+        // now move node (it keeps its old ID)
+        admin.move("/tmp/referenceable/child", "/tmp/referenceable/collision");
+        admin.save();
+
+        // now add a reference to the node (which is covered by the filter)
+        Node referenceNode = JcrUtils.getOrCreateByPath("/tmp/referenceable/reference", JcrConstants.NT_UNSTRUCTURED, admin);
+        Node referenceableNode = admin.getNode("/tmp/referenceable/collision");
+        referenceNode.setProperty(PROPERTY_NAME, referenceableNode);
+        admin.save();
+        assertProperty("/tmp/referenceable/reference/" + PROPERTY_NAME, UUID_REFERENCEABLE_CHILD);
+
+        PropertyIterator propIter = referenceableNode.getReferences();
+        assertTrue(propIter.hasNext());
+
+        // install package again (with default policy IdConflictPolicy.FAIL)
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+        assertNodeExists("/tmp/referenceable");
+        // check its UUID
+        node = admin.getNode("/tmp/referenceable/child");
+        assertEquals(UUID_REFERENCEABLE_CHILD, node.getIdentifier());
+        // now move node (it keeps its old ID)
+        admin.move("/tmp/referenceable/child", "/tmp/referenceable/collision");
+        admin.save();
+
+        // now try to remove the referenced node (with policy IdConflictPolicy.CREATE_NEW_ID)
+        ImportOptions options = getDefaultOptions();
+        options.setStrict(true);
+        options.setIdConflictPolicy(IdConflictPolicy.CREATE_NEW_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+        // check its UUID
+        node = admin.getNode("/tmp/referenceable/child");
+        assertNotEquals(UUID_REFERENCEABLE_CHILD, node.getIdentifier()); // is a new id
+        // now move node (it keeps its old ID)
+        admin.move("/tmp/referenceable/child", "/tmp/referenceable/collision");
+        admin.save();
+
+        // now try to remove the referenced node (with default policy IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID)
+        options.setIdConflictPolicy(IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+        // check its UUID
+        node = admin.getNode("/tmp/referenceable/child");
+        assertEquals(UUID_REFERENCEABLE_CHILD, node.getIdentifier());
+    }
+
+    @Test
+    public void testIdentifierCollisionInsideFilterWithReferencesOutsideFilter() throws RepositoryException, IOException, PackageException {
+        // initial installation
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+
+        assertNodeExists("/tmp/referenceable");
+        // check its UUID
+        Node node = admin.getNode("/tmp/referenceable/child");
+        assertEquals(UUID_REFERENCEABLE_CHILD, node.getIdentifier());
+
+        // now move node (it keeps its old ID)
+        admin.move("/tmp/referenceable/child", "/tmp/referenceable/collision");
+        admin.save();
+        
+        // now add a reference to the node (which is not covered by the filter)
+        Node referenceNode = JcrUtils.getOrCreateByPath("/tmp/reference", JcrConstants.NT_UNSTRUCTURED, admin);
+        Node referenceableNode = admin.getNode("/tmp/referenceable/collision");
+        referenceNode.setProperty(PROPERTY_NAME, referenceableNode);
+        admin.save();
+        assertProperty("/tmp/reference/" + PROPERTY_NAME, UUID_REFERENCEABLE_CHILD);
+
+        PropertyIterator propIter = referenceableNode.getReferences();
+        assertTrue(propIter.hasNext());
+
+        // install package again (with default policy IdConflictPolicy.FAIL)
+        Exception e = assertThrows(Exception.class, () -> { extractVaultPackageStrict("/test-packages/referenceable.zip"); } );
+        assertEquals(ReferentialIntegrityException.class, e.getClass());
+        admin.refresh(false);
+        
+        // now try to remove the referenced node (with policy IdConflictPolicy.CREATE_NEW_ID)
+        ImportOptions options = getDefaultOptions();
+        options.setStrict(true);
+        options.setIdConflictPolicy(IdConflictPolicy.CREATE_NEW_ID);
+        e = assertThrows(Exception.class, () -> { extractVaultPackage("/test-packages/referenceable.zip", options);});
+        assertEquals(ReferentialIntegrityException.class, e.getClass());
+        admin.refresh(false);
+        
+        // now try to remove the referenced node (with default policy IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID)
+        options.setIdConflictPolicy(IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+    }
+
+    @Test
+    public void testReplaceReferencedNonConflictingIdentifier() throws RepositoryException, IOException, PackageException {
+        // create referenceable node manually (other identifier)
+        Node referenceableNode = JcrUtils.getOrCreateByPath("/tmp/referenceable", JcrConstants.NT_UNSTRUCTURED, admin);
+        referenceableNode.addMixin(JcrConstants.MIX_REFERENCEABLE);
+        admin.save();
+        String identifier = referenceableNode.getIdentifier();
+
+        // now create REFERENCE property towards referenceable node
+        Node referenceNode = JcrUtils.getOrCreateByPath("/tmp/reference", JcrConstants.NT_UNSTRUCTURED, admin);
+        referenceNode.setProperty(PROPERTY_NAME, referenceableNode);
+        assertProperty("/tmp/reference/" + PROPERTY_NAME, identifier);
+        admin.save();
+
+        // now try to remove the referenced node (with default policy IdConflictPolicy.FAIL)
+        Exception e = assertThrows(Exception.class, () -> { extractVaultPackageStrict("/test-packages/referenceable.zip");});
+        assertEquals(ReferentialIntegrityException.class, ExceptionUtils.getRootCause(e).getClass());
+        admin.refresh(false);
+
+        // now try to remove the referenced node (with policy IdConflictPolicy.CREATE_NEW_ID)
+        ImportOptions options = getDefaultOptions();
+        options.setStrict(true);
+        options.setIdConflictPolicy(IdConflictPolicy.CREATE_NEW_ID);
+        e = assertThrows(Exception.class, () -> { extractVaultPackage("/test-packages/referenceable.zip", options);});
+        assertEquals(ReferentialIntegrityException.class, ExceptionUtils.getRootCause(e).getClass());
+        admin.refresh(false);
+
+        // now try to remove the referenced node (with default policy IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID)
+        options.setIdConflictPolicy(IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+    }
+
+    @Test
+    public void testOverwriteSameReferencedIdentifier() throws RepositoryException, IOException, PackageException {
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+
+        assertNodeExists("/tmp/referenceable");
+        Node node = admin.getNode("/tmp/referenceable");
+
+        // now create REFERENCE property towards referenceable node
+        Node referenceNode = JcrUtils.getOrCreateByPath("/tmp/reference", JcrConstants.NT_UNSTRUCTURED, admin);
+        referenceNode.setProperty(PROPERTY_NAME, node);
+        assertProperty("/tmp/reference/" + PROPERTY_NAME, UUID_REFERENCEABLE);
+        admin.save();
+
+        // now import again (with default policy IdConflictPolicy.FAIL)
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+
+        // now try to remove the referenced node (with policy IdConflictPolicy.CREATE_NEW_ID)
+        ImportOptions options = getDefaultOptions();
+        options.setStrict(true);
+        options.setIdConflictPolicy(IdConflictPolicy.CREATE_NEW_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+
+        // now try to remove the referenced node (with default policy IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID)
+        options.setIdConflictPolicy(IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID);
+        extractVaultPackage("/test-packages/referenceable.zip", options);
+    }
+
+    @Test
+    public void testReferentialIntegrity() throws RepositoryException {
+        // create referenceable node manually
+        Node referenceableNode = JcrUtils.getOrCreateByPath("/tmp/referenceable", JcrConstants.NT_UNSTRUCTURED, admin);
+        referenceableNode.addMixin(JcrConstants.MIX_REFERENCEABLE);
+        admin.save();
+        String identifier = referenceableNode.getIdentifier();
+
+        // now create REFERENCE property towards referenceable node
+        Node referenceNode = JcrUtils.getOrCreateByPath("/tmp/reference", JcrConstants.NT_UNSTRUCTURED, admin);
+        referenceNode.setProperty(PROPERTY_NAME, referenceableNode);
+        assertProperty("/tmp/reference/" + PROPERTY_NAME, identifier);
+        admin.save();
+
+        // try to remove referenceable node -> fails with RIE
+        Assert.assertThrows(ReferentialIntegrityException.class, () -> { referenceableNode.remove();  admin.save();});
+    }
+}
\ No newline at end of file
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/config.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/config.xml
new file mode 100644
index 0000000..b525f1c
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/config.xml
@@ -0,0 +1,93 @@
+<!--
+  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.
+  -->
+<vaultfs version="1.1">
+    <!--
+        Defines the content aggregation. The order of the defined aggregates
+        is important for finding the correct aggregator.
+    -->
+    <aggregates>
+        <!--
+            Defines an aggregate that handles nt:file and nt:resource nodes.
+        -->
+        <aggregate type="file" title="File Aggregate"/>
+
+        <!--
+            Defines an aggregate that handles file/folder like nodes. It matches
+            all nt:hierarchyNode nodes that have or define a jcr:content
+            child node and excludes child nodes that are nt:hierarchyNodes.
+        -->
+        <aggregate type="filefolder" title="File/Folder Aggregate"/>
+
+        <!--
+            Defines an aggregate that handles nt:nodeType nodes and serializes
+            them into .cnd notation.
+        -->
+        <aggregate type="nodetype" title="Node Type Aggregate" />
+
+        <!--
+            Defines an aggregate that defines full coverage for certain node
+            types that cannot be covered by the default aggregator.
+        -->
+        <aggregate type="full" title="Full Coverage Aggregate">
+            <matches>
+                <include nodeType="rep:AccessControl" respectSupertype="true" />
+                <include nodeType="rep:Policy" respectSupertype="true" />
+                <include nodeType="cq:Widget" respectSupertype="true" />
+                <include nodeType="cq:EditConfig" respectSupertype="true" />
+                <include nodeType="cq:WorkflowModel" respectSupertype="true" />
+                <include nodeType="vlt:FullCoverage" respectSupertype="true" />
+                <include nodeType="mix:language" respectSupertype="true" />
+                <include nodeType="sling:OsgiConfig" respectSupertype="true" />
+            </matches>
+        </aggregate>
+
+        <!--
+            Defines an aggregate that handles nt:folder like nodes.
+        -->
+        <aggregate type="generic" title="Folder Aggregate">
+            <matches>
+                <include nodeType="nt:folder" respectSupertype="true" />
+            </matches>
+            <contains>
+                <exclude isNode="true" />
+            </contains>
+        </aggregate>
+
+        <!--
+            Defines the default aggregate
+        -->
+        <aggregate type="generic" title="Default Aggregator" isDefault="true">
+            <matches>
+                <!-- all -->
+            </matches>
+            <contains>
+                <exclude nodeType="nt:hierarchyNode" respectSupertype="true" />
+            </contains>
+        </aggregate>
+
+    </aggregates>
+
+    <!--
+      defines the input handlers
+    -->
+    <handlers>
+        <handler type="folder"/>
+        <handler type="file"/>
+        <handler type="nodetype"/>
+        <handler type="generic"/>
+    </handlers>
+</vaultfs>
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/definition/.content.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/definition/.content.xml
new file mode 100644
index 0000000..c57b6a8
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/definition/.content.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:vlt="http://www.day.com/jcr/vault/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+    jcr:created="{Date}2015-12-16T16:59:10.779+01:00"
+    jcr:createdBy="admin"
+    jcr:description=""
+    jcr:lastModified="{Date}2015-12-16T16:59:10.779+01:00"
+    jcr:lastModifiedBy="admin"
+    jcr:primaryType="vlt:PackageDefinition"
+    buildCount="5"
+    builtWith="Adobe Experience Manager-6.1.0.20150507"
+    group="my_packages"
+    lastUnwrapped="{Date}2015-12-16T16:59:10.779+01:00"
+    lastUnwrappedBy="admin"
+    lastWrapped="{Date}2015-12-16T16:59:10.779+01:00"
+    lastWrappedBy="admin"
+    name="test_referenceable"
+    version="">
+    <filter jcr:primaryType="nt:unstructured">
+        <f0
+            jcr:primaryType="nt:unstructured"
+            mode="replace"
+            root="/tmp/referenceable"
+            rules="[]"/>
+    </filter>
+</jcr:root>
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/filter.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/filter.xml
new file mode 100644
index 0000000..5258896
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/filter.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<workspaceFilter version="1.0">
+    <filter root="/tmp/referenceable"/>
+</workspaceFilter>
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/nodetypes.cnd b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/nodetypes.cnd
new file mode 100644
index 0000000..c348e28
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/nodetypes.cnd
@@ -0,0 +1,17 @@
+<'vlt'='http://www.day.com/jcr/vault/1.0'>
+<'sling'='http://sling.apache.org/jcr/sling/1.0'>
+<'nt'='http://www.jcp.org/jcr/nt/1.0'>
+<'rep'='internal'>
+
+[vlt:FullCoverage]
+  mixin
+
+[sling:Folder] > nt:folder
+  - * (undefined) multiple
+  - * (undefined)
+  + * (nt:base) = sling:Folder version
+
+[rep:RepoAccessControllable]
+  mixin
+  + rep:repoPolicy (rep:Policy) protected ignore
+
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/properties.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/properties.xml
new file mode 100644
index 0000000..2494f50
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/META-INF/vault/properties.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+<properties>
+<comment>FileVault Package Properties</comment>
+<entry key="createdBy">admin</entry>
+<entry key="name">test_referenceable</entry>
+<entry key="lastModified">2015-12-16T16:59:10.779+01:00</entry>
+<entry key="lastModifiedBy">admin</entry>
+<entry key="created">2015-12-16T16:59:10.795+01:00</entry>
+<entry key="buildCount">5</entry>
+<entry key="version"/>
+<entry key="dependencies"/>
+<entry key="packageFormatVersion">2</entry>
+<entry key="description"/>
+<entry key="lastWrapped">2015-12-16T16:59:10.779+01:00</entry>
+<entry key="group">my_packages</entry>
+<entry key="lastWrappedBy">admin</entry>
+</properties>
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/.content.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/.content.xml
new file mode 100644
index 0000000..8ea9f2a
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/.content.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+    jcr:mixinTypes="[rep:AccessControllable,rep:RepoAccessControllable]"
+    jcr:primaryType="rep:root"
+    sling:resourceType="sling:redirect"
+    sling:target="/index.html"/>
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/tmp/.content.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/tmp/.content.xml
new file mode 100644
index 0000000..54084a8
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/tmp/.content.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal"
+    jcr:mixinTypes="[rep:AccessControllable]"
+    jcr:primaryType="sling:Folder"/>
diff --git a/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/tmp/referenceable.xml b/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/tmp/referenceable.xml
new file mode 100644
index 0000000..2d1995c
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable.zip/jcr_root/tmp/referenceable.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:vlt="http://www.day.com/jcr/vault/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+    jcr:mixinTypes="[mix:referenceable,vlt:FullCoverage]"
+    jcr:primaryType="sling:Folder"
+    jcr:uuid="352c89a4-304f-4b87-9bed-e09275597df1"
+    someproperty="somevalue">
+    <child
+        jcr:mixinTypes="[mix:referenceable]"
+        jcr:primaryType="nt:unstructured"
+        jcr:uuid="a201bd6b-25b9-4255-b7db-6fc4c3ddb32d"
+        someproperty="somevalue"/>
+</jcr:root>