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=cca5d9c849ec6b6f9ab2ddeb7e04b9b202a46835;hb=1be9234c5234883be1e8d66baf7a780fa6be8ccf;hp=e02fb7355b9d9929276841331745ef52b63092fb;hpb=5b977b6607d696d347de08ecba638ffbf4d57d16;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 e02fb7355..cca5d9c84 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,11 @@ package org.onap.cps.spi.impl; -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; - +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; +import io.micrometer.core.annotation.Timed; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -49,23 +51,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; @@ -75,99 +77,129 @@ 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}))\\]$"); + private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@.+?])?)"; + 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) { - addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, 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) { - addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, 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 Collection failedCmHandleIds = new HashSet<>(); - newLists.forEach(newList -> { + final Collection> newLists) { + final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + final Collection failedXpaths = new HashSet<>(); + for (final Collection newList : newLists) { try { - addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList); - } catch (final AlreadyDefinedException e) { - newList.forEach(listElement -> failedCmHandleIds.add((String) listElement.getLeaves().get("id"))); + addChildrenDataNodes(anchorEntity, parentNodeXpath, newList); + } catch (final AlreadyDefinedExceptionBatch e) { + failedXpaths.addAll(e.getAlreadyDefinedXpaths()); } - }); - - if (!failedCmHandleIds.isEmpty()) { - throw new AlreadyDefinedExceptionBatch(failedCmHandleIds); } - + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); + } } - private void addNewChildDataNode(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final DataNode newChild) { - final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); - final FragmentEntity newChildAsFragmentEntity = - convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), - parentFragmentEntity.getAnchor(), newChild); + 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(), anchorName, e); + throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorEntity.getName(), e); } - } - private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection newChildren) { - final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + 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 { - newChildren.forEach(newChildAsDataNode -> { + for (final DataNode newChildAsDataNode : newChildren) { final FragmentEntity newChildAsFragmentEntity = - convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), - parentFragmentEntity.getAnchor(), newChildAsDataNode); + convertToFragmentWithAllDescendants(anchorEntity, newChildAsDataNode); newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId()); fragmentEntities.add(newChildAsFragmentEntity); - }); + } fragmentRepository.saveAll(fragmentEntities); } catch (final DataIntegrityViolationException e) { - log.warn("Exception occurred : {} , Batch with size : {} will be retried using individual save operations", + log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations", e, fragmentEntities.size()); - retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren); + retrySavingEachChildIndividually(anchorEntity, parentNodeXpath, newChildren); } } - private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final Collection newChildren) { - newChildren.forEach(newChild -> addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild)); + 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) { - final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, - dataNode); + public void storeDataNodes(final String dataspaceName, final String anchorName, + final Collection dataNodes) { + final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + final List fragmentEntities = new ArrayList<>(dataNodes.size()); try { - fragmentRepository.save(fragmentEntity); + for (final DataNode dataNode: dataNodes) { + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(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(anchorEntity, dataNodes); + } + } + + private void storeDataNodesIndividually(final AnchorEntity anchorEntity, final Collection dataNodes) { + final Collection failedXpaths = new HashSet<>(); + for (final DataNode dataNode: dataNodes) { + try { + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(anchorEntity, dataNode); + fragmentRepository.save(fragmentEntity); + } catch (final DataIntegrityViolationException e) { + failedXpaths.add(dataNode.getXpath()); + } + } + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); } } @@ -175,29 +207,25 @@ 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 dataspaceEntity dataspace * @param anchorEntity anchorEntity * @param dataNodeToBeConverted dataNode * @return a Fragment built from current DataNode */ - private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { - final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); + private FragmentEntity convertToFragmentWithAllDescendants(final AnchorEntity anchorEntity, + final DataNode dataNodeToBeConverted) { + final FragmentEntity parentFragment = toFragmentEntity(anchorEntity, dataNodeToBeConverted); final Builder childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { - final FragmentEntity childFragment = - convertToFragmentWithAllDescendants(parentFragment.getDataspace(), parentFragment.getAnchor(), - childDataNode); + final FragmentEntity childFragment = convertToFragmentWithAllDescendants(anchorEntity, childDataNode); childFragmentsImmutableSetBuilder.add(childFragment); } parentFragment.setChildFragments(childFragmentsImmutableSetBuilder.build()); return parentFragment; } - private FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, final DataNode dataNode) { + private FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final DataNode dataNode) { return FragmentEntity.builder() - .dataspace(dataspaceEntity) + .dataspace(anchorEntity.getDataspace()) .anchor(anchorEntity) .xpath(dataNode.getXpath()) .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves())) @@ -205,51 +233,180 @@ 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); + @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; } - private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName, - final String xpath) { - 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; + @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); + final Collection fragmentEntities = + getFragmentEntities(anchorEntity, xpaths, fetchDescendantsOption); + return toDataNodes(fragmentEntities, fetchDescendantsOption); + } + + 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); + + 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); } + if (haveRootXpath) { + normalizedXpaths.addAll(fragmentRepository.findAllXpathByAnchorAndParentIdIsNull(anchorEntity)); + } + + final List fragmentExtracts = + fragmentRepository.findExtractsWithDescendants(anchorEntity.getId(), normalizedXpaths, + fetchDescendantsOption.getDepth()); + + return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + } + + 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)); + } + if (fragmentEntity == null) { + throw new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath); + } + return fragmentEntity; + } + + private Collection buildFragmentEntitiesFromFragmentExtracts(final AnchorEntity anchorEntity, + final String normalizedXpath) { + final List fragmentExtracts = + fragmentRepository.findByAnchorAndParentXpath(anchorEntity, normalizedXpath); + return FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); } @Override + @Timed(value = "cps.data.persistence.service.datanode.query", + description = "Time taken to query data nodes") public List 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 AnchorEntity anchorEntity = (Strings.isNullOrEmpty(anchorName)) ? ALL_ANCHORS + : getAnchorEntity(dataspaceName, anchorName); final CpsPathQuery cpsPathQuery; try { cpsPathQuery = CpsPathUtil.getCpsPathQuery(cpsPath); } 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 = (anchorEntity == ALL_ANCHORS) ? fragmentRepository.findByCpsPath(cpsPathQuery) + : fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { - final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList() - : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); + fragmentEntities = (anchorEntity == ALL_ANCHORS) ? getAncestorFragmentEntitiesAcrossAnchors(cpsPathQuery, + fragmentEntities) : getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); + } + return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities); + } + + @Override + public List queryDataNodesAcrossAnchors(final String dataspaceName, final String cpsPath, + final FetchDescendantsOption fetchDescendantsOption) { + return queryDataNodes(dataspaceName, QUERY_ACROSS_ANCHORS, cpsPath, fetchDescendantsOption); + } + + private static boolean canUseRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final CpsPathQuery cpsPathQuery) { + return fetchDescendantsOption.equals(FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + && !cpsPathQuery.hasAncestorAxis() + && !cpsPathQuery.hasLeafConditions() + && !cpsPathQuery.hasTextFunctionCondition() + && !cpsPathQuery.hasContainsFunctionCondition(); + } + + private List getDataNodesUsingRegexQuickFind(final FetchDescendantsOption fetchDescendantsOption, + final AnchorEntity anchorEntity, + final CpsPathQuery cpsPathQuery) { + final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegexForQuickFindWithDescendants(cpsPathQuery); + final List fragmentExtracts = (anchorEntity == ALL_ANCHORS) + ? fragmentRepository.quickFindWithDescendantsAcrossAnchors(xpathRegex) + : fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex); + final Collection fragmentEntities = + FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); + 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)); + } 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)); + } + } + } + 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) { + if (isRootXpath(xpathSource)) { + return xpathSource; + } + try { + return CpsPathUtil.getNormalizedXpath(xpathSource); + } catch (final PathParsingException e) { + throw new CpsPathException(e.getMessage()); } - return fragmentEntities.stream() - .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption)) - .collect(Collectors.toUnmodifiableList()); } @Override @@ -268,12 +425,12 @@ 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 = - Pattern.compile("([\\s\\S]*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) - + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/[\\s\\S]*"); + 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()) { @@ -286,94 +443,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) + .withDataspace(fragmentEntity.getAnchor().getDataspace().getName()) + .withAnchor(fragmentEntity.getAnchor().getName()) .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::getFileName, 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 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 FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); - updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode); + 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.save(fragmentEntity); + fragmentRepository.saveAll(existingFragmentEntities); } 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); + retryUpdateDataNodesIndividually(anchorEntity, existingFragmentEntities); } } - @Override - public void updateDataNodesAndDescendants(final String dataspaceName, - final String anchorName, - final List dataNodes) { - final Map dataNodeFragmentEntityMap = dataNodes.stream() - .collect(Collectors.toMap( - dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()))); - dataNodeFragmentEntityMap.forEach( - (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode)); - try { - fragmentRepository.saveAll(dataNodeFragmentEntityMap.values()); - } catch (final StaleStateException staleStateException) { - throw new ConcurrencyException("Concurrent Transactions", - String.format("A data node in dataspace :'%s' with Anchor : '%s' is updated by another transaction.", - dataspaceName, anchorName), - staleStateException); + 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 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.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild); + childFragment = convertToFragmentWithAllDescendants(existingFragmentEntity.getAnchor(), + newDataNodeChild); } else { childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath()); updateFragmentEntityAndDescendantsWithDataNode(childFragment, newDataNodeChild); @@ -389,18 +555,18 @@ 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 AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); + final FragmentEntity parentEntity = getFragmentEntity(anchorEntity, parentNodeXpath); final String listElementXpathPrefix = getListElementXpathPrefix(newListElements); final Map existingListElementFragmentEntitiesByXPath = extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix); - deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath); + parentEntity.getChildFragments().removeAll(existingListElementFragmentEntitiesByXPath.values()); final Set updatedChildFragmentEntities = new HashSet<>(); for (final DataNode newListElement : newListElements) { final FragmentEntity existingListElementEntity = existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath()); final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement, existingListElementEntity); - updatedChildFragmentEntities.add(entityToBeAdded); } parentEntity.getChildFragments().addAll(updatedChildFragmentEntities); @@ -412,88 +578,89 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService public void deleteDataNodes(final String dataspaceName, final String anchorName) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName) - .ifPresent( - anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity))); + .ifPresent(anchorEntity -> fragmentRepository.deleteByAnchorIn(Collections.singletonList(anchorEntity))); } @Override @Transactional - public void deleteListDataNode(final String dataspaceName, final String anchorName, - final String targetXpath) { - deleteDataNode(dataspaceName, anchorName, targetXpath, true); + 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 deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) { - deleteDataNode(dataspaceName, anchorName, targetXpath, false); + public void deleteDataNodes(final String dataspaceName, final String anchorName, + final Collection xpathsToDelete) { + deleteDataNodes(dataspaceName, anchorName, xpathsToDelete, false); } - private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath, - final boolean onlySupportListNodeDeletion) { - final String parentNodeXpath; - FragmentEntity parentFragmentEntity = null; - boolean targetDeleted = false; - if (isRootXpath(targetXpath)) { + private void deleteDataNodes(final String dataspaceName, final String anchorName, + final Collection xpathsToDelete, final boolean onlySupportListDeletion) { + final boolean haveRootXpath = xpathsToDelete.stream().anyMatch(CpsDataPersistenceServiceImpl::isRootXpath); + if (haveRootXpath) { deleteDataNodes(dataspaceName, anchorName); - targetDeleted = true; - } else { - if (isRootContainerNodeXpath(targetXpath)) { - parentNodeXpath = targetXpath; - } else { - parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/')); - } - 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) { - targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath); - } else { - targetDeleted = deleteAllListElements(parentFragmentEntity, targetXpath); - final boolean tryToDeleteDataNode = !targetDeleted && !onlySupportListNodeDeletion; - if (tryToDeleteDataNode) { - targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath); - } - } + return; } - if (!targetDeleted) { - final String additionalInformation = onlySupportListNodeDeletion - ? "The target is probably not a List." : ""; - throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(), - parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation); + + 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()); + } } - } - private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) { - final String normalizedTargetXpath = CpsPathUtil.getNormalizedXpath(targetXpath); - if (parentFragmentEntity.getXpath().equals(normalizedTargetXpath)) { - fragmentRepository.delete(parentFragmentEntity); - return true; + final Collection xpathsToExistingContainers = + fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist); + if (onlySupportListDeletion) { + final Collection xpathsToExistingListElements = xpathsToExistingContainers.stream() + .filter(CpsPathUtil::isPathToListElement).collect(Collectors.toList()); + deleteChecklist.removeAll(xpathsToExistingListElements); + } else { + deleteChecklist.removeAll(xpathsToExistingContainers); } - if (parentFragmentEntity.getChildFragments() - .removeIf(fragment -> fragment.getXpath().equals(normalizedTargetXpath))) { - fragmentRepository.save(parentFragmentEntity); - return true; + + 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); } - return false; + + fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingContainers); + fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingLists); } - private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) { - final String normalizedListXpath = CpsPathUtil.getNormalizedXpath(listXpath); - final String deleteTargetXpathPrefix = normalizedListXpath + "["; - if (parentFragmentEntity.getChildFragments() - .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) { - fragmentRepository.save(parentFragmentEntity); - return true; - } - return false; + @Override + @Transactional + public void deleteListDataNode(final String dataspaceName, final String anchorName, + final String targetXpath) { + deleteDataNode(dataspaceName, anchorName, targetXpath, true); } - private static void deleteListElements( - final Collection fragmentEntities, - final Map existingListElementFragmentEntitiesByXPath) { - fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values()); + @Override + @Transactional + public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) { + deleteDataNode(dataspaceName, anchorName, targetXpath, false); + } + + private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath, + final boolean onlySupportListNodeDeletion) { + final String normalizedXpath = getNormalizedXpath(targetXpath); + try { + deleteDataNodes(dataspaceName, anchorName, Collections.singletonList(normalizedXpath), + onlySupportListNodeDeletion); + } catch (final DataNodeNotFoundExceptionBatch dataNodeNotFoundExceptionBatch) { + throw new DataNodeNotFoundException(dataspaceName, anchorName, targetXpath); + } } private static String getListElementXpathPrefix(final Collection newListElements) { @@ -509,8 +676,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final DataNode newListElement, final FragmentEntity existingListElementEntity) { if (existingListElementEntity == null) { - return convertToFragmentWithAllDescendants( - parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement); + return convertToFragmentWithAllDescendants(parentEntity.getAnchor(), newListElement); } if (newListElement.getChildDataNodes().isEmpty()) { copyAttributesFromNewListElement(existingListElementEntity, newListElement); @@ -526,10 +692,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath()); } - private static boolean isRootContainerNodeXpath(final String xpath) { - return 0 == xpath.lastIndexOf('/'); - } - private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity, final DataNode newListElement) { final FragmentEntity replacementFragmentEntity = @@ -548,4 +710,19 @@ 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); + } + + private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + } }