diff options
author | Ruslan Kashapov <ruslan.kashapov@pantheon.tech> | 2021-05-05 12:06:00 +0300 |
---|---|---|
committer | Ruslan Kashapov <ruslan.kashapov@pantheon.tech> | 2021-05-11 09:39:59 +0300 |
commit | 25e3306737b7284b051dfeaedb39ef83323504d9 (patch) | |
tree | 8f5b64bfb21bf11ebbbca558a23666128dcdc5e8 /cps-ri/src | |
parent | 757b328b542d91a96d2c095744303d40edcb67f9 (diff) |
Create list-node elements (part1): CPS service and persistence layers
+ fix integrity violation exception exposed out of persistence layer
+ refactor CpsDataServiceImplSpec to eliminate repeated code
Issue-ID: CPS-360
Change-Id: Id70341fe54bf3c31af661f6aae04a7a80f4a1e9d
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
Diffstat (limited to 'cps-ri/src')
3 files changed, 75 insertions, 18 deletions
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 343a0886b3..ae399a1381 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 @@ -27,6 +27,7 @@ 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; @@ -73,7 +74,30 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService 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<DataNode> dataNodes) { + final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); + final List<FragmentEntity> newFragmentEntities = + dataNodes.stream().map( + dataNode -> toFragmentEntity(parentFragment.getDataspace(), parentFragment.getAnchor(), dataNode) + ).collect(Collectors.toUnmodifiableList()); + parentFragment.getChildFragments().addAll(newFragmentEntities); + try { + fragmentRepository.save(parentFragment); + } catch (final DataIntegrityViolationException exception) { + final List<String> conflictXpaths = dataNodes.stream() + .map(DataNode::getXpath) + .collect(Collectors.toList()); + throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception); + } } @Override diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index f632e022f0..0f0b1b4302 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -20,6 +20,9 @@ */ package org.onap.cps.spi.impl +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + import com.google.common.collect.ImmutableSet import com.google.gson.Gson import com.google.gson.GsonBuilder @@ -32,14 +35,10 @@ import org.onap.cps.spi.exceptions.DataspaceNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import org.springframework.beans.factory.annotation.Autowired -import org.springframework.dao.DataIntegrityViolationException import org.springframework.test.context.jdbc.Sql import javax.validation.ConstraintViolationException -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS - class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { @Autowired @@ -53,6 +52,7 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-100' static final long UPDATE_DATA_NODE_FRAGMENT_ID = 4202L static final long UPDATE_DATA_NODE_SUB_FRAGMENT_ID = 4203L + static final long LIST_DATA_NODE_PARENT_FRAGMENT_ID = 4206L static final DataNode newDataNode = new DataNodeBuilder().build() static DataNode existingDataNode @@ -145,7 +145,36 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { where: 'the following data is used' scenario | parentXpath | dataNode || expectedException 'parent does not exist' | 'unknown' | newDataNode || DataNodeNotFoundException - 'already existing child' | XPATH_DATA_NODE_WITH_DESCENDANTS | existingChildDataNode || DataIntegrityViolationException + 'already existing child' | XPATH_DATA_NODE_WITH_DESCENDANTS | existingChildDataNode || AlreadyDefinedException + } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Add list-node fragment with multiple elements.'() { + given: 'list node data fragment as a collection of data nodes' + def listNodeXpaths = ['/parent-201/child-204[@key="B"]', '/parent-201/child-204[@key="C"]'] + def listNodeCollection = buildDataNodeCollection(listNodeXpaths) + when: 'list-node elements added to existing parent node' + objectUnderTest.addListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listNodeCollection) + then: 'new entries successfully persisted, parent node now contains 5 children (2 new + 3 existing before)' + def parentFragment = fragmentRepository.getOne(LIST_DATA_NODE_PARENT_FRAGMENT_ID) + def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() } + assert allChildXpaths.size() == 5 + assert allChildXpaths.containsAll(listNodeXpaths) + } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Add list-node fragment error scenario: #scenario.'() { + given: 'list node data fragment as a collection of data nodes' + def listNodeCollection = buildDataNodeCollection(listNodeXpaths) + when: 'list-node elements added to existing parent node' + objectUnderTest.addListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listNodeCollection) + then: 'a #expectedException is thrown' + thrown(expectedException) + where: 'following parameters were used' + scenario | parentNodeXpath | listNodeXpaths || expectedException + 'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException + 'already existing fragment' | '/parent-201' | ['/parent-201/child-204[@key="A"]'] || AlreadyDefinedException + } static def createDataNodeTree(String... xpaths) { @@ -175,10 +204,10 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { assert result.getChildDataNodes().size() == 0 assertLeavesMaps(result.getLeaves(), expectedLeavesByXpathMap[XPATH_DATA_NODE_WITH_LEAVES]) where: 'the following data is used' - scenario | inputXPath - 'some xpath' |'/parent-100' - 'root xpath' |'/' - 'empty xpath' |'' + scenario | inputXPath + 'some xpath' | '/parent-100' + 'root xpath' | '/' + 'empty xpath' | '' } @Sql([CLEAR_DATA, SET_DATA]) @@ -196,10 +225,10 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { mappedResult.forEach( (xPath, dataNode) -> assertLeavesMaps(dataNode.getLeaves(), expectedLeavesByXpathMap[xPath])) where: 'the following data is used' - scenario | inputXPath - 'some xpath' |'/parent-100' - 'root xpath' |'/' - 'empty xpath' |'' + scenario | inputXPath + 'some xpath' | '/parent-100' + 'root xpath' | '/' + 'empty xpath' | '' } @Sql([CLEAR_DATA, SET_DATA]) @@ -299,6 +328,10 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { 'non-existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'NON-EXISTING XPATH' || DataNodeNotFoundException } + static Collection<DataNode> buildDataNodeCollection(xpaths) { + return xpaths.collect { new DataNodeBuilder().withXpath(it).build() } + } + static DataNode buildDataNode(xpath, leaves, childDataNodes) { return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build() } @@ -323,7 +356,7 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase { def static treeToFlatMapByXpath(Map<String, DataNode> flatMap, DataNode dataNodeTree) { flatMap.put(dataNodeTree.getXpath(), dataNodeTree) dataNodeTree.getChildDataNodes() - .forEach(childDataNode -> treeToFlatMapByXpath(flatMap, childDataNode)) + .forEach(childDataNode -> treeToFlatMapByXpath(flatMap, childDataNode)) return flatMap } diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql index 3e2ae8157e..1897185fa0 100755 --- a/cps-ri/src/test/resources/data/fragment.sql +++ b/cps-ri/src/test/resources/data/fragment.sql @@ -29,6 +29,6 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) (4204, 1001, 3003, 4201, '/parent-200/child-202', '{"common-leaf-name": "common-leaf value", "common-leaf-name-int" : 5}'), (4205, 1001, 3003, 4204, '/parent-200/child-202/grand-child-202', '{"common-leaf-name": "common-leaf value", "common-leaf-name-int" : 5}'), (4206, 1001, 3003, null, '/parent-201', '{"leaf-value": "original"}'), - (4207, 1001, 3003, 4206, '/parent-201/child-202', '{"common-leaf-name": "common-leaf other value", "common-leaf-name-int" : 5}'), - (4208, 1001, 3003, 4206, '/parent-201/child-203[@key1="A" and @key2=1]', '{"key1": "A", "key2" : 1, "other-leaf" : "leaf value"}'), - (4209, 1001, 3003, 4206, '/parent-201/child-203[@key1="A" and @key2=2]', '{"key1": "A", "key2" : 2, "other-leaf" : "other value"}');
\ No newline at end of file + (4207, 1001, 3003, 4206, '/parent-201/child-203', '{}'), + (4208, 1001, 3003, 4206, '/parent-201/child-204[@key="A"]', '{"key": "A"}'), + (4209, 1001, 3003, 4206, '/parent-201/child-204[@key="X"]', '{"key": "X"}');
\ No newline at end of file |