JCRVLT-828: Fix handling of target node UUID for IdConflictPolicy.LEGACY (#398)

diff --git a/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java
index 7694e50..7c10672 100644
--- a/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java
+++ b/vault-core-it/vault-core-integration-tests/src/main/java/org/apache/jackrabbit/vault/packaging/integration/ReferenceableIdentifiersImportIT.java
@@ -533,12 +533,6 @@
         assertIdConflictPolicyBehaviour(IdConflictPolicy.LEGACY, TARGET_STATE.NO_CONFLICT_TARGET_UNCHANGED, ID_KEPT, NA);
     }
 
-    @Test
-    @Ignore("JCRVLT-828")
-    public void testInstallPackageConflictTargetPresent_LEGACY() throws Exception {
-        assertIdConflictPolicyBehaviour(IdConflictPolicy.LEGACY, TARGET_STATE.CONFLICT_TARGET_PRESENT, ID_KEPT, NA);
-    }
-
     // postcondition: exception
     private void assertIdConflictPolicyBehaviour(IdConflictPolicy policy, TARGET_STATE dstState, Class<?> expectedException,
             Class<?> expectedRootCause) throws Exception {
@@ -697,4 +691,38 @@
         options.setIdConflictPolicy(policy);
         extractVaultPackage("/test-packages/referenceable-dup.zip", options);
     }
-}
\ No newline at end of file
+
+    @Test
+    public void testUuidPreservedWithLegacyPolicy() throws Exception {
+        // 1. Install package with UUID from package
+        extractVaultPackageStrict("/test-packages/referenceable.zip");
+        Node node = admin.getNode("/tmp/referenceable");
+        String originalUuid = node.getIdentifier();
+        assertEquals(UUID_REFERENCEABLE, originalUuid);
+
+        // 2. Change the UUID manually (simulating an existing node with different UUID)
+        // This simulates the scenario where node exists with UUID1, package has UUID2
+        node.remove();
+        admin.save();
+        node = JcrUtils.getOrCreateByPath("/tmp/referenceable", null, JcrConstants.NT_UNSTRUCTURED, admin, true);
+        node.addMixin(JcrConstants.MIX_VERSIONABLE);
+        node.setProperty(PROPERTY_NAME, PROPERTY_VALUE); // Keep same property
+        admin.save();
+        String currentUuid = node.getIdentifier();
+        assertNotEquals("Node should have different UUID than package", UUID_REFERENCEABLE, currentUuid);
+
+        // 3. Re-import with LEGACY policy and UPDATE_PROPERTIES mode
+        // Expected: UUID should be preserved (not replaced with package UUID)
+        // Bug: UUID gets replaced unconditionally at line 996
+        ImportOptions opts = getDefaultOptions();
+        opts.setIdConflictPolicy(IdConflictPolicy.LEGACY);
+        opts.setImportMode(ImportMode.REPLACE);
+        extractVaultPackage("/test-packages/referenceable.zip", opts);
+
+        // 4. Verify UUID is preserved (not replaced)
+        node = admin.getNode("/tmp/referenceable");
+        String finalUuid = node.getIdentifier();
+        assertEquals("UUID should be preserved with LEGACY policy", currentUuid, finalUuid);
+        assertNotEquals("UUID should NOT be replaced with package UUID", UUID_REFERENCEABLE, finalUuid);
+    }
+}
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 bdab1ff..7555d7e 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
@@ -906,16 +906,20 @@
                             } 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) 
+                                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) {
-                    // ignore
+                } catch (ItemNotFoundException expected) {
+                    // LEGACY mode: no node with same ID present, but target node exists: ignore the ID from the package being imported
+                    if (existingNode != null && idConflictPolicy == IdConflictPolicy.LEGACY && existingNode.isNodeType(JcrConstants.MIX_REFERENCEABLE)) {
+                        log.debug("IdConflictPolicy.LEGACY - ignoring Identifier {} from imported package at {} but keep existing identifier {}", identifier.get(), docViewNode.getName(), existingNode.getIdentifier());
+                        preprocessedProperties.removeIf(p -> p.getName().equals(NameConstants.JCR_UUID));
+                    }
                 }
             }
         }
@@ -1039,6 +1043,9 @@
                         newMixins.add(mixin);
                     }
                 }
+
+                boolean wasReferenceable = node.isNodeType(JcrConstants.MIX_REFERENCEABLE);
+
                 // remove mixins not in package (only for mode = replace)
                 if (importMode == ImportMode.REPLACE) {
                     for (NodeType mix : node.getMixinNodeTypes()) {
@@ -1049,12 +1056,20 @@
                                     || acHandling == AccessControlHandling.CLEAR
                                     || acHandling == AccessControlHandling.OVERWRITE) {
                                 vs.ensureCheckedOut();
-                                node.removeMixin(name);
+
+                                // can't remove a mixin which would remove the UUID in LEGACY Mode
+                                if (idConflictPolicy == idConflictPolicy.LEGACY && wasReferenceable && mix.isNodeType(JcrConstants.MIX_REFERENCEABLE)) {
+                                    log.debug("idConflictPolicy.LEGACY: not removing mixin " + mix.getName() + ", so that UUID can be preserved.");
+                                } else {
+                                    node.removeMixin(name);
+                                }
+
                                 updatedNode = node;
                             }
                         }
                     }
                 }
+
                 // add remaining mixins (for all import modes)
                 for (String mixin : newMixins) {
                     vs.ensureCheckedOut();
@@ -1080,7 +1095,7 @@
             }
             EffectiveNodeType effectiveNodeType = EffectiveNodeType.ofNode(node);
             Collection<DocViewProperty2> unprotectedProperties = removeProtectedProperties(ni.getProperties(), effectiveNodeType, node.getPath(), PROTECTED_PROPERTIES_CONSIDERED_FOR_UPDATED_NODES);
-            
+
             // add/modify properties contained in package
             if (setProperties(node, ni,unprotectedProperties, importMode == ImportMode.REPLACE|| importMode == ImportMode.UPDATE || importMode == ImportMode.UPDATE_PROPERTIES, vs)) {
                 updatedNode = node;