aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToineSiebelink <toine.siebelink@est.tech>2022-10-27 17:29:04 +0100
committerToineSiebelink <toine.siebelink@est.tech>2022-11-02 09:00:41 +0000
commit14e5bf958bb2b114e3e7d8876bd6f031900c79ec (patch)
treea72bcfee795ab6e27938e5e265327d6aace804e3
parenta096a7faa35b345c765102201a5a09cc03ef541a (diff)
Ensure prefix is correct module prefix
- Moved Prefix logic from RI to Service layer - Prefix is no PREFIX propety, not the moduel name! == RI (DB Layer) Changes - Removed prefix logic incl hazelcast - Added new basic ri-test for getDataNode and assert prefix is null - Updated exsiting ri-test to us getDataNode - Updated existing ri-test to only use " where really needed == CPS Service Layer Changes - Introduced PrefixResolver with clear and limited responsibility - Use PrefixResolver where needed - Cache prefix map per anchor, use cached entry when available - Disabled SONAR on new Regex == REST Layer - Use PrefixResolver where needed Issue-ID: CPS-1353 Signed-off-by: ToineSiebelink <toine.siebelink@est.tech> Change-Id: Ie16f0e1ee1c280f3eb69c9e64fab69a780fb692a
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java8
-rw-r--r--cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java20
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy4
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy5
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy4
-rw-r--r--cps-ri/pom.xml4
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java59
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy57
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy11
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy10
-rw-r--r--cps-service/pom.xml5
-rw-r--r--cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java (renamed from cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheConfig.java)8
-rw-r--r--cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java (renamed from cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheEntry.java)6
-rw-r--r--cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java14
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java3
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java7
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java133
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangUtils.java4
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy (renamed from cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheConfigSpec.groovy)6
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy (renamed from cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheEntrySpec.groovy)4
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy11
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy3
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy6
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy117
-rw-r--r--cps-service/src/test/resources/test-tree.yang3
-rw-r--r--csit/tests/cps-data/cps-data.robot2
26 files changed, 367 insertions, 147 deletions
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index 8dea2c02c2..fdce9bee6b 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -30,8 +30,10 @@ import org.apache.commons.lang3.StringUtils;
import org.onap.cps.api.CpsDataService;
import org.onap.cps.rest.api.CpsDataApi;
import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
+import org.onap.cps.utils.PrefixResolver;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -48,6 +50,7 @@ public class DataRestController implements CpsDataApi {
private final CpsDataService cpsDataService;
private final JsonObjectMapper jsonObjectMapper;
+ private final PrefixResolver prefixResolver;
@Override
public ResponseEntity<String> createNode(final String dataspaceName, final String anchorName,
@@ -84,9 +87,10 @@ public class DataRestController implements CpsDataApi {
final String xpath, final Boolean includeDescendants) {
final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
- final var dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
+ final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
fetchDescendantsOption);
- return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode), HttpStatus.OK);
+ final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath);
+ return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
}
@Override
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
index 7a96cffff2..577ad9c262 100644
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
@@ -25,7 +25,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.onap.cps.api.CpsQueryService;
import org.onap.cps.rest.api.CpsQueryApi;
@@ -33,6 +32,7 @@ import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
+import org.onap.cps.utils.PrefixResolver;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -45,17 +45,25 @@ public class QueryRestController implements CpsQueryApi {
private final CpsQueryService cpsQueryService;
private final JsonObjectMapper jsonObjectMapper;
+ private final PrefixResolver prefixResolver;
@Override
public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String dataspaceName,
- final String anchorName, @Valid final String cpsPath, @Valid final Boolean includeDescendants) {
+ final String anchorName, final String cpsPath, final Boolean includeDescendants) {
final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
final Collection<DataNode> dataNodes =
cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
- final List<Map<String, Object>> dataNodeList = new ArrayList<>();
- dataNodes.stream()
- .forEach(dataNode -> dataNodeList.add(DataMapUtils.toDataMapWithIdentifier(dataNode)));
- return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataNodeList), HttpStatus.OK);
+ final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+ String prefix = null;
+ for (final DataNode dataNode : dataNodes) {
+ if (prefix == null) {
+ prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
+ }
+ final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+ dataMaps.add(dataMap);
+ }
+
+ return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK);
}
}
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index 75a3fcf008..53da3e6599 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -28,6 +28,7 @@ import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
import org.onap.cps.utils.DateTimeUtility
import org.onap.cps.utils.JsonObjectMapper
+import org.onap.cps.utils.PrefixResolver
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
@@ -55,6 +56,9 @@ class DataRestControllerSpec extends Specification {
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+ @SpringBean
+ PrefixResolver prefixResolver = Mock()
+
@Autowired
MockMvc mvc
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
index 0e55285be6..27ca0cc097 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
@@ -22,6 +22,8 @@
package org.onap.cps.rest.controller
+import org.onap.cps.utils.PrefixResolver
+
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@@ -47,6 +49,9 @@ class QueryRestControllerSpec extends Specification {
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+ @SpringBean
+ PrefixResolver prefixResolver = Mock()
+
@Autowired
MockMvc mvc
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
index d4c68c30a8..e9d0e3e12a 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
@@ -40,6 +40,7 @@ import org.onap.cps.spi.exceptions.NotFoundInDataspaceException
import org.onap.cps.spi.exceptions.SchemaSetInUseException
import org.onap.cps.spi.exceptions.DataspaceInUseException
import org.onap.cps.utils.JsonObjectMapper
+import org.onap.cps.utils.PrefixResolver
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
@@ -77,6 +78,9 @@ class CpsRestExceptionHandlerSpec extends Specification {
@SpringBean
CpsRestInputMapper cpsRestInputMapper = Stub()
+ @SpringBean
+ PrefixResolver prefixResolver = Mock()
+
@Autowired
MockMvc mvc
diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml
index a193baf9fb..824a8d9710 100644
--- a/cps-ri/pom.xml
+++ b/cps-ri/pom.xml
@@ -88,10 +88,6 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
- <dependency>
- <groupId>com.hazelcast</groupId>
- <artifactId>hazelcast-spring</artifactId>
- </dependency>
<!-- T E S T D E P E N D E N C I E S -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
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 2a4a192260..5fe646ff82 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
@@ -24,7 +24,6 @@ package org.onap.cps.spi.impl;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
-import com.hazelcast.map.IMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -33,7 +32,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -46,15 +44,11 @@ import org.onap.cps.cpspath.parser.CpsPathUtil;
import org.onap.cps.cpspath.parser.PathParsingException;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.cache.AnchorDataCacheConfig;
-import org.onap.cps.spi.cache.AnchorDataCacheEntry;
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.entities.FragmentEntityArranger;
import org.onap.cps.spi.entities.FragmentExtract;
-import org.onap.cps.spi.entities.SchemaSetEntity;
-import org.onap.cps.spi.entities.YangResourceEntity;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
import org.onap.cps.spi.exceptions.ConcurrencyException;
@@ -68,8 +62,6 @@ import org.onap.cps.spi.repository.DataspaceRepository;
import org.onap.cps.spi.repository.FragmentRepository;
import org.onap.cps.spi.utils.SessionManager;
import org.onap.cps.utils.JsonObjectMapper;
-import org.onap.cps.yang.YangTextSchemaSourceSetBuilder;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
@@ -83,7 +75,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
private final FragmentRepository fragmentRepository;
private final JsonObjectMapper jsonObjectMapper;
private final SessionManager sessionManager;
- private final IMap<String, AnchorDataCacheEntry> anchorDataCache;
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 =
@@ -225,9 +216,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
final FetchDescendantsOption fetchDescendantsOption) {
final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath,
fetchDescendantsOption);
- final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
- dataNode.setModuleNamePrefix(getRootModuleNamePrefix(fragmentEntity.getAnchor()));
- return dataNode;
+ return toDataNode(fragmentEntity, fetchDescendantsOption);
}
private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName,
@@ -285,12 +274,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList()
: fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
}
- return fragmentEntities.stream()
- .map(fragmentEntity -> {
- final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
- dataNode.setModuleNamePrefix(getRootModuleNamePrefix(anchorEntity));
- return dataNode;
- }).collect(Collectors.toUnmodifiableList());
+ return fragmentEntities.stream().map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption))
+ .collect(Collectors.toUnmodifiableList());
}
@Override
@@ -337,44 +322,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
.withChildDataNodes(childDataNodes).build();
}
- private String getRootModuleNamePrefix(final AnchorEntity anchorEntity) {
- final String cachedModuleNamePrefix = getModuleNamePrefixFromCache(anchorEntity.getName());
- if (cachedModuleNamePrefix != null) {
- return cachedModuleNamePrefix;
- }
- final String moduleNamePrefix = buildSchemaContextAndRetrieveModulePrefix(anchorEntity);
- cacheModuleNamePrefix(anchorEntity.getName(), moduleNamePrefix);
- return moduleNamePrefix;
- }
-
- private String buildSchemaContextAndRetrieveModulePrefix(final AnchorEntity anchorEntity) {
- final SchemaSetEntity schemaSetEntity = anchorEntity.getSchemaSet();
- final Map<String, String> yangResourceNameToContent =
- schemaSetEntity.getYangResources().stream().collect(
- Collectors.toMap(YangResourceEntity::getFileName, YangResourceEntity::getContent));
- final SchemaContext schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent)
- .getSchemaContext();
- return schemaContext.getModules().iterator().next().getName();
- }
-
- private void cacheModuleNamePrefix(final String anchorName, final String moduleNamePrefix) {
- final AnchorDataCacheEntry anchorDataCacheEntry = new AnchorDataCacheEntry();
- anchorDataCacheEntry.setProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME, moduleNamePrefix);
- if (anchorDataCache.putIfAbsent(anchorName, anchorDataCacheEntry,
- AnchorDataCacheConfig.ANCHOR_DATA_CACHE_TTL_SECS, TimeUnit.SECONDS) == null) {
- log.debug("Module name prefix for an anchor {} is cached", anchorName);
- }
- }
-
- private String getModuleNamePrefixFromCache(final String anchorName) {
- if (anchorDataCache.containsKey(anchorName)) {
- final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchorName);
- return anchorDataCacheEntry.hasProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME)
- ? anchorDataCacheEntry.getProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME).toString() : null;
- }
- return null;
- }
-
private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
final FetchDescendantsOption fetchDescendantsOption) {
if (fetchDescendantsOption.hasNext()) {
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index 412c5aa7b9..a5e17cfefd 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -81,28 +81,41 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'StoreDataNode with descendants.'() {
+ def 'Get existing datanode with descendants.'() {
+ when: 'the node is retrieved by its xpath'
+ def dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, '/parent-1', INCLUDE_ALL_DESCENDANTS)
+ then: 'the path and prefix are populated correctly'
+ assert dataNode.xpath == '/parent-1'
+ and: 'dataNode has no prefix (to be addressed by CPS-1301'
+ assert dataNode.moduleNamePrefix == null
+ and: 'the child node has the correct path'
+ assert dataNode.childDataNodes[0].xpath == '/parent-1/child-1'
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Storing and Retrieving a new DataNode 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"
+ 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))
then: 'it can be retrieved by its xpath'
- def parentFragment = getFragmentByXpath(DATASPACE_NAME, ANCHOR_NAME1, parentXpath)
- and: 'it contains the children'
- parentFragment.childFragments.size() == 1
- def childFragment = parentFragment.childFragments[0]
- childFragment.xpath == childXpath
- and: "and its children's children"
- childFragment.childFragments.size() == 1
- def grandchildFragment = childFragment.childFragments[0]
- grandchildFragment.xpath == grandChildXpath
+ def dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, parentXpath, INCLUDE_ALL_DESCENDANTS)
+ assert dataNode.xpath == parentXpath
+ and: 'it has the correct child'
+ assert dataNode.childDataNodes.size() == 1
+ def childDataNode = dataNode.childDataNodes[0]
+ assert childDataNode.xpath == childXpath
+ and: 'and its grandchild'
+ assert childDataNode.childDataNodes.size() == 1
+ def grandChildDataNode = childDataNode.childDataNodes[0]
+ assert grandChildDataNode.xpath == grandChildXpath
}
@Sql([CLEAR_DATA, SET_DATA])
def 'Store data node for multiple anchors using the same schema.'() {
- def xpath = "/parent-new"
+ def xpath = '/parent-new'
given: 'a fragment is stored for an anchor'
objectUnderTest.storeDataNode(DATASPACE_NAME, ANCHOR_NAME1, createDataNodeTree(xpath))
when: 'another fragment is stored for an other anchor, using the same schema set'
@@ -282,7 +295,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def 'Update data node leaves.'() {
when: 'update is performed for leaves'
objectUnderTest.updateDataLeaves(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
- "/parent-200/child-201", ['leaf-value': 'new'])
+ '/parent-200/child-201', ['leaf-value': 'new'])
then: 'leaves are updated for selected data node'
def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
@@ -311,7 +324,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
@Sql([CLEAR_DATA, SET_DATA])
def 'Update data node and descendants by removing descendants.'() {
given: 'data node object with leaves updated, no children'
- def submittedDataNode = buildDataNode("/parent-200/child-201", ['leaf-value': 'new'], [])
+ def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [])
when: 'update data nodes and descendants is performed'
objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
@@ -328,8 +341,8 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
@Sql([CLEAR_DATA, SET_DATA])
def 'Update data node and descendants with new descendants'() {
given: 'data node object with leaves updated, having child with old content'
- def submittedDataNode = buildDataNode("/parent-200/child-201", ['leaf-value': 'new'], [
- buildDataNode("/parent-200/child-201/grand-child", ['leaf-value': 'original'], [])
+ def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
+ buildDataNode('/parent-200/child-201/grand-child', ['leaf-value': 'original'], [])
])
when: 'update is performed including descendants'
objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
@@ -348,8 +361,8 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
@Sql([CLEAR_DATA, SET_DATA])
def 'Update data node and descendants with same descendants but changed leaf value.'() {
given: 'data node object with leaves updated, having child with old content'
- def submittedDataNode = buildDataNode("/parent-200/child-201", ['leaf-value': 'new'], [
- buildDataNode("/parent-200/child-201/grand-child", ['leaf-value': 'new'], [])
+ def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
+ buildDataNode('/parent-200/child-201/grand-child', ['leaf-value': 'new'], [])
])
when: 'update is performed including descendants'
objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
@@ -368,8 +381,8 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
@Sql([CLEAR_DATA, SET_DATA])
def 'Update data node and descendants with different descendants xpath'() {
given: 'data node object with leaves updated, having child with old content'
- def submittedDataNode = buildDataNode("/parent-200/child-201", ['leaf-value': 'new'], [
- buildDataNode("/parent-200/child-201/grand-child-new", ['leaf-value': 'new'], [])
+ def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [
+ buildDataNode('/parent-200/child-201/grand-child-new', ['leaf-value': 'new'], [])
])
when: 'update is performed including descendants'
objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
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 b124925aa8..e69cbee471 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
@@ -22,7 +22,6 @@ package org.onap.cps.spi.impl
import com.fasterxml.jackson.databind.ObjectMapper
import org.hibernate.StaleStateException
import org.onap.cps.spi.FetchDescendantsOption
-import org.onap.cps.spi.cache.AnchorDataCacheEntry
import org.onap.cps.spi.entities.AnchorEntity
import org.onap.cps.spi.entities.FragmentEntity
import org.onap.cps.spi.entities.FragmentExtract
@@ -35,9 +34,7 @@ import org.onap.cps.spi.repository.DataspaceRepository
import org.onap.cps.spi.repository.FragmentRepository
import org.onap.cps.spi.utils.SessionManager
import org.onap.cps.utils.JsonObjectMapper
-import spock.lang.Shared
import spock.lang.Specification
-import com.hazelcast.map.IMap;
class CpsDataPersistenceServiceSpec extends Specification {
@@ -46,10 +43,8 @@ class CpsDataPersistenceServiceSpec extends Specification {
def mockFragmentRepository = Mock(FragmentRepository)
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockSessionManager = Mock(SessionManager)
- def mockAnchorDataCache = Mock(IMap<String, AnchorDataCacheEntry>)
- def objectUnderTest = new CpsDataPersistenceServiceImpl(
- mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper, mockSessionManager, mockAnchorDataCache)
+ def objectUnderTest = new CpsDataPersistenceServiceImpl(mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper, mockSessionManager)
def 'Handling of StaleStateException (caused by concurrent updates) during update data node and descendants.'() {
given: 'the fragment repository returns a fragment entity'
@@ -191,10 +186,6 @@ class CpsDataPersistenceServiceSpec extends Specification {
def mockFragmentWithJson(json) {
def anchorName = 'some anchor'
- def anchorDataCacheEntry = new AnchorDataCacheEntry()
- anchorDataCacheEntry.setProperty(objectUnderTest.TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME, 'some prefix')
- mockAnchorDataCache.containsKey(anchorName) >> true
- mockAnchorDataCache.get(anchorName) >> anchorDataCacheEntry
def mockAnchor = Mock(AnchorEntity)
mockAnchor.getId() >> 123
mockAnchor.getName() >> anchorName
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 1fbff654f8..d6f10d809d 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
@@ -23,11 +23,7 @@
package org.onap.cps.spi.impl
import com.fasterxml.jackson.databind.ObjectMapper
-import com.hazelcast.config.Config
-import com.hazelcast.instance.impl.HazelcastInstanceFactory
-import com.hazelcast.map.IMap
import org.onap.cps.DatabaseTestContainer
-import org.onap.cps.spi.cache.AnchorDataCacheEntry
import org.onap.cps.spi.repository.AnchorRepository
import org.onap.cps.spi.repository.DataspaceRepository
import org.onap.cps.spi.repository.FragmentRepository
@@ -62,12 +58,6 @@ class CpsPersistenceSpecBase extends Specification {
@SpringBean
JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
- // Instantiate Hazelcast with different name for testing purposes!
- @SpringBean
- IMap<String, AnchorDataCacheEntry> anchorDataCache = HazelcastInstanceFactory
- .getOrCreateHazelcastInstance(new Config('hazelcastTestInstance'))
- .getMap('testAnchorDataCacheMap')
-
static final String CLEAR_DATA = '/data/clear-all.sql'
static final String DATASPACE_NAME = 'DATASPACE-001'
diff --git a/cps-service/pom.xml b/cps-service/pom.xml
index f47a963154..4a98cc27d8 100644
--- a/cps-service/pom.xml
+++ b/cps-service/pom.xml
@@ -110,6 +110,11 @@
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
</dependency>
+ <dependency>
+ <!-- Hazelcast provide Distributed Caches -->
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast-spring</artifactId>
+ </dependency>
<!-- T E S T D E P E N D E N C I E S -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java
index f956120e4a..54d6ff3953 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheConfig.java
+++ b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java
@@ -18,7 +18,7 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.spi.cache;
+package org.onap.cps.cache;
import com.hazelcast.config.Config;
import com.hazelcast.config.MapConfig;
@@ -26,7 +26,6 @@ import com.hazelcast.config.NamedConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
-import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -36,7 +35,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class AnchorDataCacheConfig {
- public static final long ANCHOR_DATA_CACHE_TTL_SECS = TimeUnit.HOURS.toSeconds(1);
private static final MapConfig anchorDataCacheMapConfig = createMapConfig("anchorDataCacheMapConfig");
/**
@@ -46,7 +44,7 @@ public class AnchorDataCacheConfig {
*/
@Bean
public IMap<String, AnchorDataCacheEntry> anchorDataCache() {
- return createHazelcastInstance("hazelCastInstanceCpsRi", anchorDataCacheMapConfig)
+ return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig)
.getMap("anchorDataCache");
}
@@ -58,7 +56,7 @@ public class AnchorDataCacheConfig {
private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) {
final Config config = new Config(instanceName);
config.addMapConfig((MapConfig) namedConfig);
- config.setClusterName("cps-ri-caches");
+ config.setClusterName("cps-service-caches");
return config;
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheEntry.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java
index 98f6ec3f13..41adbdd5dc 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheEntry.java
+++ b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java
@@ -18,7 +18,7 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.spi.cache;
+package org.onap.cps.cache;
import java.io.Serializable;
import java.util.HashMap;
@@ -28,13 +28,13 @@ public class AnchorDataCacheEntry implements Serializable {
private static final long serialVersionUID = 2111243947810370698L;
- private Map<String, Object> properties = new HashMap<>();
+ private Map<String, Serializable> properties = new HashMap<>();
public Object getProperty(final String propertyName) {
return properties.get(propertyName);
}
- public void setProperty(final String propertyName, final Object value) {
+ public void setProperty(final String propertyName, final Serializable value) {
properties.put(propertyName, value);
}
diff --git a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
index 1013c13b76..f0cdaee8d6 100644
--- a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
+++ b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (c) 2021-2022 Bell Canada.
+ * Modifications Copyright (c) 2022 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +35,7 @@ import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.Anchor;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.utils.DataMapUtils;
+import org.onap.cps.utils.PrefixResolver;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@@ -60,6 +62,9 @@ public class CpsDataUpdatedEventFactory {
@Lazy
private final CpsDataService cpsDataService;
+ @Lazy
+ private final PrefixResolver prefixResolver;
+
/**
* Generates CPS Data Updated event. If observedTimestamp is not provided, then current timestamp is used.
*
@@ -87,9 +92,9 @@ public class CpsDataUpdatedEventFactory {
return cpsDataUpdatedEvent;
}
- private Data createData(final DataNode dataNode) {
- final var data = new Data();
- DataMapUtils.toDataMapWithIdentifier(dataNode).forEach(data::setAdditionalProperty);
+ private Data createData(final DataNode dataNode, final String prefix) {
+ final Data data = new Data();
+ DataMapUtils.toDataMapWithIdentifier(dataNode, prefix).forEach(data::setAdditionalProperty);
return data;
}
@@ -103,7 +108,8 @@ public class CpsDataUpdatedEventFactory {
content.withObservedTimestamp(
DATE_TIME_FORMATTER.format(observedTimestamp == null ? OffsetDateTime.now() : observedTimestamp));
if (dataNode != null) {
- content.withData(createData(dataNode));
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ content.withData(createData(dataNode, prefix));
}
return content;
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
index 19d5adef02..8170db3dad 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
@@ -38,14 +38,13 @@ public class DataNode implements Serializable {
private static final long serialVersionUID = 1482619410918597467L;
- DataNode() { }
+ DataNode() {}
private String dataspace;
private String schemaSetName;
private String anchorName;
private ModuleReference moduleReference;
private String xpath;
- @Setter(AccessLevel.PUBLIC)
private String moduleNamePrefix;
private Map<String, Object> leaves = Collections.emptyMap();
private Collection<String> xpathsChildren;
diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
index 4413c6b08b..14641e05e6 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
@@ -43,10 +43,9 @@ public class DataMapUtils {
* @param dataNode data node object
* @return a map representing same data with the root node identifier
*/
- public static Map<String, Object> toDataMapWithIdentifier(final DataNode dataNode) {
- return ImmutableMap.<String, Object>builder()
- .put(getNodeIdentifierWithPrefix(dataNode.getXpath(), dataNode.getModuleNamePrefix()), toDataMap(dataNode))
- .build();
+ public static Map<String, Object> toDataMapWithIdentifier(final DataNode dataNode, final String prefix) {
+ final String nodeIdentifierWithPrefix = getNodeIdentifierWithPrefix(dataNode.getXpath(), prefix);
+ return ImmutableMap.<String, Object>builder().put(nodeIdentifierWithPrefix, toDataMap(dataNode)).build();
}
/**
diff --git a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
new file mode 100644
index 0000000000..58b239c34c
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
@@ -0,0 +1,133 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * 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.utils;
+
+import com.hazelcast.map.IMap;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsAdminService;
+import org.onap.cps.api.impl.YangTextSchemaSourceSetCache;
+import org.onap.cps.cache.AnchorDataCacheEntry;
+import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.yang.YangTextSchemaSourceSet;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class PrefixResolver {
+ private static final long ANCHOR_DATA_CACHE_TTL_SECS = TimeUnit.HOURS.toSeconds(1);
+
+ private static final String CACHE_ENTRY_PROPERTY_NAME = "prefixPerContainerName";
+
+ private final CpsAdminService cpsAdminService;
+
+ private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache;
+
+ private final IMap<String, AnchorDataCacheEntry> anchorDataCache;
+
+ private static final Pattern TOP_LEVEL_NODE_NAME_FINDER
+ = Pattern.compile("\\/([\\w-]*)(\\[@(?!.*\\[).*?])?(\\/.*)?"); //NOSONAR
+
+ /**
+ * Get the module prefix for the given xpath for a dataspace and anchor name.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor the xpath belongs to
+ * @param xpath the xpath to prefix a prefix for
+ * @return the prefix of the module the top level element of given xpath
+ */
+ public String getPrefix(final String dataspaceName, final String anchorName, final String xpath) {
+ final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
+ return getPrefix(anchor, xpath);
+ }
+
+ /**
+ * Get the module prefix for the given xpath under the given anchor.
+ *
+ * @param anchor the anchor the xpath belong to
+ * @param xpath the xpath to prefix a prefix for
+ * @return the prefix of the module the top level element of given xpath
+ */
+ public String getPrefix(final Anchor anchor, final String xpath) {
+ final Map<String, String> prefixPerContainerName = getPrefixPerContainerName(anchor);
+ return getPrefixForTopContainer(prefixPerContainerName, xpath);
+ }
+
+ private Map<String, String> getPrefixPerContainerName(final Anchor anchor) {
+ if (anchorDataCache.containsKey(anchor.getName())) {
+ final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchor.getName());
+ if (anchorDataCacheEntry.hasProperty(CACHE_ENTRY_PROPERTY_NAME)) {
+ return (Map) anchorDataCacheEntry.getProperty(CACHE_ENTRY_PROPERTY_NAME);
+ }
+ }
+ return createAndCachePrefixPerContainerNameMap(anchor);
+ }
+
+ private String getPrefixForTopContainer(final Map<String, String> prefixPerContainerName,
+ final String xpath) {
+ final Matcher matcher = TOP_LEVEL_NODE_NAME_FINDER.matcher(xpath);
+ if (matcher.matches()) {
+ final String topLevelContainerName = matcher.group(1);
+ if (prefixPerContainerName.containsKey(topLevelContainerName)) {
+ return prefixPerContainerName.get(topLevelContainerName);
+ }
+ }
+ return "";
+ }
+
+ private Map<String, String> createAndCachePrefixPerContainerNameMap(final Anchor anchor) {
+ final YangTextSchemaSourceSet yangTextSchemaSourceSet =
+ yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), anchor.getSchemaSetName());
+ final SchemaContext schemaContext = yangTextSchemaSourceSet.getSchemaContext();
+ final Map<QNameModule, String> prefixPerQNameModule = new HashMap<>(schemaContext.getModules().size());
+ for (final Module module : schemaContext.getModules()) {
+ prefixPerQNameModule.put(module.getQNameModule(), module.getPrefix());
+ }
+ final HashMap<String, String> prefixPerContainerName = new HashMap<>();
+ for (final DataSchemaNode dataSchemaNode : schemaContext.getChildNodes()) {
+ if (dataSchemaNode instanceof DataNodeContainer) {
+ final String containerName = dataSchemaNode.getQName().getLocalName();
+ final String prefix = prefixPerQNameModule.get(dataSchemaNode.getQName().getModule());
+ prefixPerContainerName.put(containerName, prefix);
+ }
+ }
+ cachePrefixPerContainerNameMap(anchor.getName(), prefixPerContainerName);
+ return prefixPerContainerName;
+ }
+
+ private void cachePrefixPerContainerNameMap(final String anchorName,
+ final Serializable prefixPerContainerName) {
+ final AnchorDataCacheEntry anchorDataCacheEntry = new AnchorDataCacheEntry();
+ anchorDataCacheEntry.setProperty(CACHE_ENTRY_PROPERTY_NAME, prefixPerContainerName);
+ anchorDataCache.put(anchorName, anchorDataCacheEntry, ANCHOR_DATA_CACHE_TTL_SECS, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
index 5e1b486aa0..8fcdc4ebdb 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2020-2021 Nordix Foundation
+ * Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* ================================================================================
@@ -53,7 +53,6 @@ public class YangUtils {
private static final String XPATH_DELIMITER_REGEX = "\\/";
private static final String XPATH_NODE_KEY_ATTRIBUTES_REGEX = "\\[.*?\\]";
- //Might cause an issue with [] inside [] in key-values
/**
* Parses jsonData into NormalizedNode according to given schema context.
@@ -125,6 +124,7 @@ public class YangUtils {
return xpathBuilder.toString();
}
+
private static String getKeyAttributesStatement(
final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) {
final List<String> keyAttributes = nodeIdentifier.entrySet().stream().map(
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy
index a77db1be88..839444b680 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheConfigSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy
@@ -18,7 +18,7 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.spi.cache
+package org.onap.cps.cache
import com.hazelcast.core.Hazelcast
import com.hazelcast.map.IMap
import org.springframework.beans.factory.annotation.Autowired
@@ -39,12 +39,12 @@ class AnchorDataCacheConfigSpec extends Specification {
and: 'there is at least 1 instance'
assert Hazelcast.allHazelcastInstances.size() > 0
and: 'anchorDataCache is present'
- assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCpsRi')
+ assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCpsCore')
}
def 'Verify configs for Distributed Caches'(){
given: 'the Anchor Data Cache config'
- def anchorDataCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsRi').config.mapConfigs.get('anchorDataCacheMapConfig')
+ def anchorDataCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config.mapConfigs.get('anchorDataCacheMapConfig')
expect: 'system created instance with correct config'
assert anchorDataCacheConfig.backupCount == 3
assert anchorDataCacheConfig.asyncBackupCount == 3
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheEntrySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy
index 103631ec2f..f38b45d17c 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheEntrySpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy
@@ -18,9 +18,9 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.spi.cache
+package org.onap.cps.cache
+
-import org.onap.cps.spi.cache.AnchorDataCacheEntry
import spock.lang.Specification
class AnchorDataCacheEntrySpec extends Specification {
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
index 682197d517..6f9a148eb2 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
@@ -1,6 +1,7 @@
/*
- * ============LICENSE_START=======================================================
- * Copyright (c) 2021-2022 Bell Canada.
+ * ============LICENSE_START=======================================================
+ * Copyright (c) 2021-2022 Bell Canada.
+ * Modifications Copyright (c) 2022 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +24,7 @@ package org.onap.cps.notification
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import org.onap.cps.utils.DateTimeUtility
-import org.onap.cps.api.CpsAdminService
+import org.onap.cps.utils.PrefixResolver
import org.onap.cps.api.CpsDataService
import org.onap.cps.event.model.Content
import org.onap.cps.event.model.Data
@@ -37,7 +38,9 @@ class CpsDataUpdateEventFactorySpec extends Specification {
def mockCpsDataService = Mock(CpsDataService)
- def objectUnderTest = new CpsDataUpdatedEventFactory(mockCpsDataService)
+ def mockPrefixResolver = Mock(PrefixResolver)
+
+ def objectUnderTest = new CpsDataUpdatedEventFactory(mockCpsDataService, mockPrefixResolver)
def dateTimeFormat = 'yyyy-MM-dd\'T\'HH:mm:ss.SSSZ'
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
index 16d4efc273..fcfb4826d9 100644
--- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
@@ -176,12 +176,11 @@ class DataNodeBuilderSpec extends Specification {
def 'Use of adding the module name prefix attribute of data node.'() {
when: 'data node is built with a prefix'
def testDataNode = new DataNodeBuilder()
- .withModuleNamePrefix('sampleModuleNamePrefix')
.withXpath(xPath)
.withLeaves(sampleLeaves)
.build()
then: 'the result when node request is a #scenario includes the correct prefix'
- def result = new DataMapUtils().toDataMapWithIdentifier(testDataNode)
+ def result = new DataMapUtils().toDataMapWithIdentifier(testDataNode, 'sampleModuleNamePrefix')
result.toString() == expectedResult
where: 'the following parameters are used'
scenario | xPath | sampleLeaves | expectedResult
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy
index 7f2c638ff5..84dddeb60e 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy
@@ -65,11 +65,7 @@ class DataMapUtilsSpec extends Specification {
def 'Data node structure conversion to map with root node identifier.'() {
when: 'data node structure is converted to a map with root node identifier'
- def result = DataMapUtils.toDataMapWithIdentifier(dataNode)
-
- then: 'root node identifier is not null'
- result.parent != null
-
+ def result = DataMapUtils.toDataMapWithIdentifier(dataNode,dataNode.moduleNamePrefix)
then: 'root node leaves are populated under its node identifier'
def parentNode = result.parent
parentNode.parentLeaf == 'parentLeafValue'
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
new file mode 100644
index 0000000000..4c1b891168
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
@@ -0,0 +1,117 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2022 Nordix Foundation
+ * Modifications Copyright (C) 2021 Pantheon.tech
+ * Modifications Copyright (C) 2021-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.
+ * 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.utils
+
+import com.hazelcast.map.IMap
+import org.onap.cps.TestUtils
+import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.impl.YangTextSchemaSourceSetCache
+import org.onap.cps.cache.AnchorDataCacheEntry
+import org.onap.cps.spi.model.Anchor
+import org.onap.cps.yang.YangTextSchemaSourceSet
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import spock.lang.Specification
+
+class PrefixResolverSpec extends Specification {
+
+ def mockCpsAdminService = Mock(CpsAdminService)
+
+ def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
+
+ def mockAnchorDataCache = Mock(IMap<String, AnchorDataCacheEntry>)
+
+ def objectUnderTest = new PrefixResolver(mockCpsAdminService, mockYangTextSchemaSourceSetCache, mockAnchorDataCache)
+
+ def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
+
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+
+ def setup() {
+ given: 'an anchor for the test-tree model'
+ def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor')
+ and: 'the system can get this anchor'
+ mockCpsAdminService.getAnchor('testDataspace', 'testAnchor') >> anchor
+ and: 'the schema source cache contains the schema context for the test-tree module'
+ mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+ }
+
+ def 'get xpath prefix using node schema context'() {
+ when: 'the prefix of the yang module is retrieved'
+ def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', xpath)
+ then: 'the expected prefix is returned'
+ result == expectedPrefix
+ and: 'the cache is updated for the given anchor with a map of prefixes per top level container (just one one this case)'
+ 1 * mockAnchorDataCache.put('testAnchor',_ , _ ,_) >> { args -> {
+ def prefixPerContainerName = args[1].getProperty("prefixPerContainerName")
+ assert prefixPerContainerName.size() == 1
+ assert prefixPerContainerName.get('test-tree') == 'tree'
+ }
+ }
+ and: 'schema source cache is used (i.e. need to build schema context)'
+ 1 * mockYangTextSchemaSourceSetCache.get(*_) >> mockYangTextSchemaSourceSet
+ where: 'the following scenarios are applied'
+ xpath || expectedPrefix
+ '/test-tree' || 'tree'
+ '/test-tree/with/descendants' || 'tree'
+ '/test-tree[@id=1]' || 'tree'
+ '/test-tree[@id=1]/child' || 'tree'
+ '/not-defined' || ''
+ 'invalid-xpath' || ''
+ }
+
+ def 'get prefix with populated anchor data cache with #scenario cache entry'() {
+ given: 'anchor data cache is populated for the anchor with a prefix for top level container named #cachedTopLevelContainerName'
+ def anchorDataCacheEntry = new AnchorDataCacheEntry()
+ def prefixPerContainerName = [(cachedTopLevelContainerName): 'cachedPrefix']
+ anchorDataCacheEntry.setProperty('prefixPerContainerName',prefixPerContainerName)
+ mockAnchorDataCache.containsKey('testAnchor') >> true
+ mockAnchorDataCache.get('testAnchor') >> anchorDataCacheEntry
+ when: 'the prefix of the yang module is retrieved'
+ def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', '/test-tree')
+ then: 'the expected prefix is returned'
+ result == expectedPrefix
+ and: 'schema source cache is not used (i.e. no need to build schema context)'
+ 0 * mockYangTextSchemaSourceSetCache.get(*_)
+ where: 'the following scenarios are applied'
+ scenario | cachedTopLevelContainerName || expectedPrefix
+ 'matching' | 'test-tree' || 'cachedPrefix'
+ 'non-matching' | 'other' || ''
+ }
+
+ def 'get prefix with other (non relevant) data in anchor data cache'() {
+ given: 'anchor data cache is populated with non relevant other property'
+ def anchorDataCacheEntry = new AnchorDataCacheEntry()
+ anchorDataCacheEntry.setProperty('something else', 'does not matter')
+ mockAnchorDataCache.containsKey('testAnchor') >> true
+ mockAnchorDataCache.get('testAnchor') >> anchorDataCacheEntry
+ when: 'the prefix of the yang module is retrieved'
+ def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', '/test-tree')
+ then: 'the expected prefix is returned'
+ result == 'tree'
+ and: 'schema source cache is used (i.e. need to build schema context)'
+ 1 * mockYangTextSchemaSourceSetCache.get(*_) >> mockYangTextSchemaSourceSet
+ }
+
+}
diff --git a/cps-service/src/test/resources/test-tree.yang b/cps-service/src/test/resources/test-tree.yang
index 63100657ef..a9f7e224b7 100644
--- a/cps-service/src/test/resources/test-tree.yang
+++ b/cps-service/src/test/resources/test-tree.yang
@@ -33,4 +33,7 @@ module test-tree {
}
}
+ leaf topLevelLeafAddedForBranchCoverageInPrefixResolverSpec {
+ type string;
+ }
}
diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot
index 844b5d53f6..2da2b73414 100644
--- a/csit/tests/cps-data/cps-data.robot
+++ b/csit/tests/cps-data/cps-data.robot
@@ -47,7 +47,7 @@ Get Data Node by XPath
${params}= Create Dictionary xpath=/test-tree/branch[@name='Left']/nest
${headers}= Create Dictionary Authorization=${auth}
${response}= Get On Session CPS_URL ${uri} params=${params} headers=${headers} expected_status=200
- ${responseJson}= Set Variable ${response.json()['test-tree:nest']}
+ ${responseJson}= Set Variable ${response.json()['tree:nest']}
Should Be Equal As Strings ${responseJson['name']} Small