From 52896b8ecee8cce64519972f12b7a68351c6c1ad Mon Sep 17 00:00:00 2001
From: danielhanrahan <daniel.hanrahan@est.tech>
Date: Fri, 22 Nov 2024 12:17:54 +0000
Subject: CpsDataPersistenceService refactoring

- Extract common code to reduce duplication.
- Move private methods below public methods, and
  move private static methods to bottom of file.
- Note some private methods are left in-place due to
  checkstyle rules (method overloads belong together).

Issue-ID: CPS-2365
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: I088a5340e315d55aaf427444bdbfdcadf918464a
---
 .../onap/cps/ri/CpsDataPersistenceServiceImpl.java | 371 ++++++++++-----------
 1 file changed, 174 insertions(+), 197 deletions(-)

(limited to 'cps-ri/src/main/java/org/onap')

diff --git a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
index ecbe447226..b7e50815e6 100644
--- a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
@@ -116,26 +116,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         }
     }
 
-    /**
-     * Convert DataNode object into Fragment and places the result in the fragments placeholder. Performs same action
-     * for all DataNode children recursively.
-     *
-     * @param anchorEntity          anchorEntity
-     * @param dataNodeToBeConverted dataNode
-     * @return a Fragment built from current DataNode
-     */
-    private FragmentEntity convertToFragmentWithAllDescendants(final AnchorEntity anchorEntity,
-                                                               final DataNode dataNodeToBeConverted) {
-        final FragmentEntity parentFragment = toFragmentEntity(anchorEntity, dataNodeToBeConverted);
-        final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
-        for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
-            final FragmentEntity childFragment = convertToFragmentWithAllDescendants(anchorEntity, childDataNode);
-            childFragmentsImmutableSetBuilder.add(childFragment);
-        }
-        parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
-        return parentFragment;
-    }
-
     @Override
     public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                 final Collection<DataNode> newListElements) {
@@ -150,53 +130,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         addChildrenDataNodes(anchorEntity, parentNodeXpath, dataNodes);
     }
 
-    private void addChildrenDataNodes(final AnchorEntity anchorEntity, final String parentNodeXpath,
-                                      final Collection<DataNode> newChildren) {
-        final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
-        final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
-        try {
-            for (final DataNode newChildAsDataNode : newChildren) {
-                final FragmentEntity newChildAsFragmentEntity =
-                    convertToFragmentWithAllDescendants(anchorEntity, newChildAsDataNode);
-                newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
-                fragmentEntities.add(newChildAsFragmentEntity);
-            }
-            fragmentRepository.saveAll(fragmentEntities);
-        } catch (final DataIntegrityViolationException dataIntegrityViolationException) {
-            log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
-                dataIntegrityViolationException, fragmentEntities.size());
-            retrySavingEachChildIndividually(anchorEntity, parentNodeXpath, newChildren);
-        }
-    }
-
-    private void addNewChildDataNode(final AnchorEntity anchorEntity, final String parentNodeXpath,
-                                     final DataNode newChild) {
-        final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
-        final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, newChild);
-        newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
-        try {
-            fragmentRepository.save(newChildAsFragmentEntity);
-        } catch (final DataIntegrityViolationException dataIntegrityViolationException) {
-            throw AlreadyDefinedException.forDataNodes(Collections.singletonList(newChild.getXpath()),
-                anchorEntity.getName());
-        }
-    }
-
-    private void retrySavingEachChildIndividually(final AnchorEntity anchorEntity, final String parentNodeXpath,
-                                                  final Collection<DataNode> newChildren) {
-        final Collection<String> failedXpaths = new HashSet<>();
-        for (final DataNode newChild : newChildren) {
-            try {
-                addNewChildDataNode(anchorEntity, parentNodeXpath, newChild);
-            } catch (final AlreadyDefinedException alreadyDefinedException) {
-                failedXpaths.add(newChild.getXpath());
-            }
-        }
-        if (!failedXpaths.isEmpty()) {
-            throw AlreadyDefinedException.forDataNodes(failedXpaths, anchorEntity.getName());
-        }
-    }
-
     @Override
     public void batchUpdateDataLeaves(final String dataspaceName, final String anchorName,
                                       final Map<String, Map<String, Serializable>> updatedLeavesPerXPath) {
@@ -246,19 +179,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         }
     }
 
-    private void logMissingXPaths(final Collection<String> xpaths, final Collection<FragmentEntity>
-            existingFragmentEntities) {
-        final Set<String> existingXPaths = existingFragmentEntities.stream().map(FragmentEntity::getXpath)
-                .collect(Collectors.toSet());
-
-        final Set<String> missingXPaths = xpaths.stream().filter(xpath -> !existingXPaths.contains(xpath))
-                .collect(Collectors.toSet());
-
-        if (!missingXPaths.isEmpty()) {
-            log.warn("Cannot update data nodes: Target XPaths {} not found in DB.", missingXPaths);
-        }
-    }
-
     private void retryUpdateDataNodesIndividually(final AnchorEntity anchorEntity,
                                                   final Collection<FragmentEntity> fragmentEntities) {
         final Collection<String> failedXpaths = new HashSet<>();
@@ -306,14 +226,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             description = "Time taken to query data nodes")
     public List<DataNode> queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath,
                                          final FetchDescendantsOption fetchDescendantsOption) {
-        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
-        final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
-        final CpsPathQuery cpsPathQuery;
-        try {
-            cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
-        } catch (final PathParsingException pathParsingException) {
-            throw new CpsPathException(pathParsingException.getMessage());
-        }
+        final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
+        final CpsPathQuery cpsPathQuery = getCpsPathQuery(cpsPath);
 
         Collection<FragmentEntity> fragmentEntities;
         fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity, cpsPathQuery);
@@ -321,8 +235,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
             fragmentEntities = fragmentRepository.findByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
         }
-        fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption,
-                fragmentEntities);
         return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
     }
 
@@ -333,12 +245,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                                                       final FetchDescendantsOption fetchDescendantsOption,
                                                       final PaginationOption paginationOption) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
-        final CpsPathQuery cpsPathQuery;
-        try {
-            cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
-        } catch (final PathParsingException e) {
-            throw new CpsPathException(e.getMessage());
-        }
+        final CpsPathQuery cpsPathQuery = getCpsPathQuery(cpsPath);
 
         final List<Long> anchorIds;
         if (paginationOption == NO_PAGINATION) {
@@ -359,22 +266,10 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             } else {
                 fragmentEntities = fragmentRepository.findByAnchorIdsAndXpathIn(anchorIds, ancestorXpaths);
             }
-
         }
-        fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption,
-                fragmentEntities);
         return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
     }
 
-    private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption,
-                                                               final Collection<FragmentEntity> fragmentEntities) {
-        final List<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
-        for (final FragmentEntity fragmentEntity : fragmentEntities) {
-            dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
-        }
-        return Collections.unmodifiableList(dataNodes);
-    }
-
     @Override
     public String startSession() {
         return sessionManager.startSession();
@@ -394,41 +289,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     @Override
     public Integer countAnchorsForDataspaceAndCpsPath(final String dataspaceName, final String cpsPath) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
-        final CpsPathQuery cpsPathQuery;
-        try {
-            cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath);
-        } catch (final PathParsingException e) {
-            throw new CpsPathException(e.getMessage());
-        }
+        final CpsPathQuery cpsPathQuery = getCpsPathQuery(cpsPath);
         final List<Long> anchorIdList = getAnchorIdsForPagination(dataspaceEntity, cpsPathQuery, NO_PAGINATION);
         return anchorIdList.size();
     }
 
-    private DataNode toDataNode(final FragmentEntity fragmentEntity,
-                                final FetchDescendantsOption fetchDescendantsOption) {
-        final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
-        Map<String, Serializable> leaves = new HashMap<>();
-        if (fragmentEntity.getAttributes() != null) {
-            leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
-        }
-        return new DataNodeBuilder()
-                .withXpath(fragmentEntity.getXpath())
-                .withLeaves(leaves)
-                .withDataspace(fragmentEntity.getAnchor().getDataspace().getName())
-                .withAnchor(fragmentEntity.getAnchor().getName())
-                .withChildDataNodes(childDataNodes).build();
-    }
-
-    private FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final DataNode dataNode) {
-        return FragmentEntity.builder()
-            .anchor(anchorEntity)
-            .xpath(dataNode.getXpath())
-            .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
-            .build();
-    }
-
-
-
     @Override
     @Transactional
     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
@@ -487,18 +352,18 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
         final Collection<String> deleteChecklist = getNormalizedXpaths(xpathsToDelete);
         final Collection<String> xpathsToExistingContainers =
-            fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist);
+                fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist);
         if (onlySupportListDeletion) {
             final Collection<String> xpathsToExistingListElements = xpathsToExistingContainers.stream()
-                .filter(CpsPathUtil::isPathToListElement).toList();
+                    .filter(CpsPathUtil::isPathToListElement).toList();
             deleteChecklist.removeAll(xpathsToExistingListElements);
         } else {
             deleteChecklist.removeAll(xpathsToExistingContainers);
         }
 
         final Collection<String> xpathsToExistingLists = deleteChecklist.stream()
-            .filter(xpath -> fragmentRepository.existsByAnchorAndXpathStartsWith(anchorEntity, xpath + "["))
-            .toList();
+                .filter(xpath -> fragmentRepository.existsByAnchorAndXpathStartsWith(anchorEntity, xpath + "["))
+                .toList();
         deleteChecklist.removeAll(xpathsToExistingLists);
 
         if (!deleteChecklist.isEmpty()) {
@@ -531,7 +396,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         final String normalizedXpath = getNormalizedXpath(targetXpath);
         try {
             deleteDataNodes(dataspaceName, anchorName, Collections.singletonList(normalizedXpath),
-                onlySupportListNodeDeletion);
+                    onlySupportListNodeDeletion);
         } catch (final DataNodeNotFoundExceptionBatch dataNodeNotFoundExceptionBatch) {
             throw new DataNodeNotFoundException(dataspaceName, anchorName, targetXpath);
         }
@@ -559,18 +424,110 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                                                               final Collection<String> xpaths,
                                                               final FetchDescendantsOption fetchDescendantsOption) {
         final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName);
-        Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpaths);
-        fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption,
-            fragmentEntities);
+        final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(anchorEntity, xpaths);
         return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
     }
 
+
+    private void addChildrenDataNodes(final AnchorEntity anchorEntity, final String parentNodeXpath,
+                                      final Collection<DataNode> newChildren) {
+        final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
+        final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
+        try {
+            for (final DataNode newChildAsDataNode : newChildren) {
+                final FragmentEntity newChildAsFragmentEntity =
+                        convertToFragmentWithAllDescendants(anchorEntity, newChildAsDataNode);
+                newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+                fragmentEntities.add(newChildAsFragmentEntity);
+            }
+            fragmentRepository.saveAll(fragmentEntities);
+        } catch (final DataIntegrityViolationException dataIntegrityViolationException) {
+            log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
+                    dataIntegrityViolationException, fragmentEntities.size());
+            retrySavingEachChildIndividually(anchorEntity, parentNodeXpath, newChildren);
+        }
+    }
+
+    private void addNewChildDataNode(final AnchorEntity anchorEntity, final String parentNodeXpath,
+                                     final DataNode newChild) {
+        final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath);
+        final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, newChild);
+        newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+        try {
+            fragmentRepository.save(newChildAsFragmentEntity);
+        } catch (final DataIntegrityViolationException dataIntegrityViolationException) {
+            throw AlreadyDefinedException.forDataNodes(Collections.singletonList(newChild.getXpath()),
+                    anchorEntity.getName());
+        }
+    }
+
+    private void retrySavingEachChildIndividually(final AnchorEntity anchorEntity, final String parentNodeXpath,
+                                                  final Collection<DataNode> newChildren) {
+        final Collection<String> failedXpaths = new HashSet<>();
+        for (final DataNode newChild : newChildren) {
+            try {
+                addNewChildDataNode(anchorEntity, parentNodeXpath, newChild);
+            } catch (final AlreadyDefinedException alreadyDefinedException) {
+                failedXpaths.add(newChild.getXpath());
+            }
+        }
+        if (!failedXpaths.isEmpty()) {
+            throw AlreadyDefinedException.forDataNodes(failedXpaths, anchorEntity.getName());
+        }
+    }
+
+    private FragmentEntity convertToFragmentWithAllDescendants(final AnchorEntity anchorEntity,
+                                                               final DataNode dataNodeToBeConverted) {
+        final FragmentEntity parentFragment = toFragmentEntity(anchorEntity, dataNodeToBeConverted);
+        final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
+        for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
+            final FragmentEntity childFragment = convertToFragmentWithAllDescendants(anchorEntity, childDataNode);
+            childFragmentsImmutableSetBuilder.add(childFragment);
+        }
+        parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build());
+        return parentFragment;
+    }
+
+    private FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final DataNode dataNode) {
+        return FragmentEntity.builder()
+                .anchor(anchorEntity)
+                .xpath(dataNode.getXpath())
+                .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves()))
+                .build();
+    }
+
+    private List<DataNode> createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption,
+                                                               final Collection<FragmentEntity> fragmentEntities) {
+        final Collection<FragmentEntity> fragmentEntitiesWithDescendants =
+                fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption, fragmentEntities);
+        final List<DataNode> dataNodes = new ArrayList<>(fragmentEntitiesWithDescendants.size());
+        for (final FragmentEntity fragmentEntity : fragmentEntitiesWithDescendants) {
+            dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
+        }
+        return Collections.unmodifiableList(dataNodes);
+    }
+
+    private DataNode toDataNode(final FragmentEntity fragmentEntity,
+                                final FetchDescendantsOption fetchDescendantsOption) {
+        final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
+        Map<String, Serializable> leaves = new HashMap<>();
+        if (fragmentEntity.getAttributes() != null) {
+            leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
+        }
+        return new DataNodeBuilder()
+                .withXpath(fragmentEntity.getXpath())
+                .withLeaves(leaves)
+                .withDataspace(fragmentEntity.getAnchor().getDataspace().getName())
+                .withAnchor(fragmentEntity.getAnchor().getName())
+                .withChildDataNodes(childDataNodes).build();
+    }
+
     private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
                                              final FetchDescendantsOption fetchDescendantsOption) {
         if (fetchDescendantsOption.hasNext()) {
             return fragmentEntity.getChildFragments().stream()
                 .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
-                .collect(Collectors.toList());
+                .toList();
         }
         return Collections.emptyList();
     }
@@ -585,29 +542,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return fragmentRepository.findAnchorIdsForPagination(dataspaceEntity, cpsPathQuery, paginationOption);
     }
 
-    private static String getNormalizedXpath(final String xpathSource) {
-        if (isRootXpath(xpathSource)) {
-            return xpathSource;
-        }
-        try {
-            return CpsPathUtil.getNormalizedXpath(xpathSource);
-        } catch (final PathParsingException pathParsingException) {
-            throw new CpsPathException(pathParsingException.getMessage());
-        }
-    }
-
-    private static Collection<String> getNormalizedXpaths(final Collection<String> xpaths) {
-        final Collection<String> normalizedXpaths = new HashSet<>(xpaths.size());
-        for (final String xpath : xpaths) {
-            try {
-                normalizedXpaths.add(getNormalizedXpath(xpath));
-            } catch (final CpsPathException cpsPathException) {
-                log.warn("Error parsing xpath \"{}\": {}", xpath, cpsPathException.getMessage());
-            }
-        }
-        return normalizedXpaths;
-    }
-
     private FragmentEntity getFragmentEntity(final AnchorEntity anchorEntity, final String xpath) {
         final FragmentEntity fragmentEntity;
         if (isRootXpath(xpath)) {
@@ -628,7 +562,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         final boolean haveRootXpath = normalizedXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath);
 
         final List<FragmentEntity> fragmentEntities = fragmentRepository.findByAnchorAndXpathIn(anchorEntity,
-            normalizedXpaths);
+                normalizedXpaths);
 
         for (final FragmentEntity fragmentEntity : fragmentEntities) {
             normalizedXpaths.remove(fragmentEntity.getXpath());
@@ -647,15 +581,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return fragmentEntities;
     }
 
-    private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
-        if (newListElements.isEmpty()) {
-            throw new CpsAdminException("Invalid list replacement",
-                    "Cannot replace list elements with empty collection");
-        }
-        final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
-        return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
-    }
-
     private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
                                                      final DataNode newListElement,
                                                      final FragmentEntity existingListElementEntity) {
@@ -671,6 +596,28 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return existingListElementEntity;
     }
 
+    private String mergeLeaves(final Map<String, Serializable> updateLeaves, final String currentLeavesAsString) {
+        Map<String, Serializable> currentLeavesAsMap = new HashMap<>();
+        if (currentLeavesAsString != null) {
+            currentLeavesAsMap = jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class);
+            currentLeavesAsMap.putAll(updateLeaves);
+        }
+
+        if (currentLeavesAsMap.isEmpty()) {
+            return "";
+        }
+        return jsonObjectMapper.asJsonString(currentLeavesAsMap);
+    }
+
+    private void copyAttributesFromNewDataNode(final FragmentEntity existingFragmentEntity,
+                                               final DataNode newDataNode) {
+        final String oldOrderedLeavesAsJson = getOrderedLeavesAsJson(existingFragmentEntity.getAttributes());
+        final String newOrderedLeavesAsJson = getOrderedLeavesAsJson(newDataNode.getLeaves());
+        if (!oldOrderedLeavesAsJson.equals(newOrderedLeavesAsJson)) {
+            existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
+        }
+    }
+
     private String getOrderedLeavesAsJson(final Map<String, Serializable> currentLeaves) {
         final Map<String, Serializable> sortedLeaves = new TreeMap<>(String::compareTo);
         sortedLeaves.putAll(currentLeaves);
@@ -686,6 +633,38 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return jsonObjectMapper.asJsonString(sortedLeaves);
     }
 
+    private static String getNormalizedXpath(final String xpathSource) {
+        if (isRootXpath(xpathSource)) {
+            return xpathSource;
+        }
+        try {
+            return CpsPathUtil.getNormalizedXpath(xpathSource);
+        } catch (final PathParsingException pathParsingException) {
+            throw new CpsPathException(pathParsingException.getMessage());
+        }
+    }
+
+    private static Collection<String> getNormalizedXpaths(final Collection<String> xpaths) {
+        final Collection<String> normalizedXpaths = new HashSet<>(xpaths.size());
+        for (final String xpath : xpaths) {
+            try {
+                normalizedXpaths.add(getNormalizedXpath(xpath));
+            } catch (final CpsPathException cpsPathException) {
+                log.warn("Error parsing xpath \"{}\": {}", xpath, cpsPathException.getMessage());
+            }
+        }
+        return normalizedXpaths;
+    }
+
+    private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
+        if (newListElements.isEmpty()) {
+            throw new CpsAdminException("Invalid list replacement",
+                    "Cannot replace list elements with empty collection");
+        }
+        final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
+        return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf('[') + 1);
+    }
+
     private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
             final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
         return childEntities.stream()
@@ -717,25 +696,23 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
     }
 
-    private void copyAttributesFromNewDataNode(final FragmentEntity existingFragmentEntity,
-                                               final DataNode newDataNode) {
-        final String oldOrderedLeavesAsJson = getOrderedLeavesAsJson(existingFragmentEntity.getAttributes());
-        final String newOrderedLeavesAsJson = getOrderedLeavesAsJson(newDataNode.getLeaves());
-        if (!oldOrderedLeavesAsJson.equals(newOrderedLeavesAsJson)) {
-            existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
+    private static CpsPathQuery getCpsPathQuery(final String cpsPath) {
+        try {
+            return CpsPathUtil.getCpsPathQuery(cpsPath);
+        } catch (final PathParsingException e) {
+            throw new CpsPathException(e.getMessage());
         }
     }
 
-    private String mergeLeaves(final Map<String, Serializable> updateLeaves, final String currentLeavesAsString) {
-        Map<String, Serializable> currentLeavesAsMap = new HashMap<>();
-        if (currentLeavesAsString != null) {
-            currentLeavesAsMap = jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class);
-            currentLeavesAsMap.putAll(updateLeaves);
-        }
-
-        if (currentLeavesAsMap.isEmpty()) {
-            return "";
+    private static void logMissingXPaths(final Collection<String> xpaths,
+                                         final Collection<FragmentEntity> existingFragmentEntities) {
+        final Set<String> existingXPaths =
+                existingFragmentEntities.stream().map(FragmentEntity::getXpath).collect(Collectors.toSet());
+        final Set<String> missingXPaths =
+                xpaths.stream().filter(xpath -> !existingXPaths.contains(xpath)).collect(Collectors.toSet());
+        if (!missingXPaths.isEmpty()) {
+            log.warn("Cannot update data nodes: Target XPaths {} not found in DB.", missingXPaths);
         }
-        return jsonObjectMapper.asJsonString(currentLeavesAsMap);
     }
+
 }
-- 
cgit