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 2022/06/10 13:41:58 UTC

[jackrabbit-filevault] branch master updated: JCRVLT-598 introduce new IdConflictPolicy.LEGACY which restores the pre-3.5.2 behaviour (#225)

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

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git


The following commit(s) were added to refs/heads/master by this push:
     new 6558ce62 JCRVLT-598 introduce new IdConflictPolicy.LEGACY which restores the pre-3.5.2 behaviour (#225)
6558ce62 is described below

commit 6558ce6299e3d3546f0911c053dd65aed6bf8dca
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Jun 10 15:41:54 2022 +0200

    JCRVLT-598 introduce new IdConflictPolicy.LEGACY which restores the pre-3.5.2 behaviour (#225)
---
 src/site/markdown/referenceablenodes.md            |   5 +-
 .../jackrabbit/vault/fs/api/IdConflictPolicy.java  |   9 +-
 .../jackrabbit/vault/fs/api/package-info.java      |   2 +-
 .../vault/fs/impl/io/DocViewImporter.java          |  63 +++++++++---
 .../jackrabbit/vault/fs/io/ImportOptions.java      |   5 +-
 .../ReferenceableIdentifiersImportIT.java          | 110 ++++++++++++++-------
 .../META-INF/vault/filter.xml                      |   5 +-
 .../tmp/differentparentconflicts/.content.xml      |   4 +
 .../{ => differentparentconflicts}/duplicate.xml   |   6 +-
 .../referenceable.xml                              |   2 -
 .../tmp/{ => sameparentconflicts}/duplicate.xml    |   7 +-
 .../{ => sameparentconflicts}/referenceable.xml    |   5 -
 12 files changed, 152 insertions(+), 71 deletions(-)

diff --git a/src/site/markdown/referenceablenodes.md b/src/site/markdown/referenceablenodes.md
index 0fe1a256..1f75cb12 100644
--- a/src/site/markdown/referenceablenodes.md
+++ b/src/site/markdown/referenceablenodes.md
@@ -26,7 +26,8 @@ The import behavior depends on the used FileVault version.
 
 ## Import behavior prior FileVault 3.5.2
 
-The ids of referenceable nodes are only kept during import when a node with the same name does not yet exist in the repository. For existing nodes the ids are never updated with the value from the package. They either get a new id or keep their old one (in case the old node was already a referenceable node). In case of conflicts with nodes at a different path, the existing conflicting node is [removed][5] but all its references are kept (i.e. they point to a different path after the import).
+The ids of referenceable nodes are only kept during import when a node with the same name does not yet exist in the repository. For existing nodes the ids are never updated with the value from the package. They either get a new id or keep their old one (in case the old node was already a referenceable node). In case of conflicts with nodes which are not siblings (i.e. don't share their direct parent node) the newly imported node gets a new id. 
+In case of conflicts among siblings the existing conflicting node is [removed][5] but all its references are kept (i.e. they point to a different path after the import) when the existing conflicting node is contained in the filter rules, if the conflicting node is outside the filter rules the to-be imported node is skipped (and import continues with its children as if they were below the existing one).
 
 ## Import behavior since FileVault 3.5.2
 
@@ -34,7 +35,7 @@ Since version 3.5.2 ([JCRVLT-551](https://issues.apache.org/jira/browse/JCRVLT-5
 
 ### Id Conflict Policies
 
-The import behavior of packages with conflicting ids can be tweaked with `ImportOptions.setIdConflictPolicy(...)`. For further details refer to its [javadoc][3]. The old behavior can be achieved with `IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID`.
+The import behavior of packages with conflicting ids can be tweaked with `ImportOptions.setIdConflictPolicy(...)` per package or the default can be overridden in the [OSGi Configuration](config.html). For further details refer to the [JavaDoc][3]. The old behavior can be achieved with `IdConflictPolicy.LEGACY`.
 
 [1]: https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.8%20Referenceable%20Nodes
 [2]: https://s.apache.org/jcr-2.0-spec/3_Repository_Model.html#3.8.2%20Referential%20Integrity
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
index 83abb3af..9c1b6523 100644
--- 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
@@ -25,5 +25,12 @@ public enum IdConflictPolicy {
     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
+    FORCE_REMOVE_CONFLICTING_ID,
+    /** Assign the newly imported conflicting node a new id in case the conflicting existing node does not have the same parent (i.e. is no sibling).
+     * If the newly imported node is a sibling of the existing conflicting one either remove the existing node with the conflicting id but keep its references (in case the conflicting one is contained in the filter) 
+     * or skip the to be imported node (and continue with importing its children as if they were below the existing one).
+     * This was the policy which was always used in FileVault prior version 3.5.2.
+     * @since 3.6.2
+     */
+    LEGACY
 }
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 48709deb..625d01f4 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.10.0")
+@Version("2.11.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/DocViewImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
index 98e76635..c4bd9431 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
@@ -65,6 +65,8 @@ 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.ImportInfo.Info;
+import org.apache.jackrabbit.vault.fs.api.ImportInfo.Type;
 import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.api.ItemFilterSet;
 import org.apache.jackrabbit.vault.fs.api.NodeNameList;
@@ -141,7 +143,7 @@ public class DocViewImporter implements DocViewParserHandler {
     private final Session session;
 
     /**
-     * final import information
+     * import information for the nodes touched through this DocView (initially empty)
      */
     private ImportInfoImpl importInfo = new ImportInfoImpl();
 
@@ -819,6 +821,7 @@ public class DocViewImporter implements DocViewParserHandler {
     private StackElement addNode(DocViewNode2 docViewNode) throws RepositoryException, IOException {
         final Node currentNode = stack.getNode();
 
+        Collection<DocViewProperty2> preprocessedProperties = new LinkedList<>(docViewNode.getProperties());
         Node existingNode = null;
         if (NameConstants.ROOT.equals(docViewNode.getName())) {
             // special case for root node update
@@ -828,23 +831,58 @@ public class DocViewImporter implements DocViewParserHandler {
                 existingNode = currentNode.getNode(docViewNode.getName().toString());
             }
             Optional<String> identifier = docViewNode.getIdentifier();
-            if (identifier.isPresent() && idConflictPolicy == IdConflictPolicy.FAIL) {
+            if (identifier.isPresent()) {
                 try {
                     // does uuid already exist in the repo?
                     Node sameIdNode = session.getNodeByIdentifier(identifier.get());
+                    String newNodePath = currentNode.getPath() + "/" + npResolver.getJCRName(docViewNode.getName());
                     // edge-case: same node path -> uuid is kept
                     if (existingNode != null && existingNode.getPath().equals(sameIdNode.getPath())) {
-                        log.debug("Node with existing identifier {} at {} is being updated without modifying its uuid", docViewNode.getIdentifier(), existingNode.getPath());
+                        log.debug("Node at {} with existing identifier {} is being updated without modifying its identifier", existingNode.getPath(), docViewNode.getIdentifier());
                     } 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.", docViewNode.getIdentifier(), currentNode.getPath(), docViewNode.getName(), sameIdNode.getPath());
-                            removeReferences(sameIdNode);
-                            session.removeItem(sameIdNode.getPath());
-                            existingNode = null;
-                        } else {
-                            // uuid found in path not-covered by filter
-                            throw new ReferentialIntegrityException("UUID " + docViewNode.getIdentifier() + " already taken by node " + sameIdNode.getPath());
+                        log.warn("Node Collision: To-be imported node {} uses a node identifier {} which is already taken by {}, trying to resolve conflict according to policy {}", 
+                                newNodePath, docViewNode.getIdentifier(), sameIdNode.getPath(), idConflictPolicy.name());
+                        if (idConflictPolicy == IdConflictPolicy.FAIL) {
+                            // uuid found in path covered by filter
+                            if (isIncluded(sameIdNode, 0)) {
+                                Info sameIdNodeInfo = importInfo.getInfo(sameIdNode.getPath());
+                                // is the conflicting node part of the package (i.e. the package contained duplicate uuids)
+                                if (sameIdNodeInfo != null && sameIdNodeInfo.getType() != Type.DEL) {
+                                    throw new ReferentialIntegrityException("Node identifier " + docViewNode.getIdentifier() + " already taken by node " + sameIdNode.getPath() + " from the same package");
+                                } else {
+                                    log.warn("Trying to remove existing conflicting node {} (and all its references)", sameIdNode.getPath());
+                                    removeReferences(sameIdNode);
+                                    String sameIdNodePath = sameIdNode.getPath();
+                                    session.removeItem(sameIdNodePath);
+                                    log.warn("Node {} and its references removed", sameIdNodePath);
+                                }
+                                existingNode = null;
+                            } else {
+                                // uuid found in path not-covered by filter
+                                throw new ReferentialIntegrityException("Node identifier " + docViewNode.getIdentifier() + " already taken by node " + sameIdNode.getPath());
+                            }
+                        } else if (idConflictPolicy == IdConflictPolicy.LEGACY) {
+                            // is the conflicting node a sibling
+                            if (sameIdNode.getParent().isSame(currentNode)) {
+                                String sameIdNodePath = sameIdNode.getPath();
+                                if (isIncluded(sameIdNode, 0)) {
+                                    log.warn("Existing conflicting node {} has same parent as to-be imported one and is contained in the filter, trying to remove it.", sameIdNodePath);
+                                    session.removeItem(sameIdNodePath); // references point to new node afterwards
+                                    importInfo.onDeleted(sameIdNodePath);
+                                } else {
+                                    log.warn("Existing conflicting node {} has same parent as to-be imported one and is not contained in the filter, ignoring new node but continue with children below existing conflicting node", sameIdNodePath);
+                                    importInfo.onRemapped(newNodePath, sameIdNodePath);
+                                    existingNode = sameIdNode;
+                                }
+                            } else {
+                                log.warn("To-be imported node and existing conflicting node have different parents. Will create new identifier for the former. ({})",
+                                        newNodePath);
+                                preprocessedProperties.removeIf(p -> p.getName().equals(NameConstants.JCR_UUID) 
+                                        || p.getName().equals(NameConstants.JCR_BASEVERSION) 
+                                        || p.getName().equals(NameConstants.JCR_PREDECESSORS)
+                                        || p.getName().equals(NameConstants.JCR_SUCCESSORS)
+                                        || p.getName().equals(NameConstants.JCR_VERSIONHISTORY));
+                            }
                         }
                     }
                 } catch (ItemNotFoundException e) {
@@ -854,7 +892,6 @@ public class DocViewImporter implements DocViewParserHandler {
         }
 
         // check if new node needs to be checked in
-        Collection<DocViewProperty2> preprocessedProperties = new LinkedList<>(docViewNode.getProperties());
         preprocessedProperties.removeIf(p -> p.getName().equals(NameConstants.JCR_ISCHECKEDOUT));
         boolean isCheckedIn = "false".equals(docViewNode.getPropertyValue(NameConstants.JCR_ISCHECKEDOUT).orElse("true"));
 
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 7372b952..e5c4c491 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
@@ -452,7 +452,7 @@ public class ImportOptions {
 
     /**
      * 
-     * @return the id conflict policy
+     * @return the id conflict policy if one is set, otherwise {@link IdConflictPolicy#FAIL}
      * @since 3.5.1
      */
     public @NotNull IdConflictPolicy getIdConflictPolicy() {
@@ -460,7 +460,8 @@ public class ImportOptions {
     }
 
     /**
-     * @since 3.5.10
+     * @return {@code true} if {@link #setIdConflictPolicy(IdConflictPolicy)} has been called otherwise {@code false}
+     * @since 3.6.0
      */
     public boolean hasIdConflictPolicyBeenSet() {
         return idConflictPolicy != null;
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
index 35cecffa..907fec4e 100644
--- 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
@@ -19,11 +19,16 @@ package org.apache.jackrabbit.vault.packaging.integration;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.jcr.Node;
 import javax.jcr.PathNotFoundException;
@@ -295,15 +300,16 @@ public class ReferenceableIdentifiersImportIT extends IntegrationTestBase {
 
     @Test
     public void testImportDupPolicyFail() throws RepositoryException, IOException, PackageException {
+        // TODO: this is supposed to fail the installation
         testImportDup(IdConflictPolicy.FAIL);
-        Node referenceableNode = getNodeOrNull("/tmp/referenceable");
-        Node duplicateNode = getNodeOrNull("/tmp/duplicate");
+        Node referenceableNode = getNodeOrNull("/tmp/differentparentconflicts/referenceable/child");
+        Node duplicateNode = getNodeOrNull("/tmp/differentparentconflicts/duplicate/child");
         if (duplicateNode == null && referenceableNode != null) {
             assertTrue(referenceableNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
-            assertEquals(referenceableNode.getIdentifier(), UUID_REFERENCEABLE);
+            assertEquals(referenceableNode.getIdentifier(), UUID_REFERENCEABLE_CHILD);
         } else if (duplicateNode != null && referenceableNode == null) {
             assertTrue(duplicateNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
-            assertEquals(duplicateNode.getIdentifier(), UUID_REFERENCEABLE);
+            assertEquals(duplicateNode.getIdentifier(), UUID_REFERENCEABLE_CHILD);
         } else {
             fail("both nodes imported");
         }
@@ -312,51 +318,87 @@ public class ReferenceableIdentifiersImportIT extends IntegrationTestBase {
     @Test
     public void testImportDupPolicyCreateNewId() throws RepositoryException, IOException, PackageException {
         testImportDup(IdConflictPolicy.CREATE_NEW_ID);
-        Node referenceableNode = getNodeOrNull("/tmp/referenceable");
-        Node duplicateNode = getNodeOrNull("/tmp/duplicate");
-        if (duplicateNode == null) {
-            fail("'duplicate' not imported");
-        } else if (referenceableNode == null) {
-            fail("'referencable' not imported");
+        Node referenceableNode = getNodeOrNull("/tmp/differentparentconflicts/referenceable/child");
+        Node duplicateNode = getNodeOrNull("/tmp/differentparentconflicts/duplicate/child");
+        assertNotNull("'duplicate' not imported", duplicateNode);
+        assertNotNull("'referencable' not imported", referenceableNode);
+        assertTrue(referenceableNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
+        String refref = referenceableNode.getIdentifier();
+        assertTrue(duplicateNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
+        String dupref = duplicateNode.getIdentifier();
+        assertNotEquals("identifiers should be different", refref, dupref);
+
+        // For this test, Jackrabbit and Oak behave differently; for now, we
+        // just observe the behavior (and the test ensures, that it doesn't
+        // change without us noticing)
+        if (isOak()) {
+            assertTrue("identifiers should be new", !UUID_REFERENCEABLE_CHILD.equals(refref) && !UUID_REFERENCEABLE_CHILD.equals(dupref));
         } else {
-            assertTrue(referenceableNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
-            String refref = referenceableNode.getIdentifier();
-            assertTrue(duplicateNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
-            String dupref = duplicateNode.getIdentifier();
-            assertNotEquals("identifiers should be different", refref, dupref);
-
-            // For this test, Jackrabbit and Oak behave differently; for now, we
-            // just observe the behavior (and the test ensures, that it doesn't
-            // change without us noticing)
-            if (isOak()) {
-                assertTrue("identifiers should be new", !UUID_REFERENCEABLE.equals(refref) && !UUID_REFERENCEABLE.equals(dupref));
-            } else {
-                int newUUIDs = 0;
-                if (!UUID_REFERENCEABLE.equals(refref)) {
-                    newUUIDs += 1;
-                }
-                if (!UUID_REFERENCEABLE.equals(dupref)) {
-                    newUUIDs += 1;
-                }
-                assertEquals("for Jackrabbit classic, exactly one changed UUID was expected", 1, newUUIDs);
+            int newUUIDs = 0;
+            if (!UUID_REFERENCEABLE_CHILD.equals(refref)) {
+                newUUIDs += 1;
             }
+            if (!UUID_REFERENCEABLE_CHILD.equals(dupref)) {
+                newUUIDs += 1;
+            }
+            assertEquals("for Jackrabbit classic, exactly one changed UUID was expected", 1, newUUIDs);
         }
     }
 
     @Test
     public void testImportDupPolicyForceRemove() throws RepositoryException, IOException, PackageException {
         testImportDup(IdConflictPolicy.FORCE_REMOVE_CONFLICTING_ID);
-        Node referenceableNode = getNodeOrNull("/tmp/referenceable");
-        Node duplicateNode = getNodeOrNull("/tmp/duplicate");
+        Node referenceableNode = getNodeOrNull("/tmp/differentparentconflicts/referenceable/child");
+        Node duplicateNode = getNodeOrNull("/tmp/differentparentconflicts/duplicate/child");
+        if (duplicateNode == null && referenceableNode != null) {
+            assertTrue(referenceableNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
+            assertEquals(UUID_REFERENCEABLE_CHILD, referenceableNode.getIdentifier());
+        } else if (duplicateNode != null && referenceableNode == null) {
+            assertTrue(duplicateNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
+            assertEquals(UUID_REFERENCEABLE_CHILD, duplicateNode.getIdentifier());
+        } else {
+            fail("both nodes imported");
+        }
+    }
+
+    @Test
+    public void testImportDupPolicyLegacy() throws RepositoryException, IOException, PackageException {
+        testImportDup(IdConflictPolicy.LEGACY);
+        // behaviour for same parent conflicts: remove the conflicting one (i.e. the first one) with the new one (references point to new one afterwards)
+        Node referenceableNode = getNodeOrNull("/tmp/sameparentconflicts/referenceable");
+        Node duplicateNode = getNodeOrNull("/tmp/sameparentconflicts/duplicate");
         if (duplicateNode == null && referenceableNode != null) {
             assertTrue(referenceableNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
-            assertEquals(referenceableNode.getIdentifier(), UUID_REFERENCEABLE);
+            assertEquals(UUID_REFERENCEABLE, referenceableNode.getIdentifier());
+            admin.move(referenceableNode.getPath(), "/tmp/sameparentconflicts/referenceable-outsidepackage");
         } else if (duplicateNode != null && referenceableNode == null) {
             assertTrue(duplicateNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
-            assertEquals(duplicateNode.getIdentifier(), UUID_REFERENCEABLE);
+            assertEquals(UUID_REFERENCEABLE, duplicateNode.getIdentifier());
+            admin.move(duplicateNode.getPath(), "/tmp/sameparentconflicts/referenceable-outsidepackage");
         } else {
             fail("both nodes imported");
         }
+        // behaviour for non-sibling conflicts: assign the new one a new UUID
+        referenceableNode = getNodeOrNull("/tmp/differentparentconflicts/referenceable/child");
+        duplicateNode = getNodeOrNull("/tmp/differentparentconflicts/duplicate/child");
+        assertNotNull(referenceableNode);
+        assertNotNull(duplicateNode);
+        assertTrue(referenceableNode.isNodeType(JcrConstants.MIX_REFERENCEABLE));
+        Set<String> uuids = new HashSet<>(Arrays.asList(referenceableNode.getIdentifier(), duplicateNode.getIdentifier()));
+        assertTrue(uuids.contains(UUID_REFERENCEABLE_CHILD)); // one must have kept the old id
+        assertEquals(2, uuids.size());
+
+        // create a reference towards an identifier used by the package (but used now outside the packag filter)
+        referenceableNode = admin.getNode("/tmp/sameparentconflicts/referenceable-outsidepackage");
+        Node referenceNode = JcrUtils.getOrCreateByPath("/tmp/reference", JcrConstants.NT_UNSTRUCTURED, admin);
+        referenceNode.setProperty(PROPERTY_NAME, referenceableNode);
+
+        // now reinstall
+        testImportDup(IdConflictPolicy.LEGACY);
+        referenceableNode = getNodeOrNull("/tmp/sameparentconflicts/referenceable");
+        duplicateNode = getNodeOrNull("/tmp/sameparentconflicts/duplicate");
+        assertNull(referenceableNode); // package must not contain new conflicting nodes
+        assertNull(duplicateNode);
     }
 
     private Node getNodeOrNull(String path) throws RepositoryException {
diff --git a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/META-INF/vault/filter.xml b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/META-INF/vault/filter.xml
index 1f2812bc..f3ddf87b 100644
--- a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/META-INF/vault/filter.xml
+++ b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/META-INF/vault/filter.xml
@@ -1,4 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <workspaceFilter version="1.0">
-    <filter root="/tmp"/>
+    <filter root="/tmp/sameparentconflicts/referenceable"/>
+    <filter root="/tmp/sameparentconflicts/duplicate"/>
+    <filter root="/tmp/differentparentconflicts/referenceable"/>
+    <filter root="/tmp/differentparentconflicts/duplicate"/>
 </workspaceFilter>
diff --git a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/.content.xml b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/.content.xml
new file mode 100644
index 00000000..54084a82
--- /dev/null
+++ b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/.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-dup.zip/jcr_root/tmp/duplicate.xml b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/duplicate.xml
similarity index 72%
copy from vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/duplicate.xml
copy to vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/duplicate.xml
index 2d1995cd..876b1a76 100644
--- a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/duplicate.xml
+++ b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/duplicate.xml
@@ -1,12 +1,10 @@
 <?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">
+    someproperty="somevaluefromduplicate">
     <child
         jcr:mixinTypes="[mix:referenceable]"
         jcr:primaryType="nt:unstructured"
         jcr:uuid="a201bd6b-25b9-4255-b7db-6fc4c3ddb32d"
-        someproperty="somevalue"/>
+        someproperty="somevaluefromduplicate"/>
 </jcr:root>
diff --git a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/referenceable.xml b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/referenceable.xml
similarity index 82%
copy from vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/referenceable.xml
copy to vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/referenceable.xml
index 2d1995cd..99fbb46b 100644
--- a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/referenceable.xml
+++ b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/differentparentconflicts/referenceable.xml
@@ -1,8 +1,6 @@
 <?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]"
diff --git a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/duplicate.xml b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/sameparentconflicts/duplicate.xml
similarity index 66%
rename from vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/duplicate.xml
rename to vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/sameparentconflicts/duplicate.xml
index 2d1995cd..451eaccd 100644
--- a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/duplicate.xml
+++ b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/sameparentconflicts/duplicate.xml
@@ -3,10 +3,5 @@
     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"/>
+    someproperty="somevaluefromduplicate">
 </jcr:root>
diff --git a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/referenceable.xml b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/sameparentconflicts/referenceable.xml
similarity index 70%
rename from vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/referenceable.xml
rename to vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/sameparentconflicts/referenceable.xml
index 2d1995cd..9ed6059f 100644
--- a/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/referenceable.xml
+++ b/vault-core/src/test/resources/test-packages/referenceable-dup.zip/jcr_root/tmp/sameparentconflicts/referenceable.xml
@@ -4,9 +4,4 @@
     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>