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=adb29b6292f7841bb9601d091a68c4ee2e7ebdc4;hb=c6babc01c2bce84bd8cfe358d94513ece2c37046;hp=fd4e768cab379ec955fa290220c3726632c45ce4;hpb=c7b1283b6a13a03cc36af57f6ec550a0b2adcb3c;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 fd4e768ca..adb29b629 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,13 +1,15 @@ /* - * ============LICENSE_START======================================================= + * ============LICENSE_START======================================================= * Copyright (C) 2021 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2020-2021 Bell Canada. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * 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. @@ -26,55 +28,108 @@ 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.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +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.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.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; + /** + * 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 static final Gson GSON = new GsonBuilder().create(); + private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@\\S+?]){0,1})"; @Override public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath, final DataNode dataNode) { final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentXpath); - final FragmentEntity fragmentEntity = + final var fragmentEntity = toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode); parentFragment.getChildFragments().add(fragmentEntity); - fragmentRepository.save(parentFragment); + try { + fragmentRepository.save(parentFragment); + } catch (final DataIntegrityViolationException exception) { + throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception); + } + } + + @Override + public void addListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final Collection dataNodes) { + final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final List newFragmentEntities = + dataNodes.stream().map( + dataNode -> toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode) + ).collect(Collectors.toUnmodifiableList()); + 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) + .collect(Collectors.toList()); + throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception); + } } @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, + final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + final var fragmentEntity = convertToFragmentWithAllDescendants(dataspaceEntity, anchorEntity, dataNode); - fragmentRepository.save(fragmentEntity); + try { + fragmentRepository.save(fragmentEntity); + } catch (final DataIntegrityViolationException exception) { + throw AlreadyDefinedException.forDataNode(dataNode.getXpath(), anchorName, exception); + } } /** @@ -88,7 +143,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService */ private static FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { - final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); + final var parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); final Builder childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { final FragmentEntity childFragment = @@ -100,6 +155,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() @@ -113,15 +179,58 @@ 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); + final var fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); return toDataNode(fragmentEntity, fetchDescendantsOption); } 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); - return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, xpath); + final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); + if (isRootXpath(xpath)) { + return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); + } else { + return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, + xpath); + } + } + + @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 CpsPathQuery cpsPathQuery; + try { + cpsPathQuery = CpsPathQuery.createFrom(cpsPath); + } catch (final IllegalStateException e) { + throw new CpsPathException(e.getMessage()); + } + List fragmentEntities = + fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); + if (cpsPathQuery.hasAncestorAxis()) { + final Set ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); + fragmentEntities = ancestorXpaths.isEmpty() + ? Collections.emptyList() : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths); + } + return fragmentEntities.stream() + .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption)) + .collect(Collectors.toUnmodifiableList()); + } + + 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*"); + for (final FragmentEntity fragmentEntity : fragmentEntities) { + final var matcher = pattern.matcher(fragmentEntity.getXpath()); + if (matcher.matches()) { + ancestorXpath.add(matcher.group(1)); + } + } + return ancestorXpath; } private static DataNode toDataNode(final FragmentEntity fragmentEntity, @@ -147,28 +256,74 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath, final Map leaves) { - final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath); + final var 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 FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()); - removeExistingDescendants(fragmentEntity); - - fragmentEntity.setAttributes(GSON.toJson(dataNode.getLeaves())); - final Set childFragmentEntities = dataNode.getChildDataNodes().stream().map( - childDataNode -> convertToFragmentWithAllDescendants( - fragmentEntity.getDataspace(), fragmentEntity.getAnchor(), childDataNode) + public void replaceDataNodeTree(final String dataspaceName, final String anchorName, + final DataNode dataNode) { + final var 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); + } + } + + private void replaceDataNodeTree(final FragmentEntity existingFragmentEntity, final DataNode submittedDataNode) { + + existingFragmentEntity.setAttributes(GSON.toJson(submittedDataNode.getLeaves())); + + final Map existingChildrenByXpath = existingFragmentEntity.getChildFragments() + .stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity)); + + final var updatedChildFragments = new HashSet(); + + for (final DataNode submittedChildDataNode : submittedDataNode.getChildDataNodes()) { + final FragmentEntity childFragment; + if (existingChildrenByXpath.containsKey(submittedChildDataNode.getXpath())) { + childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath()); + replaceDataNodeTree(childFragment, submittedChildDataNode); + } else { + childFragment = convertToFragmentWithAllDescendants( + existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), submittedChildDataNode); + } + updatedChildFragments.add(childFragment); + } + existingFragmentEntity.setChildFragments(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()); - fragmentEntity.setChildFragments(childFragmentEntities); + parentEntity.getChildFragments().addAll(childFragmentEntities); + fragmentRepository.save(parentEntity); + } - fragmentRepository.save(fragmentEntity); + private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) { + final String listNodeXpathPrefix = listNodeXpath + "["; + 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); } }