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=2397d317920c39fa7bcb7ee8965814644451c9f4;hb=46de0fbcdd71324353cf9adf824060a57bc5d642;hp=af1eca33e67035f391eb5f53e2e7e713f845cc11;hpb=c37678a3eb62685d32a1581729e2a4e26002bffc;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 af1eca33e..2397d3179 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,5 +1,5 @@ /* - * ============LICENSE_START======================================================= + * ============LICENSE_START======================================================= * Copyright (C) 2021 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2020-2021 Bell Canada. @@ -9,6 +9,7 @@ * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,57 +24,80 @@ package org.onap.cps.spi.impl; import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.StaleStateException; import org.onap.cps.cpspath.parser.CpsPathQuery; -import org.onap.cps.cpspath.parser.CpsPathQueryType; import org.onap.cps.spi.CpsDataPersistenceService; 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.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.ConcurrencyException; import org.onap.cps.spi.exceptions.CpsPathException; +import org.onap.cps.spi.exceptions.DataNodeNotFoundException; +import org.onap.cps.spi.exceptions.DataValidationException; 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.FragmentRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @Service +@Slf4j public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService { - @Autowired private DataspaceRepository dataspaceRepository; - @Autowired private AnchorRepository anchorRepository; - @Autowired private FragmentRepository fragmentRepository; + private final ObjectMapper objectMapper; + + /** + * Constructor. + * + * @param dataspaceRepository dataspaceRepository + * @param anchorRepository anchorRepository + * @param fragmentRepository fragmentRepository + */ + public CpsDataPersistenceServiceImpl(final DataspaceRepository dataspaceRepository, + final AnchorRepository anchorRepository, final FragmentRepository fragmentRepository) { + this.dataspaceRepository = dataspaceRepository; + this.anchorRepository = anchorRepository; + this.fragmentRepository = fragmentRepository; + objectMapper = new ObjectMapper(); + } + private static final Gson GSON = new GsonBuilder().create(); - private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@\\S+?]){0,1})"; + private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})"; + private static final String REG_EX_FOR_LIST_NODE_KEY = "\\[(\\@([^/]*?)){0,99}( and)*\\]$"; @Override public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath, final DataNode dataNode) { final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentXpath); - final var fragmentEntity = + final FragmentEntity fragmentEntity = toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode); parentFragment.getChildFragments().add(fragmentEntity); try { @@ -94,6 +118,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService parentFragment.getChildFragments().addAll(newFragmentEntities); try { fragmentRepository.save(parentFragment); + dataNodes.forEach( + dataNode -> getChildFragments(dataspaceName, anchorName, dataNode) + ); } catch (final DataIntegrityViolationException exception) { final List conflictXpaths = dataNodes.stream() .map(DataNode::getXpath) @@ -104,9 +131,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); - final var fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final FragmentEntity fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, dataNode); try { fragmentRepository.save(fragmentEntity); @@ -126,7 +153,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService */ private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { - final var parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); + final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); final Builder childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { final FragmentEntity childFragment = @@ -138,6 +165,17 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return parentFragment; } + private void getChildFragments(final String dataspaceName, final String anchorName, final DataNode dataNode) { + for (final DataNode childDataNode: dataNode.getChildDataNodes()) { + final FragmentEntity getChildsParentFragmentByXPath = + getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); + final FragmentEntity childFragmentEntity = toFragmentEntity(getChildsParentFragmentByXPath.getDataspace(), + getChildsParentFragmentByXPath.getAnchor(), childDataNode); + getChildsParentFragmentByXPath.getChildFragments().add(childFragmentEntity); + fragmentRepository.save(getChildsParentFragmentByXPath); + } + } + private static FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity, final AnchorEntity anchorEntity, final DataNode dataNode) { return FragmentEntity.builder() @@ -151,16 +189,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath, final FetchDescendantsOption fetchDescendantsOption) { - final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); return toDataNode(fragmentEntity, fetchDescendantsOption); } private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName, final String xpath) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); if (isRootXpath(xpath)) { - return fragmentRepository.getFirstByDataspaceAndAnchor(dataspaceEntity, anchorEntity); + return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); } else { return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, xpath); @@ -170,28 +208,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public List queryDataNodes(final String dataspaceName, final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); final CpsPathQuery cpsPathQuery; try { cpsPathQuery = CpsPathQuery.createFrom(cpsPath); } catch (final IllegalStateException e) { throw new CpsPathException(e.getMessage()); } - List fragmentEntities; - if (CpsPathQueryType.XPATH_LEAF_VALUE.equals(cpsPathQuery.getCpsPathQueryType())) { - fragmentEntities = fragmentRepository - .getByAnchorAndXpathAndLeafAttributes(anchorEntity.getId(), cpsPathQuery.getXpathPrefix(), - cpsPathQuery.getLeafName(), cpsPathQuery.getLeafValue()); - } else if (CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES.equals(cpsPathQuery.getCpsPathQueryType())) { - final String leafDataAsJson = GSON.toJson(cpsPathQuery.getLeavesData()); - fragmentEntities = fragmentRepository - .getByAnchorAndDescendentNameAndLeafValues(anchorEntity.getId(), cpsPathQuery.getDescendantName(), - leafDataAsJson); - } else { - fragmentEntities = fragmentRepository - .getByAnchorAndXpathEndsInDescendantName(anchorEntity.getId(), cpsPathQuery.getDescendantName()); - } + List fragmentEntities = + fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); fragmentEntities = ancestorXpaths.isEmpty() @@ -205,11 +231,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private static Set processAncestorXpath(final List fragmentEntities, final CpsPathQuery cpsPathQuery) { final Set ancestorXpath = new HashSet<>(); - final var pattern = - Pattern.compile("(\\S*\\/" + Pattern.quote(cpsPathQuery.getAncestorSchemaNodeIdentifier()) - + REG_EX_FOR_OPTIONAL_LIST_INDEX + "\\/\\S*"); + 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 var matcher = pattern.matcher(fragmentEntity.getXpath()); + final Matcher matcher = pattern.matcher(fragmentEntity.getXpath()); if (matcher.matches()) { ancestorXpath.add(matcher.group(1)); } @@ -217,17 +243,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return ancestorXpath; } - private static DataNode toDataNode(final FragmentEntity fragmentEntity, + private DataNode toDataNode(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { - final Map leaves = GSON.fromJson(fragmentEntity.getAttributes(), Map.class); final List childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption); + Map leaves = new HashMap<>(); + if (fragmentEntity.getAttributes() != null) { + try { + leaves = objectMapper.readValue(fragmentEntity.getAttributes(), Map.class); + } catch (final JsonProcessingException jsonProcessingException) { + final String message = "Parsing error occurred while processing fragmentEntity attributes."; + log.error(message); + throw new DataValidationException(message, + jsonProcessingException.getMessage(), jsonProcessingException); + } + } return new DataNodeBuilder() .withXpath(fragmentEntity.getXpath()) .withLeaves(leaves) .withChildDataNodes(childDataNodes).build(); } - private static List getChildDataNodes(final FragmentEntity fragmentEntity, + private List getChildDataNodes(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) { return fragmentEntity.getChildFragments().stream() @@ -240,55 +276,151 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath, final Map leaves) { - final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); fragmentEntity.setAttributes(GSON.toJson(leaves)); fragmentRepository.save(fragmentEntity); } @Override public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) { - final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); - removeExistingDescendants(fragmentEntity); + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); + replaceDataNodeTree(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); + } + } - fragmentEntity.setAttributes(GSON.toJson(dataNode.getLeaves())); - final Set childFragmentEntities = dataNode.getChildDataNodes().stream().map( - childDataNode -> convertToFragmentWithAllDescendants( - fragmentEntity.getDataspace(), fragmentEntity.getAnchor(), childDataNode) - ).collect(Collectors.toUnmodifiableSet()); - fragmentEntity.setChildFragments(childFragmentEntities); + private static void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, + final DataNode submittedDataNode) { - fragmentRepository.save(fragmentEntity); + existingFragmentEntity.setAttributes(GSON.toJson(submittedDataNode.getLeaves())); + + final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments() + .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); + + final Collection updatedChildFragments = new HashSet<>(); + + for (final DataNode submittedChildDataNode : submittedDataNode.getChildDataNodes()) { + final FragmentEntity childFragment; + if (isNewDataNode(submittedChildDataNode, existingChildrenByXpath)) { + childFragment = convertToFragmentWithAllDescendants( + existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), submittedChildDataNode); + } else { + childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath()); + replaceDataNodeTree(childFragment, submittedChildDataNode); + } + updatedChildFragments.add(childFragment); + } + existingFragmentEntity.getChildFragments().clear(); + existingFragmentEntity.getChildFragments().addAll(updatedChildFragments); } @Override @Transactional public void replaceListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection dataNodes) { - final var parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); - final var firstChildNodeXpath = dataNodes.iterator().next().getXpath(); - final var listNodeXpath = firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[")); - removeListNodeDescendants(parentEntity, listNodeXpath); - final Set childFragmentEntities = dataNodes.stream().map( - dataNode -> convertToFragmentWithAllDescendants( - parentEntity.getDataspace(), parentEntity.getAnchor(), dataNode) - ).collect(Collectors.toUnmodifiableSet()); - parentEntity.getChildFragments().addAll(childFragmentEntities); + final Collection replacementDataNodes) { + final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final String listNodeXpathPrefix = getListNodeXpathPrefix(replacementDataNodes); + final Map existingListElementFragmentEntitiesByXPath = + extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listNodeXpathPrefix); + removeExistingListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath); + final Set updatedChildFragmentEntities = new HashSet<>(); + for (final DataNode replacementDataNode : replacementDataNodes) { + final FragmentEntity existingListNodeElementEntity = + existingListElementFragmentEntitiesByXPath.get(replacementDataNode.getXpath()); + final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, replacementDataNode, + existingListNodeElementEntity); + + updatedChildFragmentEntities.add(entityToBeAdded); + } + parentEntity.getChildFragments().addAll(updatedChildFragmentEntities); fragmentRepository.save(parentEntity); } + private static void removeExistingListElements( + final Collection fragmentEntities, + final Map existingListElementFragmentEntitiesByXPath) { + fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values()); + } + + private static String getListNodeXpathPrefix(final Collection replacementDataNodes) { + final String firstChildNodeXpath = replacementDataNodes.iterator().next().getXpath(); + return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[") + 1); + } + + private static FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity, + final DataNode replacementDataNode, + final FragmentEntity existingListNodeElementEntity) { + if (existingListNodeElementEntity == null) { + return convertToFragmentWithAllDescendants( + parentEntity.getDataspace(), parentEntity.getAnchor(), replacementDataNode); + } + if (replacementDataNode.getChildDataNodes().isEmpty()) { + copyAttributesFromReplacementDataNode(existingListNodeElementEntity, replacementDataNode); + existingListNodeElementEntity.getChildFragments().clear(); + } else { + replaceDataNodeTree(existingListNodeElementEntity, replacementDataNode); + } + return existingListNodeElementEntity; + } + + private static boolean isNewDataNode(final DataNode replacementDataNode, + final Map existingListNodeElementsByXpath) { + return !existingListNodeElementsByXpath.containsKey(replacementDataNode.getXpath()); + } + + private static void copyAttributesFromReplacementDataNode(final FragmentEntity existingListNodeElementEntity, + final DataNode replacementDataNode) { + final FragmentEntity replacementFragmentEntity = + FragmentEntity.builder().attributes(GSON.toJson(replacementDataNode.getLeaves())).build(); + existingListNodeElementEntity.setAttributes(replacementFragmentEntity.getAttributes()); + } + + private static Map extractListElementFragmentEntitiesByXPath( + final Set childEntities, final String listNodeXpathPrefix) { + return childEntities.stream() + .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listNodeXpathPrefix)) + .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity)); + } + + @Override + @Transactional + public void deleteListDataNodes(final String dataspaceName, final String anchorName, final String listNodeXpath) { + final String parentNodeXpath = listNodeXpath.substring(0, listNodeXpath.lastIndexOf('/')); + final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final String descendantNode = listNodeXpath.substring(listNodeXpath.lastIndexOf('/')); + final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(descendantNode); + + final boolean xpathPointsToAValidChildNodeWithKey = parentEntity.getChildFragments().stream().anyMatch( + fragment -> fragment.getXpath().equals(listNodeXpath)); + + final boolean xpathPointsToAValidChildNodeWithoutKey = parentEntity.getChildFragments().stream().anyMatch( + fragment -> fragment.getXpath().replaceAll(REG_EX_FOR_LIST_NODE_KEY, "").equals(listNodeXpath)); + + if ((descendantNodeHasListNodeKey.find() && xpathPointsToAValidChildNodeWithKey) + || + (!descendantNodeHasListNodeKey.find() && xpathPointsToAValidChildNodeWithoutKey)) { + removeListNodeDescendants(parentEntity, listNodeXpath); + } else { + throw new DataNodeNotFoundException(parentEntity.getDataspace().getName(), + parentEntity.getAnchor().getName(), listNodeXpath); + } + } + private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) { - final String listNodeXpathPrefix = listNodeXpath + "["; + final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(listNodeXpath); + final String listNodeXpathPrefix = listNodeXpath + (descendantNodeHasListNodeKey.find() ? "" : "["); if (parentFragmentEntity.getChildFragments() .removeIf(fragment -> fragment.getXpath().startsWith(listNodeXpathPrefix))) { fragmentRepository.save(parentFragmentEntity); } } - private void removeExistingDescendants(final FragmentEntity fragmentEntity) { - fragmentEntity.setChildFragments(Collections.emptySet()); - fragmentRepository.save(fragmentEntity); - } - private static boolean isRootXpath(final String xpath) { return "/".equals(xpath) || "".equals(xpath); }