summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java42
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy124
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy6
-rw-r--r--cps-ri/src/test/resources/data/fragment.sql11
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java14
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java25
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java14
7 files changed, 197 insertions, 39 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 c73b65ddd8..d8f3df112f 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,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,15 +20,23 @@
package org.onap.cps.spi.impl;
+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.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
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.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;
@@ -93,8 +102,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
}
private static FragmentEntity toFragmentEntity(final DataspaceEntity dataspaceEntity,
- final AnchorEntity anchorEntity,
- final DataNode dataNode) {
+ final AnchorEntity anchorEntity, final DataNode dataNode) {
return FragmentEntity.builder()
.dataspace(dataspaceEntity)
.anchor(anchorEntity)
@@ -102,4 +110,34 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
.attributes(GSON.toJson(dataNode.getLeaves()))
.build();
}
+
+ @Override
+ public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+ final FragmentEntity fragmentEntity =
+ fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, xpath);
+ return toDataNode(fragmentEntity, fetchDescendantsOption);
+ }
+
+ private static DataNode toDataNode(final FragmentEntity fragmentEntity,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Map<String, Object> leaves = GSON.fromJson(fragmentEntity.getAttributes(), Map.class);
+ final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
+ return new DataNodeBuilder()
+ .withXpath(fragmentEntity.getXpath())
+ .withLeaves(leaves)
+ .withChildDataNodes(childDataNodes).build();
+ }
+
+ private static List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
+ return fragmentEntity.getChildFragments().stream()
+ .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption))
+ .collect(Collectors.toUnmodifiableList());
+ }
+ return Collections.emptyList();
+ }
}
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 03e352a874..e3fa885301 100644
--- 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
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +29,10 @@ 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 spock.lang.Unroll
+
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
@@ -37,49 +42,34 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
static final String SET_DATA = '/data/fragment.sql'
static final long ID_DATA_NODE_WITH_DESCENDANTS = 4001
static final String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1'
+ static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-100'
static final DataNode newDataNode = new DataNodeBuilder().build()
static DataNode existingDataNode
static DataNode existingChildDataNode
+ static Map<String, Map<String, Object>> expectedLeavesByXpathMap = [
+ '/parent-100' : ["x": "y"],
+ '/parent-100/child-001' : ["a": "b", "c": ["d", "e", "f"]],
+ '/parent-100/child-002' : ["g": "h", "i": ["j", "k"]],
+ '/parent-100/child-002/grand-child': ["l": "m", "n": ["o", "p"]]
+ ]
+
static {
existingDataNode = createDataNodeTree(XPATH_DATA_NODE_WITH_DESCENDANTS)
existingChildDataNode = createDataNodeTree('/parent-1/child-1')
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get fragment with descendants.'() {
- /*
- TODO: This test is not really testing the object under test! Needs to be updated as part of CPS-71
- Actually I think this test will become redundant once th store data node tests is asserted using
- a new getByXpath() method in the service (object under test)
- A lot of preloaded dat will become redundant then too
- */
- //
- when: 'a fragment is retrieved from the repository'
- def fragment = fragmentRepository.findById(ID_DATA_NODE_WITH_DESCENDANTS).orElseThrow()
- then: 'it has the correct xpath'
- fragment.xpath == '/parent-1'
- and: 'it contains the children'
- fragment.childFragments.size() == 1
- def childFragment = fragment.childFragments[0]
- childFragment.xpath == '/parent-1/child-1'
- and: "and its children's children"
- childFragment.childFragments.size() == 1
- def grandchildFragment = childFragment.childFragments[0]
- grandchildFragment.xpath == '/parent-1/child-1/grandchild-1'
- }
-
- @Sql([CLEAR_DATA, SET_DATA])
def 'StoreDataNode with descendants.'() {
when: 'a fragment with descendants is stored'
def parentXpath = "/parent-new"
def childXpath = "/parent-new/child-new"
def grandChildXpath = "/parent-new/child-new/grandchild-new"
objectUnderTest.storeDataNode(DATASPACE_NAME, ANCHOR_NAME1,
- createDataNodeTree(parentXpath, childXpath, grandChildXpath))
+ createDataNodeTree(parentXpath, childXpath, grandChildXpath))
then: 'it can be retrieved by its xpath'
- def parentFragment = getFragmentByXpath(parentXpath)
+ def parentFragment = getFragmentByXpath(DATASPACE_NAME, ANCHOR_NAME1, parentXpath)
and: 'it contains the children'
parentFragment.childFragments.size() == 1
def childFragment = parentFragment.childFragments[0]
@@ -91,7 +81,7 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Store datanode error scenario: #scenario.'() {
+ def 'Store datanode error scenario: #scenario.'() {
when: 'attempt to store a data node with #scenario'
objectUnderTest.storeDataNode(dataspaceName, anchorName, dataNode)
then: 'a #expectedException is thrown'
@@ -113,14 +103,14 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
def expectedExistingChildPath = '/parent-1/child-1'
def parentFragment = fragmentRepository.findById(ID_DATA_NODE_WITH_DESCENDANTS).orElseThrow()
parentFragment.getChildFragments().size() == 2
- and : 'it still has the old child'
- parentFragment.getChildFragments().find( {it.xpath == expectedExistingChildPath})
- and : 'it has the new child'
- parentFragment.getChildFragments().find( {it.xpath == newChild.xpath})
+ and: 'it still has the old child'
+ parentFragment.getChildFragments().find({ it.xpath == expectedExistingChildPath })
+ and: 'it has the new child'
+ parentFragment.getChildFragments().find({ it.xpath == newChild.xpath })
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Add child error scenario: #scenario.'() {
+ def 'Add child error scenario: #scenario.'() {
when: 'attempt to add a child data node with #scenario'
objectUnderTest.addChildDataNode(DATASPACE_NAME, ANCHOR_NAME1, parentXpath, dataNode)
then: 'a #expectedException is thrown'
@@ -141,10 +131,74 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
dataNodeBuilder.build()
}
- def getFragmentByXpath = xpath -> {
- //TODO: Remove this method when CPS-71 gets implemented
- fragmentRepository.findAll().stream()
- .filter(fragment -> fragment.getXpath().contains(xpath)).findAny().orElseThrow()
+ def getFragmentByXpath(dataspaceName, anchorName, xpath) {
+ def dataspace = dataspaceRepository.getByName(dataspaceName)
+ def anchor = anchorRepository.getByDataspaceAndName(dataspace, anchorName)
+ return fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspace, anchor, xpath).orElseThrow()
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Get data node by xpath without descendants.'() {
+ when: 'data node is requested'
+ def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
+ XPATH_DATA_NODE_WITH_LEAVES, OMIT_DESCENDANTS)
+ then: 'data node is returned with no descendants'
+ assert result.getXpath() == XPATH_DATA_NODE_WITH_LEAVES
+ and: 'expected leaves'
+ assert result.getChildDataNodes().size() == 0
+ assertLeavesMaps(result.getLeaves(), expectedLeavesByXpathMap[XPATH_DATA_NODE_WITH_LEAVES])
}
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Get data node by xpath with all descendants.'() {
+ when: 'data node is requested with all descendants'
+ def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
+ XPATH_DATA_NODE_WITH_LEAVES, INCLUDE_ALL_DESCENDANTS)
+ def mappedResult = treeToFlatMapByXpath(new HashMap<>(), result)
+ then: 'data node is returned with all the descendants populated'
+ assert mappedResult.size() == 4
+ assert result.getChildDataNodes().size() == 2
+ assert mappedResult.get('/parent-100/child-001').getChildDataNodes().size() == 0
+ assert mappedResult.get('/parent-100/child-002').getChildDataNodes().size() == 1
+ and: 'extracted leaves maps are matching expected'
+ mappedResult.forEach(
+ (xpath, dataNode) ->
+ assertLeavesMaps(dataNode.getLeaves(), expectedLeavesByXpathMap[xpath])
+ )
+ }
+
+ def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) {
+ expectedLeavesMap.forEach((key, value) -> {
+ def actualValue = actualLeavesMap[key]
+ if (value instanceof Collection<?> && actualValue instanceof Collection<?>) {
+ assert value.size() == actualValue.size()
+ assert value.containsAll(actualValue)
+ } else {
+ assert value == actualValue
+ }
+ }
+ )
+ return true
+ }
+
+ def static treeToFlatMapByXpath(Map<String, DataNode> flatMap, DataNode dataNodeTree) {
+ flatMap.put(dataNodeTree.getXpath(), dataNodeTree)
+ dataNodeTree.getChildDataNodes()
+ .forEach(childDataNode -> treeToFlatMapByXpath(flatMap, childDataNode))
+ return flatMap
+ }
+
+ @Unroll
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Get data node error scenario: #scenario.'() {
+ when: 'attempt to get data node with #scenario'
+ objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS)
+ then: 'a #expectedException is thrown'
+ thrown(expectedException)
+ where: 'the following data is used'
+ scenario | dataspaceName | anchorName | xpath || expectedException
+ 'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' | 'not relevant' || DataspaceNotFoundException
+ 'non-existing anchor' | DATASPACE_NAME | 'NO ANCHOR' | 'not relevant' || AnchorNotFoundException
+ 'non-existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'NO XPATH' || DataNodeNotFoundException
+ }
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
index 54807efd2b..c8a8b9bf1e 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
@@ -20,6 +21,7 @@
package org.onap.cps.spi.impl
import org.onap.cps.DatabaseTestContainer
+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.spi.repository.YangResourceRepository
@@ -43,6 +45,9 @@ class CpsPersistenceSpecBase extends Specification {
YangResourceRepository yangResourceRepository
@Autowired
+ AnchorRepository anchorRepository
+
+ @Autowired
FragmentRepository fragmentRepository
static final String CLEAR_DATA = '/data/clear-all.sql'
@@ -52,5 +57,6 @@ class CpsPersistenceSpecBase extends Specification {
static final String SCHEMA_SET_NAME2 = 'SCHEMA-SET-002'
static final String ANCHOR_NAME1 = 'ANCHOR-001'
static final String ANCHOR_NAME2 = 'ANCHOR-002'
+ static final String ANCHOR_FOR_DATA_NODES_WITH_LEAVES = 'ANCHOR-003'
}
diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql
index 05f5bfe455..e652703268 100644
--- a/cps-ri/src/test/resources/data/fragment.sql
+++ b/cps-ri/src/test/resources/data/fragment.sql
@@ -5,7 +5,8 @@ INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
(2001, 'SCHEMA-SET-001', 1001);
INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
- (3001, 'ANCHOR-001', 1001, 2001);
+ (3001, 'ANCHOR-001', 1001, 2001),
+ (3003, 'ANCHOR-003', 1001, 2001);
INSERT INTO FRAGMENT (ID, XPATH, ANCHOR_ID, PARENT_ID, DATASPACE_ID) VALUES
(4001, '/parent-1', 3001, null, 1001),
@@ -13,4 +14,10 @@ INSERT INTO FRAGMENT (ID, XPATH, ANCHOR_ID, PARENT_ID, DATASPACE_ID) VALUES
(4003, '/parent-3', 3001, null, 1001),
(4004, '/parent-1/child-1', 3001, 4001, 1001),
(4005, '/parent-2/child-2', 3001, 4002, 1001),
- (4006, '/parent-1/child-1/grandchild-1', 3001, 4004, 1001); \ No newline at end of file
+ (4006, '/parent-1/child-1/grandchild-1', 3001, 4004, 1001);
+
+INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
+ (4101, 1001, 3003, null, '/parent-100', '{"x": "y"}'),
+ (4102, 1001, 3003, 4101, '/parent-100/child-001', '{"a": "b", "c": ["d", "e", "f"]}'),
+ (4103, 1001, 3003, 4101, '/parent-100/child-002', '{"g": "h", "i": ["j", "k"]}'),
+ (4104, 1001, 3003, 4103, '/parent-100/child-002/grand-child', '{"l": "m", "n": ["o", "p"]}'); \ No newline at end of file
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index d59fa47467..97aecaafd1 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -1,6 +1,7 @@
/*-
* ============LICENSE_START=======================================================
* Copyright (C) 2020 Nordix Foundation. All rights reserved.
+ * Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,4 +51,17 @@ public interface CpsDataPersistenceService {
*/
void addChildDataNode(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentXpath,
@NonNull DataNode dataNode);
+
+ /**
+ * Retrieves datanode by XPath for given dataspace and anchor.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param xpath xpath
+ * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
+ * (recursively) as well
+ * @return data node object
+ */
+ DataNode getDataNode(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String xpath,
+ @NonNull FetchDescendantsOption fetchDescendantsOption);
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
new file mode 100644
index 0000000000..0c994d8d7b
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
@@ -0,0 +1,25 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Pantheon.tech
+ * ================================================================================
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi;
+
+public enum FetchDescendantsOption {
+ OMIT_DESCENDANTS,
+ INCLUDE_ALL_DESCENDANTS
+}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
index d187f62e0f..67e93dd82b 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +43,7 @@ public class DataNodeBuilder {
private NormalizedNode<?, ?> normalizedNodeTree;
private String xpath;
+ private Map<String, Object> leaves = Collections.emptyMap();
private Collection<DataNode> childDataNodes = Collections.emptySet();
@@ -68,6 +70,17 @@ public class DataNodeBuilder {
}
/**
+ * To use attributes for creating {@link DataNode}.
+ *
+ * @param leaves for the data node
+ * @return DataNodeBuilder
+ */
+ public DataNodeBuilder withLeaves(final Map<String, Object> leaves) {
+ this.leaves = leaves;
+ return this;
+ }
+
+ /**
* To specify child nodes needs to be used while creating {@link DataNode}.
*
* @param childDataNodes to be added to the dataNode
@@ -96,6 +109,7 @@ public class DataNodeBuilder {
private DataNode buildFromAttributes() {
final DataNode dataNode = new DataNode();
dataNode.setXpath(xpath);
+ dataNode.setLeaves(leaves);
dataNode.setChildDataNodes(childDataNodes);
return dataNode;
}