X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;f=cps-ri%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Fcps%2Fspi%2Fimpl%2FCpsDataPersistenceServiceImpl.java;h=f634008dc618c0dbdd893f8703008574660ae4d5;hb=ceda6a0b4aa498ae236092cf36d396c004c61cd7;hp=117cb43b4f51e49a310e57ec38622013963ca68d;hpb=1410509e33142c0c79ff0e111c63abc47f5936d3;p=cps.git diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 117cb43b4..f634008dc 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -1,8 +1,9 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2020-2022 Bell Canada. + * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +23,10 @@ package org.onap.cps.spi.impl; -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; - import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; +import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -48,22 +49,23 @@ import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.entities.AnchorEntity; import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.FragmentEntity; -import org.onap.cps.spi.entities.SchemaSetEntity; -import org.onap.cps.spi.entities.YangResourceEntity; +import org.onap.cps.spi.entities.FragmentEntityArranger; +import org.onap.cps.spi.entities.FragmentExtract; import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.ConcurrencyException; import org.onap.cps.spi.exceptions.CpsAdminException; import org.onap.cps.spi.exceptions.CpsPathException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; +import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.repository.AnchorRepository; import org.onap.cps.spi.repository.DataspaceRepository; +import org.onap.cps.spi.repository.FragmentQueryBuilder; import org.onap.cps.spi.repository.FragmentRepository; import org.onap.cps.spi.utils.SessionManager; import org.onap.cps.utils.JsonObjectMapper; -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -73,63 +75,143 @@ import org.springframework.stereotype.Service; public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService { private final DataspaceRepository dataspaceRepository; - private final AnchorRepository anchorRepository; - private final FragmentRepository fragmentRepository; - private final JsonObjectMapper jsonObjectMapper; - private final SessionManager sessionManager; private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})"; - private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE = - Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$"); @Override - @Transactional public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath, final DataNode newChildDataNode) { - addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode)); + addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode); + } + + @Override + public void addChildDataNodes(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final Collection dataNodes) { + addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes); } @Override - @Transactional public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection newListElements) { - addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements); + addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements); + } + + @Override + public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection> newLists) { + final Collection failedXpaths = new HashSet<>(); + newLists.forEach(newList -> { + try { + addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList); + } catch (final AlreadyDefinedExceptionBatch e) { + failedXpaths.addAll(e.getAlreadyDefinedXpaths()); + } + }); + + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); + } + } - private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection newChildren) { - final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + private void addNewChildDataNode(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final DataNode newChild) { + final FragmentEntity parentFragmentEntity = + getFragmentEntity(dataspaceName, anchorName, parentNodeXpath); + final FragmentEntity newChildAsFragmentEntity = + convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), + parentFragmentEntity.getAnchor(), newChild); + newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId()); try { - for (final DataNode newChildAsDataNode : newChildren) { - final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants( - parentFragmentEntity.getDataspace(), - parentFragmentEntity.getAnchor(), - newChildAsDataNode); + fragmentRepository.save(newChildAsFragmentEntity); + } catch (final DataIntegrityViolationException e) { + throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e); + } + + } + + private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection newChildren) { + final FragmentEntity parentFragmentEntity = + getFragmentEntity(dataspaceName, anchorName, parentNodeXpath); + final List fragmentEntities = new ArrayList<>(newChildren.size()); + try { + newChildren.forEach(newChildAsDataNode -> { + final FragmentEntity newChildAsFragmentEntity = + convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), + parentFragmentEntity.getAnchor(), newChildAsDataNode); newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId()); - fragmentRepository.save(newChildAsFragmentEntity); + fragmentEntities.add(newChildAsFragmentEntity); + }); + fragmentRepository.saveAll(fragmentEntities); + } catch (final DataIntegrityViolationException e) { + log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations", + e, fragmentEntities.size()); + retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren); + } + } + + private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName, + final String parentNodeXpath, + final Collection newChildren) { + final Collection failedXpaths = new HashSet<>(); + for (final DataNode newChild : newChildren) { + try { + addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild); + } catch (final AlreadyDefinedException e) { + failedXpaths.add(newChild.getXpath()); } - } catch (final DataIntegrityViolationException exception) { - final List conflictXpaths = newChildren.stream() - .map(DataNode::getXpath) - .collect(Collectors.toList()); - throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception); + } + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); } } @Override public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) { + storeDataNodes(dataspaceName, anchorName, Collections.singletonList(dataNode)); + } + + @Override + public void storeDataNodes(final String dataspaceName, final String anchorName, + final Collection dataNodes) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, - dataNode); + final List fragmentEntities = new ArrayList<>(dataNodes.size()); try { - fragmentRepository.save(fragmentEntity); + for (final DataNode dataNode: dataNodes) { + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, + dataNode); + fragmentEntities.add(fragmentEntity); + } + fragmentRepository.saveAll(fragmentEntities); } catch (final DataIntegrityViolationException exception) { - throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception); + log.warn("Exception occurred : {} , While saving : {} data nodes, Retrying saving data nodes individually", + exception, dataNodes.size()); + storeDataNodesIndividually(dataspaceName, anchorName, dataNodes); + } + } + + private void storeDataNodesIndividually(final String dataspaceName, final String anchorName, + final Collection dataNodes) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final Collection failedXpaths = new HashSet<>(); + for (final DataNode dataNode: dataNodes) { + try { + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, + dataNode); + fragmentRepository.save(fragmentEntity); + } catch (final DataIntegrityViolationException e) { + failedXpaths.add(dataNode.getXpath()); + } + } + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); } } @@ -143,7 +225,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService * @return a Fragment built from current DataNode */ private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { + final AnchorEntity anchorEntity, + final DataNode dataNodeToBeConverted) { final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); final Builder childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { @@ -167,28 +250,83 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } @Override - public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath, - final FetchDescendantsOption fetchDescendantsOption) { - final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); - return toDataNode(fragmentEntity, fetchDescendantsOption); + public Collection getDataNodes(final String dataspaceName, final String anchorName, + final String xpath, + final FetchDescendantsOption fetchDescendantsOption) { + final String targetXpath = isRootXpath(xpath) ? xpath : CpsPathUtil.getNormalizedXpath(xpath); + final Collection dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName, + Collections.singletonList(targetXpath), fetchDescendantsOption); + if (dataNodes.isEmpty()) { + throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath); + } + return dataNodes; + } + + @Override + public Collection getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName, + final Collection xpaths, + final FetchDescendantsOption fetchDescendantsOption) { + final Collection fragmentEntities = getFragmentEntities(dataspaceName, anchorName, xpaths); + return toDataNodes(fragmentEntities, fetchDescendantsOption); } - private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName, - final String xpath) { + private Collection getFragmentEntities(final String dataspaceName, final String anchorName, + final Collection xpaths) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - if (isRootXpath(xpath)) { - return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); - } else { - final String normalizedXpath; + + final Collection nonRootXpaths = new HashSet<>(xpaths); + final boolean haveRootXpath = nonRootXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath); + + final Collection normalizedXpaths = new HashSet<>(nonRootXpaths.size()); + for (final String xpath : nonRootXpaths) { try { - normalizedXpath = CpsPathUtil.getNormalizedXpath(xpath); + normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath)); } catch (final PathParsingException e) { - throw new CpsPathException(e.getMessage()); + log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage()); } - return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, - normalizedXpath); } + final Collection fragmentEntities = + new HashSet<>(fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), normalizedXpaths)); + + if (haveRootXpath) { + final List fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity, + anchorEntity); + fragmentEntities.addAll(FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts)); + } + + return fragmentEntities; + } + + private FragmentEntity getFragmentEntity(final String dataspaceName, final String anchorName, final String xpath) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final FragmentEntity fragmentEntity; + if (isRootXpath(xpath)) { + final List fragmentExtracts = fragmentRepository.getTopLevelFragments(dataspaceEntity, + anchorEntity); + fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts) + .stream().findFirst().orElse(null); + } else { + final String normalizedXpath = getNormalizedXpath(xpath); + fragmentEntity = + fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath); + } + if (fragmentEntity == null) { + throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath); + } + return fragmentEntity; + + } + + private Collection buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity, + final String normalizedXpath) { + final List fragmentExtracts = + fragmentRepository.findByAnchorIdAndParentXpath(anchorEntity.getId(), normalizedXpath); + log.debug("Fetched {} fragment entities by anchor {} and cps path {}.", + fragmentExtracts.size(), anchorEntity.getName(), normalizedXpath); + return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + } @Override @@ -202,16 +340,84 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } catch (final PathParsingException e) { throw new CpsPathException(e.getMessage()); } - List fragmentEntities = - fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); + + Collection fragmentEntities; + if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) { + return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery); + } + fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { - final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() - : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities); + } + return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities); + } + + private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final CpsPathQuery cpsPathQuery) { + return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + && !cpsPathQuery.hasLeafConditions() + && !cpsPathQuery.hasTextFunctionCondition(); + } + + private List getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final CpsPathQuery cpsPathQuery) { + Collection fragmentEntities; + final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true); + final List fragmentExtracts = + fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex); + fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + if (cpsPathQuery.hasAncestorAxis()) { + fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities); + } + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); + } + + private Collection getAncestorFragmentEntities(final int anchorId, + final CpsPathQuery cpsPathQuery, + final Collection fragmentEntities) { + final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); + return ancestorXpaths.isEmpty() ? Collections.emptyList() + : fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorId, ancestorXpaths); + } + + private List createDataNodesFromProxiedFragmentEntities( + final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final Collection proxiedFragmentEntities) { + final List dataNodes = new ArrayList<>(proxiedFragmentEntities.size()); + for (final FragmentEntity proxiedFragmentEntity : proxiedFragmentEntities) { + if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) { + dataNodes.add(toDataNode(proxiedFragmentEntity, fetchDescendantsOption)); + } else { + final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath()); + final Collection unproxiedFragmentEntities = + buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath); + for (final FragmentEntity unproxiedFragmentEntity : unproxiedFragmentEntities) { + dataNodes.add(toDataNode(unproxiedFragmentEntity, fetchDescendantsOption)); + } + } } - return fragmentEntities.stream() - .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption)) - .collect(Collectors.toUnmodifiableList()); + return Collections.unmodifiableList(dataNodes); + } + + private List createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, + final Collection fragmentEntities) { + final List dataNodes = new ArrayList<>(fragmentEntities.size()); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption)); + } + return Collections.unmodifiableList(dataNodes); + } + + private static String getNormalizedXpath(final String xpathSource) { + final String normalizedXpath; + try { + normalizedXpath = CpsPathUtil.getNormalizedXpath(xpathSource); + } catch (final PathParsingException e) { + throw new CpsPathException(e.getMessage()); + } + return normalizedXpath; } @Override @@ -230,7 +436,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds); } - private static Set processAncestorXpath(final List fragmentEntities, + private static Set processAncestorXpath(final Collection fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set ancestorXpath = new HashSet<>(); final Pattern pattern = @@ -248,61 +454,103 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private DataNode toDataNode(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { final List childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption); - Map leaves = new HashMap<>(); + Map leaves = new HashMap<>(); if (fragmentEntity.getAttributes() != null) { leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class); } return new DataNodeBuilder() - .withModuleNamePrefix(getFirstModuleName(fragmentEntity)) .withXpath(fragmentEntity.getXpath()) .withLeaves(leaves) .withChildDataNodes(childDataNodes).build(); } - private String getFirstModuleName(final FragmentEntity fragmentEntity) { - final SchemaSetEntity schemaSetEntity = fragmentEntity.getAnchor().getSchemaSet(); - final Map yangResourceNameToContent = - schemaSetEntity.getYangResources().stream().collect( - Collectors.toMap(YangResourceEntity::getName, YangResourceEntity::getContent)); - final SchemaContext schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) - .getSchemaContext(); - return schemaContext.getModules().iterator().next().getName(); + private Collection toDataNodes(final Collection fragmentEntities, + final FetchDescendantsOption fetchDescendantsOption) { + final Collection dataNodes = new ArrayList<>(fragmentEntities.size()); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption)); + } + return dataNodes; } private List getChildDataNodes(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { - if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) { + if (fetchDescendantsOption.hasNext()) { return fragmentEntity.getChildFragments().stream() - .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption)) - .collect(Collectors.toUnmodifiableList()); + .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next())) + .collect(Collectors.toList()); } return Collections.emptyList(); } @Override public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath, - final Map leaves) { - final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); - fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves)); + final Map updateLeaves) { + final FragmentEntity fragmentEntity = getFragmentEntity(dataspaceName, anchorName, xpath); + final String currentLeavesAsString = fragmentEntity.getAttributes(); + final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString); + fragmentEntity.setAttributes(mergedLeaves); fragmentRepository.save(fragmentEntity); } @Override - public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) { - final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); - replaceDataNodeTree(fragmentEntity, dataNode); + public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName, + final DataNode dataNode) { + final FragmentEntity fragmentEntity = getFragmentEntity(dataspaceName, anchorName, dataNode.getXpath()); + updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode); try { fragmentRepository.save(fragmentEntity); } catch (final StaleStateException staleStateException) { throw new ConcurrencyException("Concurrent Transactions", String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.", - dataspaceName, anchorName, dataNode.getXpath()), - staleStateException); + dataspaceName, anchorName, dataNode.getXpath())); } } - private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, - final DataNode newDataNode) { + @Override + public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName, + final List updatedDataNodes) { + final Map xpathToUpdatedDataNode = updatedDataNodes.stream() + .collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode)); + + final Collection xpaths = xpathToUpdatedDataNode.keySet(); + final Collection existingFragmentEntities = + getFragmentEntities(dataspaceName, anchorName, xpaths); + + for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) { + final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath()); + updateFragmentEntityAndDescendantsWithDataNode(existingFragmentEntity, updatedDataNode); + } + + try { + fragmentRepository.saveAll(existingFragmentEntities); + } catch (final StaleStateException staleStateException) { + retryUpdateDataNodesIndividually(dataspaceName, anchorName, existingFragmentEntities); + } + } + + private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName, + final Collection fragmentEntities) { + final Collection failedXpaths = new HashSet<>(); + + fragmentEntities.forEach(dataNodeFragment -> { + try { + fragmentRepository.save(dataNodeFragment); + } catch (final StaleStateException e) { + failedXpaths.add(dataNodeFragment.getXpath()); + } + }); + + if (!failedXpaths.isEmpty()) { + final String failedXpathsConcatenated = String.join(",", failedXpaths); + throw new ConcurrencyException("Concurrent Transactions", String.format( + "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.", + failedXpathsConcatenated, dataspaceName, anchorName)); + } + } + + private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity, + final DataNode newDataNode) { existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves())); @@ -318,10 +566,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild); } else { childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath()); - replaceDataNodeTree(childFragment, newDataNodeChild); + updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild); } updatedChildFragments.add(childFragment); } + existingFragmentEntity.getChildFragments().clear(); existingFragmentEntity.getChildFragments().addAll(updatedChildFragments); } @@ -330,7 +579,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Transactional public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection newListElements) { - final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final FragmentEntity parentEntity = getFragmentEntity(dataspaceName, anchorName, parentNodeXpath); final String listElementXpathPrefix = getListElementXpathPrefix(newListElements); final Map existingListElementFragmentEntitiesByXPath = extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix); @@ -357,6 +606,48 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity))); } + @Override + @Transactional + public void deleteDataNodes(final String dataspaceName, final Collection anchorNames) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final Collection anchorEntities = + anchorRepository.findAllByDataspaceAndNameIn(dataspaceEntity, anchorNames); + fragmentRepository.deleteByAnchorIn(anchorEntities); + } + + @Override + @Transactional + public void deleteDataNodes(final String dataspaceName, final String anchorName, + final Collection xpathsToDelete) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + + final Collection deleteChecklist = new HashSet<>(xpathsToDelete.size()); + for (final String xpath : xpathsToDelete) { + try { + deleteChecklist.add(CpsPathUtil.getNormalizedXpath(xpath)); + } catch (final PathParsingException e) { + log.debug("Error parsing xpath \"{}\": {}", xpath, e.getMessage()); + } + } + + final Collection xpathsToExistingContainers = + fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist); + deleteChecklist.removeAll(xpathsToExistingContainers); + + final Collection xpathsToExistingLists = deleteChecklist.stream() + .filter(xpath -> fragmentRepository.existsByAnchorAndXpathStartsWith(anchorEntity, xpath + "[")) + .collect(Collectors.toList()); + deleteChecklist.removeAll(xpathsToExistingLists); + + if (!deleteChecklist.isEmpty()) { + throw new DataNodeNotFoundExceptionBatch(dataspaceName, anchorName, deleteChecklist); + } + + fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingContainers); + fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingLists); + } + @Override @Transactional public void deleteListDataNode(final String dataspaceName, final String anchorName, @@ -374,7 +665,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final boolean onlySupportListNodeDeletion) { final String parentNodeXpath; FragmentEntity parentFragmentEntity = null; - boolean targetDeleted = false; + boolean targetDeleted; if (isRootXpath(targetXpath)) { deleteDataNodes(dataspaceName, anchorName); targetDeleted = true; @@ -382,13 +673,10 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService if (isRootContainerNodeXpath(targetXpath)) { parentNodeXpath = targetXpath; } else { - parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/')); + parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(targetXpath); } - parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); - final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/')); - final boolean isListElement = REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE - .matcher(lastXpathElement).find(); - if (isListElement) { + parentFragmentEntity = getFragmentEntity(dataspaceName, anchorName, parentNodeXpath); + if (CpsPathUtil.isPathToListElement(targetXpath)) { targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath); } else { targetDeleted = deleteAllListElements(parentFragmentEntity, targetXpath); @@ -409,7 +697,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) { final String normalizedTargetXpath = CpsPathUtil.getNormalizedXpath(targetXpath); if (parentFragmentEntity.getXpath().equals(normalizedTargetXpath)) { - fragmentRepository.delete(parentFragmentEntity); + fragmentRepository.deleteFragmentEntity(parentFragmentEntity.getId()); return true; } if (parentFragmentEntity.getChildFragments() @@ -457,7 +745,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService copyAttributesFromNewListElement(existingListElementEntity, newListElement); existingListElementEntity.getChildFragments().clear(); } else { - replaceDataNodeTree(existingListElementEntity, newListElement); + updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement); } return existingListElementEntity; } @@ -489,4 +777,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private static boolean isRootXpath(final String xpath) { return "/".equals(xpath) || "".equals(xpath); } + + private String mergeLeaves(final Map updateLeaves, final String currentLeavesAsString) { + final Map currentLeavesAsMap = currentLeavesAsString.isEmpty() + ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class); + currentLeavesAsMap.putAll(updateLeaves); + if (currentLeavesAsMap.isEmpty()) { + return ""; + } + return jsonObjectMapper.asJsonString(currentLeavesAsMap); + } }