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=85b0cb73fac6ea07df4ba2d79f1845acf6e2b177;hb=c9af4eac4dc7f86bd3f33d923f9efe4a8523ed57;hp=fdbafd4bee579d57446ade96c09b0790b0af868d;hpb=0e210d77ec39915046a95615f5f9d2a2dc65162b;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 fdbafd4be..85b0cb73f 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,8 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2020-2021 Bell Canada. + * Modifications Copyright (C) 2020-2022 Bell Canada. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,17 +26,18 @@ import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; 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.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.StaleStateException; import org.onap.cps.cpspath.parser.CpsPathQuery; @@ -48,46 +49,39 @@ 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.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.onap.cps.utils.JsonObjectMapper; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @Service @Slf4j +@RequiredArgsConstructor public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService { - private DataspaceRepository dataspaceRepository; + private final DataspaceRepository dataspaceRepository; - private AnchorRepository anchorRepository; + private final AnchorRepository anchorRepository; - private FragmentRepository fragmentRepository; + private final FragmentRepository fragmentRepository; - /** - * 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; - } + private final JsonObjectMapper jsonObjectMapper; - 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 Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE = + Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$"); @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 { @@ -98,7 +92,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } @Override - public void addListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, + public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection dataNodes) { final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); final List newFragmentEntities = @@ -108,6 +102,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) @@ -118,9 +115,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); @@ -138,9 +135,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService * @param dataNodeToBeConverted dataNode * @return a Fragment built from current DataNode */ - private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, + private 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 = @@ -152,27 +149,38 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService return parentFragment; } - private static FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity, + 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 FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity, final AnchorEntity anchorEntity, final DataNode dataNode) { return FragmentEntity.builder() .dataspace(dataspaceEntity) .anchor(anchorEntity) .xpath(dataNode.getXpath()) - .attributes(GSON.toJson(dataNode.getLeaves())) + .attributes(jsonObjectMapper.asJsonString(dataNode.getLeaves())) .build(); } @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.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); } else { @@ -184,8 +192,8 @@ 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); @@ -207,11 +215,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)); } @@ -219,17 +227,20 @@ 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) { + leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class); + } 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() @@ -242,15 +253,14 @@ 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); - fragmentEntity.setAttributes(GSON.toJson(leaves)); + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); + fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(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()); + public void replaceDataNodeTree(final String dataspaceName, final String anchorName, final DataNode dataNode) { + final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); replaceDataNodeTree(fragmentEntity, dataNode); try { fragmentRepository.save(fragmentEntity); @@ -262,51 +272,164 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } } - private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, final DataNode submittedDataNode) { + private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, + final DataNode newDataNode) { - existingFragmentEntity.setAttributes(GSON.toJson(submittedDataNode.getLeaves())); + existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves())); final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments() .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); - final var updatedChildFragments = new HashSet(); + final Collection updatedChildFragments = new HashSet<>(); - for (final DataNode submittedChildDataNode : submittedDataNode.getChildDataNodes()) { + for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) { final FragmentEntity childFragment; - if (existingChildrenByXpath.containsKey(submittedChildDataNode.getXpath())) { - childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath()); - replaceDataNodeTree(childFragment, submittedChildDataNode); - } else { + if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) { childFragment = convertToFragmentWithAllDescendants( - existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), submittedChildDataNode); + existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild); + } else { + childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath()); + replaceDataNodeTree(childFragment, newDataNodeChild); } updatedChildFragments.add(childFragment); } - existingFragmentEntity.setChildFragments(updatedChildFragments); + 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); + public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection newListElements) { + final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final String listElementXpathPrefix = getListElementXpathPrefix(newListElements); + final Map existingListElementFragmentEntitiesByXPath = + extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix); + deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath); + 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); fragmentRepository.save(parentEntity); } - private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) { - final String listNodeXpathPrefix = listNodeXpath + "["; + @Override + @Transactional + 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))); + } + + @Override + @Transactional + public void deleteListDataNode(final String dataspaceName, final String anchorName, + final String targetXpath) { + deleteDataNode(dataspaceName, anchorName, targetXpath, true); + } + + @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 parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/')); + final FragmentEntity 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(); + boolean targetExist; + if (isListElement) { + targetExist = deleteDataNode(parentFragmentEntity, targetXpath); + } else { + targetExist = deleteAllListElements(parentFragmentEntity, targetXpath); + final boolean tryToDeleteDataNode = !targetExist && !onlySupportListNodeDeletion; + if (tryToDeleteDataNode) { + targetExist = deleteDataNode(parentFragmentEntity, targetXpath); + } + } + if (!targetExist) { + final String additionalInformation = onlySupportListNodeDeletion + ? "The target is probably not a List." : ""; + throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(), + parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation); + } + } + + private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) { + if (parentFragmentEntity.getChildFragments() + .removeIf(fragment -> fragment.getXpath().equals(targetXpath))) { + fragmentRepository.save(parentFragmentEntity); + return true; + } + return false; + } + + + private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) { + final String deleteTargetXpathPrefix = listXpath + "["; if (parentFragmentEntity.getChildFragments() - .removeIf(fragment -> fragment.getXpath().startsWith(listNodeXpathPrefix))) { + .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) { fragmentRepository.save(parentFragmentEntity); + return true; } + return false; + } + + private static void deleteListElements( + final Collection fragmentEntities, + final Map existingListElementFragmentEntitiesByXPath) { + fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values()); + } + + private static String getListElementXpathPrefix(final Collection newListElements) { + final String firstChildNodeXpath = newListElements.iterator().next().getXpath(); + return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[") + 1); + } + + private FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity, + final DataNode newListElement, + final FragmentEntity existingListElementEntity) { + if (existingListElementEntity == null) { + return convertToFragmentWithAllDescendants( + parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement); + } + if (newListElement.getChildDataNodes().isEmpty()) { + copyAttributesFromNewListElement(existingListElementEntity, newListElement); + existingListElementEntity.getChildFragments().clear(); + } else { + replaceDataNodeTree(existingListElementEntity, newListElement); + } + return existingListElementEntity; + } + + private static boolean isNewDataNode(final DataNode replacementDataNode, + final Map existingListElementsByXpath) { + return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath()); + } + + 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 static Map extractListElementFragmentEntitiesByXPath( + final Set childEntities, final String listElementXpathPrefix) { + return childEntities.stream() + .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix)) + .collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity)); } private static boolean isRootXpath(final String xpath) {