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:36:34 UTC

[jackrabbit-filevault] branch feature/JCRVLT-551-keep-jcr-uuid updated (0506000 -> 847ac60)

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

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


 discard 0506000  JCRVLT-551 try to set UUID from package
     new 847ac60  JCRVLT-551 try to set UUID from package

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (0506000)
            \
             N -- N -- N   refs/heads/feature/JCRVLT-551-keep-jcr-uuid (847ac60)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

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


Summary of changes:
 .../src/main/java/org/apache/jackrabbit/vault/fs/io/ImportOptions.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

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

Posted by kw...@apache.org.
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 847ac6079cde864f8f51ec6b8435d0f07449de6a
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      |  31 +-
 .../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(+), 209 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..82a6f64 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,12 +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.Nullable;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Option that control the package import.
@@ -66,6 +67,8 @@ public class ImportOptions {
 
     private DependencyHandling dependencyHandling = null;
 
+    private @NotNull IdConflictPolicy idConflictPolicy = IdConflictPolicy.FAIL;
+
     /**
      * Default constructor.
      */
@@ -96,6 +99,7 @@ public class ImportOptions {
             hookClassLoader = base.hookClassLoader;
             pathMapping = base.pathMapping;
             dependencyHandling = base.dependencyHandling;
+            idConflictPolicy = base.idConflictPolicy;
         }
     }
 
@@ -121,6 +125,7 @@ public class ImportOptions {
         ret.hookClassLoader = hookClassLoader;
         ret.pathMapping = pathMapping;
         ret.dependencyHandling = dependencyHandling;
+        ret.idConflictPolicy = idConflictPolicy;
         return ret;
     }
 
@@ -421,6 +426,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.2
+     */
+    public void setIdConflictPolicy(@NotNull IdConflictPolicy idConflictPolicy) {
+        this.idConflictPolicy = idConflictPolicy;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -440,6 +463,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 +530,8 @@ public class ImportOptions {
             return false;
         if (strict != other.strict)
             return false;
+        if (!idConflictPolicy.equals(other.idConflictPolicy))
+            return false;
         return true;
     }
 
@@ -521,7 +547,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>