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=fd47793a7a7b6f7a61ac1a984a6b3c1515c5fd3f;hb=26804e49980bc1d3139b09b7934e94a2ea1e7743;hp=369e5289b1a810fad8d32db7319c60f2582a7180;hpb=a05d9e732fabb2496012e0ae13d9bf61b671ee5d;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 369e5289b..fd47793a7 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,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. @@ -23,10 +23,12 @@ package org.onap.cps.spi.impl; -import com.google.common.base.Strings; +import static org.onap.cps.spi.PaginationOption.NO_PAGINATION; + import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import io.micrometer.core.annotation.Timed; +import jakarta.transaction.Transactional; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -36,10 +38,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.StaleStateException; @@ -48,13 +50,11 @@ import org.onap.cps.cpspath.parser.CpsPathUtil; import org.onap.cps.cpspath.parser.PathParsingException; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.PaginationOption; 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.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; @@ -64,7 +64,6 @@ 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; @@ -82,98 +81,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private final JsonObjectMapper jsonObjectMapper; private final SessionManager sessionManager; - private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?])?)"; - private static final String QUERY_ACROSS_ANCHORS = null; - private static final AnchorEntity ALL_ANCHORS = null; - - @Override - public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final DataNode newChildDataNode) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - addNewChildDataNode(anchorEntity, parentNodeXpath, newChildDataNode); - } - - @Override - public void addChildDataNodes(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final Collection dataNodes) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - addChildrenDataNodes(anchorEntity, parentNodeXpath, dataNodes); - } - - @Override - public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection newListElements) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - addChildrenDataNodes(anchorEntity, parentNodeXpath, newListElements); - } - - @Override - public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection> newLists) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final Collection failedXpaths = new HashSet<>(); - for (final Collection newList : newLists) { - try { - addChildrenDataNodes(anchorEntity, parentNodeXpath, newList); - } catch (final AlreadyDefinedExceptionBatch e) { - failedXpaths.addAll(e.getAlreadyDefinedXpaths()); - } - } - if (!failedXpaths.isEmpty()) { - throw new AlreadyDefinedExceptionBatch(failedXpaths); - } - } - - 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 e) { - throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorEntity.getName(), e); - } - } - - private void addChildrenDataNodes(final AnchorEntity anchorEntity, final String parentNodeXpath, - final Collection newChildren) { - final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath); - final List 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 e) { - log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations", - e, fragmentEntities.size()); - retrySavingEachChildIndividually(anchorEntity, parentNodeXpath, newChildren); - } - } - - private void retrySavingEachChildIndividually(final AnchorEntity anchorEntity, final String parentNodeXpath, - final Collection newChildren) { - final Collection failedXpaths = new HashSet<>(); - for (final DataNode newChild : newChildren) { - try { - addNewChildDataNode(anchorEntity, parentNodeXpath, newChild); - } catch (final AlreadyDefinedException e) { - failedXpaths.add(newChild.getXpath()); - } - } - 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)); - } + private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@.+?])?)"; @Override public void storeDataNodes(final String dataspaceName, final String anchorName, @@ -188,7 +96,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService fragmentRepository.saveAll(fragmentEntities); } catch (final DataIntegrityViolationException exception) { log.warn("Exception occurred : {} , While saving : {} data nodes, Retrying saving data nodes individually", - exception, dataNodes.size()); + exception, dataNodes.size()); storeDataNodesIndividually(anchorEntity, dataNodes); } } @@ -199,12 +107,12 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService try { final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, dataNode); fragmentRepository.save(fragmentEntity); - } catch (final DataIntegrityViolationException e) { + } catch (final DataIntegrityViolationException dataIntegrityViolationException) { failedXpaths.add(dataNode.getXpath()); } } if (!failedXpaths.isEmpty()) { - throw new AlreadyDefinedExceptionBatch(failedXpaths); + throw AlreadyDefinedException.forDataNodes(failedXpaths, anchorEntity.getName()); } } @@ -228,87 +136,153 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return parentFragment; } - private FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final DataNode dataNode) { - return FragmentEntity.builder() - .dataspace(anchorEntity.getDataspace()) - .anchor(anchorEntity) - .xpath(dataNode.getXpath()) - .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves())) - .build(); - } - @Override - @Timed(value = "cps.data.persistence.service.datanode.get", - description = "Time taken to get a data node") - public Collection getDataNodes(final String dataspaceName, final String anchorName, - final String xpath, - final FetchDescendantsOption fetchDescendantsOption) { - final String targetXpath = getNormalizedXpath(xpath); - final Collection dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName, - Collections.singletonList(targetXpath), fetchDescendantsOption); - if (dataNodes.isEmpty()) { - throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath); - } - return dataNodes; + public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection newListElements) { + final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + addChildrenDataNodes(anchorEntity, parentNodeXpath, newListElements); } @Override - @Timed(value = "cps.data.persistence.service.datanode.batch.get", - description = "Time taken to get data nodes") - public Collection getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName, - final Collection xpaths, - final FetchDescendantsOption fetchDescendantsOption) { + public void addChildDataNodes(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final Collection dataNodes) { final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final Collection fragmentEntities = - getFragmentEntities(anchorEntity, xpaths, fetchDescendantsOption); - return toDataNodes(fragmentEntities, fetchDescendantsOption); + addChildrenDataNodes(anchorEntity, parentNodeXpath, dataNodes); } - private Collection getFragmentEntities(final AnchorEntity anchorEntity, - final Collection xpaths, - final FetchDescendantsOption fetchDescendantsOption) { - final Collection nonRootXpaths = new HashSet<>(xpaths); - final boolean haveRootXpath = nonRootXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath); + private void addChildrenDataNodes(final AnchorEntity anchorEntity, final String parentNodeXpath, + final Collection newChildren) { + final FragmentEntity parentFragmentEntity = getFragmentEntity(anchorEntity, parentNodeXpath); + final List 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); + } + } - final Collection normalizedXpaths = new HashSet<>(nonRootXpaths.size()); - for (final String xpath : nonRootXpaths) { + 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 newChildren) { + final Collection failedXpaths = new HashSet<>(); + for (final DataNode newChild : newChildren) { try { - normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath)); - } catch (final PathParsingException e) { - log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage()); + addNewChildDataNode(anchorEntity, parentNodeXpath, newChild); + } catch (final AlreadyDefinedException alreadyDefinedException) { + failedXpaths.add(newChild.getXpath()); } } - if (haveRootXpath) { - normalizedXpaths.addAll(fragmentRepository.findAllXpathByAnchorAndParentIdIsNull(anchorEntity)); + if (!failedXpaths.isEmpty()) { + throw AlreadyDefinedException.forDataNodes(failedXpaths, anchorEntity.getName()); } + } + + @Override + public void batchUpdateDataLeaves(final String dataspaceName, final String anchorName, + final Map> updatedLeavesPerXPath) { + final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + + final Collection xpathsOfUpdatedLeaves = updatedLeavesPerXPath.keySet(); + final Collection fragmentEntities = getFragmentEntities(anchorEntity, xpathsOfUpdatedLeaves); - final List fragmentExtracts = - fragmentRepository.findExtractsWithDescendants(anchorEntity.getId(), normalizedXpaths, - fetchDescendantsOption.getDepth()); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + final Map updatedLeaves = updatedLeavesPerXPath.get(fragmentEntity.getXpath()); + final String mergedLeaves = mergeLeaves(updatedLeaves, fragmentEntity.getAttributes()); + fragmentEntity.setAttributes(mergedLeaves); + } - return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + try { + fragmentRepository.saveAll(fragmentEntities); + } catch (final StaleStateException staleStateException) { + retryUpdateDataNodesIndividually(anchorEntity, fragmentEntities); + } } - private FragmentEntity getFragmentEntity(final AnchorEntity anchorEntity, final String xpath) { - final FragmentEntity fragmentEntity; - if (isRootXpath(xpath)) { - final List fragmentExtracts = fragmentRepository.findAllExtractsByAnchor(anchorEntity); - fragmentEntity = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts) - .stream().findFirst().orElse(null); - } else { - fragmentEntity = fragmentRepository.getByAnchorAndXpath(anchorEntity, getNormalizedXpath(xpath)); + @Override + public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName, + final Collection updatedDataNodes) { + final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + + final Map xpathToUpdatedDataNode = updatedDataNodes.stream() + .collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode)); + + final Collection xpaths = xpathToUpdatedDataNode.keySet(); + Collection existingFragmentEntities = getFragmentEntities(anchorEntity, xpaths); + existingFragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities( + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS, existingFragmentEntities); + + for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) { + final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath()); + updateFragmentEntityAndDescendantsWithDataNode(existingFragmentEntity, updatedDataNode); } - if (fragmentEntity == null) { - throw new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath); + + try { + fragmentRepository.saveAll(existingFragmentEntities); + } catch (final StaleStateException staleStateException) { + retryUpdateDataNodesIndividually(anchorEntity, existingFragmentEntities); + } + } + + private void retryUpdateDataNodesIndividually(final AnchorEntity anchorEntity, + final Collection fragmentEntities) { + final Collection failedXpaths = new HashSet<>(); + for (final FragmentEntity dataNodeFragment : fragmentEntities) { + try { + fragmentRepository.save(dataNodeFragment); + } catch (final StaleStateException staleStateException) { + 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, anchorEntity.getDataspace().getName(), anchorEntity.getName())); } - return fragmentEntity; } - private Collection buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity, - final String normalizedXpath) { - final List fragmentExtracts = - fragmentRepository.findByAnchorAndParentXpath(anchorEntity, normalizedXpath); - return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity, + final DataNode newDataNode) { + copyAttributesFromNewDataNode(existingFragmentEntity, newDataNode); + + final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream() + .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); + + final Collection updatedChildFragments = new HashSet<>(); + for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) { + final FragmentEntity childFragment; + if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) { + childFragment = convertToFragmentWithAllDescendants(existingFragmentEntity.getAnchor(), + newDataNodeChild); + } else { + childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath()); + updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild); + } + updatedChildFragments.add(childFragment); + } + + existingFragmentEntity.getChildFragments().clear(); + existingFragmentEntity.getChildFragments().addAll(updatedChildFragments); } @Override @@ -316,85 +290,65 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService description = "Time taken to query data nodes") public List queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { - final AnchorEntity anchorEntity = (Strings.isNullOrEmpty(anchorName)) ? ALL_ANCHORS - : getAnchorEntity(dataspaceName, anchorName); + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); final CpsPathQuery cpsPathQuery; try { cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath); - } catch (final PathParsingException e) { - throw new CpsPathException(e.getMessage()); + } catch (final PathParsingException pathParsingException) { + throw new CpsPathException(pathParsingException.getMessage()); } Collection fragmentEntities; - if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) { - return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery); - } - fragmentEntities = (anchorEntity == ALL_ANCHORS) ? fragmentRepository.findByCpsPath(cpsPathQuery) - : fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); + fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity, cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = (anchorEntity == ALL_ANCHORS) ? getAncestorFragmentEntitiesAcrossAnchors(cpsPathQuery, - fragmentEntities) : getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); + fragmentEntities = fragmentRepository.findByAnchorAndXpathIn(anchorEntity, ancestorXpaths); } - return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities); + fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption, + fragmentEntities); + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); } @Override + @Timed(value = "cps.data.persistence.service.datanode.query.anchors", + description = "Time taken to query data nodes across all anchors or list of anchors") public List queryDataNodesAcrossAnchors(final String dataspaceName, final String cpsPath, - final FetchDescendantsOption fetchDescendantsOption) { - return queryDataNodes(dataspaceName, QUERY_ACROSS_ANCHORS, cpsPath, fetchDescendantsOption); - } + 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()); + } - private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, - final CpsPathQuery cpsPathQuery) { - return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) - && !cpsPathQuery.hasLeafConditions() - && !cpsPathQuery.hasTextFunctionCondition(); - } + final List anchorIds; + if (paginationOption == NO_PAGINATION) { + anchorIds = Collections.emptyList(); + } else { + anchorIds = getAnchorIdsForPagination(dataspaceEntity, cpsPathQuery, paginationOption); + if (anchorIds.isEmpty()) { + return Collections.emptyList(); + } + } + Collection fragmentEntities = + fragmentRepository.findByDataspaceAndCpsPath(dataspaceEntity, cpsPathQuery, anchorIds); - private List getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, - final AnchorEntity anchorEntity, - final CpsPathQuery cpsPathQuery) { - Collection fragmentEntities; - final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true); - final List fragmentExtracts = (anchorEntity == ALL_ANCHORS) - ? fragmentRepository.quickFindWithDescendantsAcrossAnchor(xpathRegex) : - fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex); - fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); if (cpsPathQuery.hasAncestorAxis()) { final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = (anchorEntity == ALL_ANCHORS) ? getAncestorFragmentEntitiesAcrossAnchors(cpsPathQuery, - fragmentEntities) : getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); - } - return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); - } - - private Collection getAncestorFragmentEntitiesAcrossAnchors(final CpsPathQuery cpsPathQuery, - final Collection fragmentEntities) { - final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - return ancestorXpaths.isEmpty() ? Collections.emptyList() : fragmentRepository.findAllByXpathIn(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)); + if (anchorIds.isEmpty()) { + fragmentEntities = fragmentRepository.findByDataspaceAndXpathIn(dataspaceEntity, ancestorXpaths); } else { - final String normalizedXpath = getNormalizedXpath(proxiedFragmentEntity.getXpath()); - final AnchorEntity anchorEntityForFragmentExtract = (anchorEntity == ALL_ANCHORS) - ? proxiedFragmentEntity.getAnchor() : anchorEntity; - final Collection unproxiedFragmentEntities = - buildFragmentEntitiesFromFragmentExtracts(anchorEntityForFragmentExtract, normalizedXpath); - for (final FragmentEntity unproxiedFragmentEntity : unproxiedFragmentEntities) { - dataNodes.add(toDataNode(unproxiedFragmentEntity, fetchDescendantsOption)); - } + fragmentEntities = fragmentRepository.findByAnchorIdsAndXpathIn( + anchorIds.toArray(new Long[0]), ancestorXpaths.toArray(new String[0])); } + } - return Collections.unmodifiableList(dataNodes); + fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption, + fragmentEntities); + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); } private List createDataNodesFromFragmentEntities(final FetchDescendantsOption fetchDescendantsOption, @@ -406,17 +360,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return Collections.unmodifiableList(dataNodes); } - private static String getNormalizedXpath(final String xpathSource) { - if (isRootXpath(xpathSource)) { - return xpathSource; - } - try { - return CpsPathUtil.getNormalizedXpath(xpathSource); - } catch (final PathParsingException e) { - throw new CpsPathException(e.getMessage()); - } - } - @Override public String startSession() { return sessionManager.startSession(); @@ -433,19 +376,17 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService sessionManager.lockAnchor(sessionId, dataspaceName, anchorName, timeoutInMilliseconds); } - private static Set processAncestorXpath(final Collection fragmentEntities, - final CpsPathQuery cpsPathQuery) { - final Set ancestorXpath = new HashSet<>(); - final Pattern pattern = - Pattern.compile("([\\s\\S]*/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) - + REG_EX_FOR_OPTIONAL_LIST_INDEX + "/[\\s\\S]*"); - for (final FragmentEntity fragmentEntity : fragmentEntities) { - final Matcher matcher = pattern.matcher(fragmentEntity.getXpath()); - if (matcher.matches()) { - ancestorXpath.add(matcher.group(1)); - } + @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()); } - return ancestorXpath; + final List anchorIdList = getAnchorIdsForPagination(dataspaceEntity, cpsPathQuery, NO_PAGINATION); + return anchorIdList.size(); } private DataNode toDataNode(final FragmentEntity fragmentEntity, @@ -463,116 +404,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService .withChildDataNodes(childDataNodes).build(); } - 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.hasNext()) { - return fragmentEntity.getChildFragments().stream() - .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 updateLeaves) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, xpath); - final String currentLeavesAsString = fragmentEntity.getAttributes(); - final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString); - fragmentEntity.setAttributes(mergedLeaves); - fragmentRepository.save(fragmentEntity); - } - - @Override - public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName, - final DataNode dataNode) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, 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())); - } - } - - @Override - public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName, - final Collection updatedDataNodes) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - - final Map xpathToUpdatedDataNode = updatedDataNodes.stream() - .collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode)); - - final Collection xpaths = xpathToUpdatedDataNode.keySet(); - final Collection existingFragmentEntities = - getFragmentEntities(anchorEntity, xpaths, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); - - for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) { - final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath()); - updateFragmentEntityAndDescendantsWithDataNode(existingFragmentEntity, updatedDataNode); - } - - try { - fragmentRepository.saveAll(existingFragmentEntities); - } catch (final StaleStateException staleStateException) { - retryUpdateDataNodesIndividually(anchorEntity, existingFragmentEntities); - } - } - - private void retryUpdateDataNodesIndividually(final AnchorEntity anchorEntity, - final Collection fragmentEntities) { - final Collection failedXpaths = new HashSet<>(); - for (final FragmentEntity dataNodeFragment : fragmentEntities) { - 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, anchorEntity.getDataspace().getName(), anchorEntity.getName())); - } + private FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final DataNode dataNode) { + return FragmentEntity.builder() + .anchor(anchorEntity) + .xpath(dataNode.getXpath()) + .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves())) + .build(); } - private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity, - final DataNode newDataNode) { - existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves())); - - final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream() - .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); - - final Collection updatedChildFragments = new HashSet<>(); - for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) { - final FragmentEntity childFragment; - if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) { - childFragment = convertToFragmentWithAllDescendants(existingFragmentEntity.getAnchor(), - newDataNodeChild); - } else { - childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath()); - updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild); - } - updatedChildFragments.add(childFragment); - } - existingFragmentEntity.getChildFragments().clear(); - existingFragmentEntity.getChildFragments().addAll(updatedChildFragments); - } @Override @Transactional @@ -630,15 +470,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final Collection deleteChecklist = new HashSet<>(xpathsToDelete.size()); - for (final String xpath : xpathsToDelete) { - try { - deleteChecklist.add(CpsPathUtil.getNormalizedXpath(xpath)); - } catch (final PathParsingException e) { - log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage()); - } - } - + final Collection deleteChecklist = getNormalizedXpaths(xpathsToDelete); final Collection xpathsToExistingContainers = fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist); if (onlySupportListDeletion) { @@ -686,6 +518,116 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } } + @Override + @Timed(value = "cps.data.persistence.service.datanode.get", + description = "Time taken to get a data node") + public Collection getDataNodes(final String dataspaceName, final String anchorName, + final String xpath, + final FetchDescendantsOption fetchDescendantsOption) { + final String targetXpath = getNormalizedXpath(xpath); + final Collection dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName, + Collections.singletonList(targetXpath), fetchDescendantsOption); + if (dataNodes.isEmpty()) { + throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath); + } + return dataNodes; + } + + @Override + @Timed(value = "cps.data.persistence.service.datanode.batch.get", + description = "Time taken to get data nodes") + public Collection getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName, + final Collection xpaths, + final FetchDescendantsOption fetchDescendantsOption) { + final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + Collection fragmentEntities = getFragmentEntities(anchorEntity, xpaths); + fragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities(fetchDescendantsOption, + fragmentEntities); + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); + } + + private List getChildDataNodes(final FragmentEntity fragmentEntity, + final FetchDescendantsOption fetchDescendantsOption) { + if (fetchDescendantsOption.hasNext()) { + return fragmentEntity.getChildFragments().stream() + .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next())) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + } + + private List getAnchorIdsForPagination(final DataspaceEntity dataspaceEntity, final CpsPathQuery cpsPathQuery, + final PaginationOption paginationOption) { + 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 getNormalizedXpaths(final Collection xpaths) { + final Collection 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)) { + fragmentEntity = fragmentRepository.findOneByAnchorId(anchorEntity.getId()).orElse(null); + } else { + fragmentEntity = fragmentRepository.getByAnchorAndXpath(anchorEntity, getNormalizedXpath(xpath)); + } + if (fragmentEntity == null) { + throw new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath); + } + return fragmentEntity; + } + + private Collection getFragmentEntities(final AnchorEntity anchorEntity, + final Collection xpaths) { + final Collection normalizedXpaths = getNormalizedXpaths(xpaths); + + final boolean haveRootXpath = normalizedXpaths.removeIf(CpsDataPersistenceServiceImpl::isRootXpath); + + final List fragmentEntities = fragmentRepository.findByAnchorAndXpathIn(anchorEntity, + normalizedXpaths); + + for (final FragmentEntity fragmentEntity : fragmentEntities) { + normalizedXpaths.remove(fragmentEntity.getXpath()); + } + + for (final String xpath : normalizedXpaths) { + if (!CpsPathUtil.isPathToListElement(xpath)) { + fragmentEntities.addAll(fragmentRepository.findListByAnchorAndXpath(anchorEntity, xpath)); + } + } + + if (haveRootXpath) { + fragmentEntities.addAll(fragmentRepository.findRootsByAnchorId(anchorEntity.getId())); + } + + return fragmentEntities; + } + private static String getListElementXpathPrefix(final Collection newListElements) { if (newListElements.isEmpty()) { throw new CpsAdminException("Invalid list replacement", @@ -702,7 +644,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return convertToFragmentWithAllDescendants(parentEntity.getAnchor(), newListElement); } if (newListElement.getChildDataNodes().isEmpty()) { - copyAttributesFromNewListElement(existingListElementEntity, newListElement); + copyAttributesFromNewDataNode(existingListElementEntity, newListElement); existingListElementEntity.getChildFragments().clear(); } else { updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement); @@ -710,17 +652,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return existingListElementEntity; } - private static boolean isNewDataNode(final DataNode replacementDataNode, - final Map existingListElementsByXpath) { - return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath()); + private String getOrderedLeavesAsJson(final Map currentLeaves) { + final Map sortedLeaves = new TreeMap<>(String::compareTo); + sortedLeaves.putAll(currentLeaves); + return jsonObjectMapper.asJsonString(sortedLeaves); } - private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity, - final DataNode newListElement) { - final FragmentEntity replacementFragmentEntity = - FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString( - newListElement.getLeaves())).build(); - existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes()); + private String getOrderedLeavesAsJson(final String currentLeavesAsString) { + if (currentLeavesAsString == null) { + return "{}"; + } + final Map sortedLeaves = jsonObjectMapper.convertJsonString(currentLeavesAsString, + TreeMap.class); + return jsonObjectMapper.asJsonString(sortedLeaves); } private static Map extractListElementFragmentEntitiesByXPath( @@ -730,22 +674,49 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity)); } + private static Set processAncestorXpath(final Collection fragmentEntities, + final CpsPathQuery cpsPathQuery) { + final Set ancestorXpath = new HashSet<>(); + final Pattern pattern = + Pattern.compile("(.*/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) + + REG_EX_FOR_OPTIONAL_LIST_INDEX + "/.*"); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + final Matcher matcher = pattern.matcher(fragmentEntity.getXpath()); + if (matcher.matches()) { + ancestorXpath.add(matcher.group(1)); + } + } + return ancestorXpath; + } + private static boolean isRootXpath(final String xpath) { return "/".equals(xpath) || "".equals(xpath); } + private static boolean isNewDataNode(final DataNode replacementDataNode, + final Map existingListElementsByXpath) { + 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 String mergeLeaves(final Map updateLeaves, final String currentLeavesAsString) { - final Map currentLeavesAsMap = currentLeavesAsString.isEmpty() - ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class); - currentLeavesAsMap.putAll(updateLeaves); + Map currentLeavesAsMap = new HashMap<>(); + if (currentLeavesAsString != null) { + currentLeavesAsMap = jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class); + currentLeavesAsMap.putAll(updateLeaves); + } + if (currentLeavesAsMap.isEmpty()) { return ""; } return jsonObjectMapper.asJsonString(currentLeavesAsMap); } - - private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) { - final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - } }