aboutsummaryrefslogtreecommitdiffstats
path: root/cps-service/src
diff options
context:
space:
mode:
Diffstat (limited to 'cps-service/src')
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java14
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java4
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java17
-rw-r--r--cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java45
-rw-r--r--cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java44
-rw-r--r--cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java104
-rw-r--r--cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java4
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java25
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/ContentType.java10
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java92
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java87
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangParser.java34
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java57
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy14
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy2
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy80
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy76
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy13
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy (renamed from cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy)77
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy67
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy57
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy33
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy48
-rw-r--r--cps-service/src/test/resources/application.yml10
-rw-r--r--cps-service/src/test/resources/bookstore-categories-data.json49
-rw-r--r--cps-service/src/test/resources/bookstore-categories-data.xml14
-rw-r--r--cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang2
-rw-r--r--cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang2
-rw-r--r--cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang4
-rwxr-xr-xcps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang2
30 files changed, 484 insertions, 603 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
index 68e1880d77..b3eff8eb26 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
@@ -322,4 +322,18 @@ public interface CpsDataService {
Map<String, String> yangResourcesNameToContentMap,
String targetData,
FetchDescendantsOption fetchDescendantsOption);
+
+
+ /**
+ * Validates JSON or XML data by parsing it using the schema associated to an anchor within the given dataspace.
+ * Validation is performed without persisting the data.
+ *
+ * @param dataspaceName the name of the dataspace where the anchor is located.
+ * @param anchorName the name of the anchor used to validate the data.
+ * @param parentNodeXpath the xpath of the parent node where the data is to be validated.
+ * @param nodeData the JSON or XML data to be validated.
+ * @param contentType the content type of the data (e.g., JSON or XML).
+ */
+ void validateData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+ ContentType contentType);
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
index 931209c998..bbfe496d85 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
@@ -178,8 +178,8 @@ public interface CpsModuleService {
* an attribute key-value pair used in the WHERE clause for parent fragments.
* @param childAttributes a map of attributes to filter child fragments. Each entry in this map represents
* an attribute key-value pair used in the WHERE clause for child fragments.
- * @return a collection of {@link ModuleReference} objects that match the given criteria. Each
- * {@code ModuleReference} contains information about a module's name and revision.
+ * @return a collection of {@link ModuleReference} objects that match the given criteria.
+ * Each {@code ModuleReference} contains information about a module's name and revision.
* @implNote The method assumes that both `parentAttributes` and `childAttributes` maps contain at least
* one entry. The first entry from `parentAttributes` is used to filter parent fragments,
* and the first entry from `childAttributes` is used to filter child fragments.
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 951770b053..b1b545be68 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -64,6 +64,7 @@ import org.springframework.stereotype.Service;
public class CpsDataServiceImpl implements CpsDataService {
private static final String ROOT_NODE_XPATH = "/";
+ private static final String PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH = "";
private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
private static final String NO_DATA_NODES = "No data nodes.";
@@ -358,6 +359,14 @@ public class CpsDataServiceImpl implements CpsDataService {
sendDataUpdatedEvent(anchor, listNodeXpath, Operation.DELETE, observedTimestamp);
}
+ @Override
+ public void validateData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final String nodeData, final ContentType contentType) {
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH :
+ CpsPathUtil.getNormalizedXpath(parentNodeXpath);
+ yangParser.validateData(contentType, nodeData, anchor, xpath);
+ }
private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor,
final Collection<DataNode> sourceDataNodes) {
@@ -400,8 +409,7 @@ public class CpsDataServiceImpl implements CpsDataService {
private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) {
final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size());
for (final DataNode dataNode: dataNodes) {
- final String prefix = prefixResolver
- .getPrefix(anchor.getDataspaceName(), anchor.getName(), dataNode.getXpath());
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
prefixToDataNodes.add(prefixToDataNode);
}
@@ -423,7 +431,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final ContentType contentType) {
if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
- final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, "");
+ final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
+ anchor, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
final Collection<DataNode> dataNodes = new DataNodeBuilder()
.withContainerNode(containerNode)
.buildCollection();
@@ -451,7 +460,7 @@ public class CpsDataServiceImpl implements CpsDataService {
if (isRootNodeXpath(xpath)) {
final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
- yangResourcesNameToContentMap, "");
+ yangResourcesNameToContentMap, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
final Collection<DataNode> dataNodes = new DataNodeBuilder()
.withContainerNode(containerNode)
.buildCollection();
diff --git a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java
deleted file mode 100644
index efe19c6cb7..0000000000
--- a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * ============LICENSE_START========================================================
- * Copyright (C) 2022-2023 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.cache;
-
-import com.hazelcast.config.MapConfig;
-import com.hazelcast.map.IMap;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * Core infrastructure of the hazelcast distributed cache for anchor data config use cases.
- */
-@Configuration
-public class AnchorDataCacheConfig extends HazelcastCacheConfig {
-
- private static final MapConfig anchorDataCacheMapConfig = createMapConfig("anchorDataCacheMapConfig");
-
- /**
- * Distributed instance of anchor data cache that contains module prefix by anchor name as properties.
- *
- * @return configured map of anchor data cache
- */
- @Bean
- public IMap<String, AnchorDataCacheEntry> anchorDataCache() {
- return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig).getMap("anchorDataCache");
- }
-}
diff --git a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java
deleted file mode 100644
index 41adbdd5dc..0000000000
--- a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheEntry.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * ============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.cache;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-
-public class AnchorDataCacheEntry implements Serializable {
-
- private static final long serialVersionUID = 2111243947810370698L;
-
- private Map<String, Serializable> properties = new HashMap<>();
-
- public Object getProperty(final String propertyName) {
- return properties.get(propertyName);
- }
-
- public void setProperty(final String propertyName, final Serializable value) {
- properties.put(propertyName, value);
- }
-
- public boolean hasProperty(final String propertyName) {
- return properties.containsKey(propertyName);
- }
-}
diff --git a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java
deleted file mode 100644
index 418de9b17a..0000000000
--- a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * ============LICENSE_START========================================================
- * Copyright (C) 2023-2024 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.cache;
-
-import com.hazelcast.config.Config;
-import com.hazelcast.config.MapConfig;
-import com.hazelcast.config.NamedConfig;
-import com.hazelcast.config.QueueConfig;
-import com.hazelcast.config.RestEndpointGroup;
-import com.hazelcast.config.SetConfig;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-
-/**
- * Core infrastructure of the hazelcast distributed cache.
- */
-@Slf4j
-public class HazelcastCacheConfig {
-
- @Value("${hazelcast.cluster-name}")
- protected String clusterName;
-
- @Value("${hazelcast.mode.kubernetes.enabled}")
- protected boolean cacheKubernetesEnabled;
-
- @Value("${hazelcast.mode.kubernetes.service-name}")
- protected String cacheKubernetesServiceName;
-
- protected HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName,
- final NamedConfig namedConfig) {
- return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig));
- }
-
- private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) {
- final Config config = new Config(instanceName);
- if (namedConfig instanceof MapConfig) {
- config.addMapConfig((MapConfig) namedConfig);
- }
- if (namedConfig instanceof QueueConfig) {
- config.addQueueConfig((QueueConfig) namedConfig);
- }
- if (namedConfig instanceof SetConfig) {
- config.addSetConfig((SetConfig) namedConfig);
- }
-
- config.setClusterName(clusterName);
- config.setClassLoader(org.onap.cps.spi.model.Dataspace.class.getClassLoader());
- exposeClusterInformation(config);
- updateDiscoveryMode(config);
- return config;
- }
-
- protected static MapConfig createMapConfig(final String configName) {
- final MapConfig mapConfig = new MapConfig(configName);
- mapConfig.setBackupCount(1);
- return mapConfig;
- }
-
- protected static QueueConfig createQueueConfig(final String configName) {
- final QueueConfig commonQueueConfig = new QueueConfig(configName);
- commonQueueConfig.setBackupCount(1);
- return commonQueueConfig;
- }
-
- protected static SetConfig createSetConfig(final String configName) {
- final SetConfig commonSetConfig = new SetConfig(configName);
- commonSetConfig.setBackupCount(1);
- return commonSetConfig;
- }
-
- protected void updateDiscoveryMode(final Config config) {
- if (cacheKubernetesEnabled) {
- log.info("Enabling kubernetes mode with service-name : {}", cacheKubernetesServiceName);
- config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true)
- .setProperty("service-name", cacheKubernetesServiceName);
- }
- }
-
- protected void exposeClusterInformation(final Config config) {
- config.getNetworkConfig().getRestApiConfig().setEnabled(true)
- .enableGroups(RestEndpointGroup.HEALTH_CHECK, RestEndpointGroup.CLUSTER_READ);
- }
-
-}
diff --git a/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java b/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java
index 8023fbfb25..46384b5933 100644
--- a/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java
+++ b/cps-service/src/main/java/org/onap/cps/events/EventsPublisher.java
@@ -44,7 +44,7 @@ public class EventsPublisher<T> {
/**
* KafkaTemplate for legacy (non-cloud) events.
- * Note: Cloud events should be used. This will be addressed as part of https://jira.onap.org/browse/CPS-1717.
+ * Note: Cloud events should be used. This will be addressed as part of https://lf-onap.atlassian.net/browse/CPS-1717.
*/
private final KafkaTemplate<String, T> legacyKafkaEventTemplate;
@@ -73,7 +73,7 @@ public class EventsPublisher<T> {
/**
* Generic Event publisher.
- * Note: Cloud events should be used. This will be addressed as part of https://jira.onap.org/browse/CPS-1717.
+ * Note: Cloud events should be used. This will be addressed as part of https://lf-onap.atlassian.net/browse/CPS-1717.
*
* @param topicName valid topic name
* @param eventKey message key
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 9859acdf0e..de57914527 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
@@ -204,18 +204,17 @@ public class DataNodeBuilder {
private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode,
final NormalizedNode normalizedNode) {
- if (normalizedNode instanceof ChoiceNode) {
- addChoiceNode(currentDataNode, (ChoiceNode) normalizedNode);
- } else if (normalizedNode instanceof DataContainerNode) {
- addYangContainer(currentDataNode, (DataContainerNode) normalizedNode);
- } else if (normalizedNode instanceof MapNode) {
- addDataNodeForEachListElement(currentDataNode, (MapNode) normalizedNode);
- } else if (normalizedNode instanceof ValueNode) {
- final ValueNode<NormalizedNode> valuesNode = (ValueNode) normalizedNode;
- addYangLeaf(currentDataNode, valuesNode.getIdentifier().getNodeType().getLocalName(),
- (Serializable) valuesNode.body());
- } else if (normalizedNode instanceof LeafSetNode) {
- addYangLeafList(currentDataNode, (LeafSetNode<?>) normalizedNode);
+ if (normalizedNode instanceof ChoiceNode choiceNode) {
+ addChoiceNode(currentDataNode, choiceNode);
+ } else if (normalizedNode instanceof DataContainerNode dataContainerNode) {
+ addYangContainer(currentDataNode, dataContainerNode);
+ } else if (normalizedNode instanceof MapNode mapNode) {
+ addDataNodeForEachListElement(currentDataNode, mapNode);
+ } else if (normalizedNode instanceof ValueNode<?> valueNode) {
+ addYangLeaf(currentDataNode, valueNode.getIdentifier().getNodeType().getLocalName(),
+ (Serializable) valueNode.body());
+ } else if (normalizedNode instanceof LeafSetNode<?> leafSetNode) {
+ addYangLeafList(currentDataNode, leafSetNode);
} else {
log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass());
}
@@ -243,7 +242,7 @@ public class DataNodeBuilder {
private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) {
final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName();
- List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body())
+ List<?> leafListValues = (leafSetNode.body())
.stream()
.map(NormalizedNode::body)
.collect(Collectors.toList());
diff --git a/cps-service/src/main/java/org/onap/cps/utils/ContentType.java b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java
index f888504843..eb8e592d9b 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/ContentType.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Deutsche Telekom AG
+ * Modifications Copyright (C) 2024 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +21,14 @@
package org.onap.cps.utils;
+import org.springframework.http.MediaType;
+
public enum ContentType {
JSON,
- XML
+ XML;
+
+ public static ContentType fromString(final String contentTypeAsString) {
+ return contentTypeAsString.contains(MediaType.APPLICATION_XML_VALUE) ? XML : JSON;
+ }
+
}
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
index 35dc7347b2..c3097cc552 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation.
+ * Copyright (C) 2022-2024 Nordix Foundation.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,53 +20,25 @@
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 lombok.RequiredArgsConstructor;
-import org.onap.cps.api.CpsAnchorService;
import org.onap.cps.api.impl.YangTextSchemaSourceSetCache;
-import org.onap.cps.cache.AnchorDataCacheEntry;
import org.onap.cps.cpspath.parser.CpsPathPrefixType;
import org.onap.cps.cpspath.parser.CpsPathQuery;
import org.onap.cps.cpspath.parser.CpsPathUtil;
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.common.QName;
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.opendaylight.yangtools.yang.model.api.SchemaNode;
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 CpsAnchorService cpsAnchorService;
-
private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache;
- private final IMap<String, AnchorDataCacheEntry> anchorDataCache;
-
- /**
- * 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 = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- return getPrefix(anchor, xpath);
- }
-
/**
* Get the module prefix for the given xpath under the given anchor.
*
@@ -75,57 +47,25 @@ public class PrefixResolver {
* @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 CpsPathQuery cpsPathQuery = CpsPathUtil.getCpsPathQuery(xpath);
- if (cpsPathQuery.getCpsPathPrefixType() == CpsPathPrefixType.ABSOLUTE) {
- final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0);
- if (prefixPerContainerName.containsKey(topLevelContainerName)) {
- return prefixPerContainerName.get(topLevelContainerName);
- }
+ if (cpsPathQuery.getCpsPathPrefixType() != CpsPathPrefixType.ABSOLUTE) {
+ return "";
}
- return "";
- }
+ final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0);
- private Map<String, String> createAndCachePrefixPerContainerNameMap(final Anchor anchor) {
final YangTextSchemaSourceSet yangTextSchemaSourceSet =
- yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), anchor.getSchemaSetName());
+ 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);
+ return schemaContext.getChildNodes().stream()
+ .filter(DataNodeContainer.class::isInstance)
+ .map(SchemaNode::getQName)
+ .filter(qname -> qname.getLocalName().equals(topLevelContainerName))
+ .findFirst()
+ .map(QName::getModule)
+ .flatMap(schemaContext::findModule)
+ .map(Module::getPrefix)
+ .orElse("");
}
}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
index 7a6d0bb3d5..94b97bd88f 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Deutsche Telekom AG
* Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * Modifications Copyright (C) 2024 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,11 +22,13 @@
package org.onap.cps.utils;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -33,6 +36,7 @@ import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
@@ -40,10 +44,14 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
+import org.onap.cps.spi.exceptions.DataValidationException;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import org.xml.sax.SAXException;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -156,6 +164,85 @@ public class XmlFileUtils {
return document;
}
+ /**
+ * Convert a list of data maps to XML format.
+ *
+ * @param dataMaps List of data maps to convert
+ * @return XML string representation of the data maps
+ */
+ @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION")
+ public static String convertDataMapsToXml(final List<Map<String, Object>> dataMaps) {
+ try {
+ final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
+ final Document document = documentBuilder.newDocument();
+ final DocumentFragment documentFragment = document.createDocumentFragment();
+ for (final Map<String, Object> dataMap : dataMaps) {
+ createXmlElements(document, documentFragment, dataMap);
+ }
+ return transformFragmentToString(documentFragment);
+ } catch (final DOMException | NullPointerException | ParserConfigurationException | TransformerException
+ exception) {
+ throw new DataValidationException(
+ "Data Validation Failed", "Failed to parse xml data: " + exception.getMessage(), exception);
+ }
+ }
+
+ private static void createXmlElements(final Document document, final Node parentNode,
+ final Map<String, Object> dataMap) {
+ for (final Map.Entry<String, Object> mapEntry : dataMap.entrySet()) {
+ if (mapEntry.getValue() instanceof List) {
+ appendList(document, parentNode, mapEntry);
+ } else if (mapEntry.getValue() instanceof Map) {
+ appendMap(document, parentNode, mapEntry);
+ } else {
+ appendObject(document, parentNode, mapEntry);
+ }
+ }
+ }
+
+ private static void appendList(final Document document, final Node parentNode,
+ final Map.Entry<String, Object> mapEntry) {
+ final List<Object> list = (List<Object>) mapEntry.getValue();
+ if (list.isEmpty()) {
+ final Element listElement = document.createElement(mapEntry.getKey());
+ parentNode.appendChild(listElement);
+ } else {
+ for (final Object element : list) {
+ final Element listElement = document.createElement(mapEntry.getKey());
+ if (element instanceof Map) {
+ createXmlElements(document, listElement, (Map<String, Object>) element);
+ } else {
+ listElement.appendChild(document.createTextNode(element.toString()));
+ }
+ parentNode.appendChild(listElement);
+ }
+ }
+ }
+
+ private static void appendMap(final Document document, final Node parentNode,
+ final Map.Entry<String, Object> mapEntry) {
+ final Element childElement = document.createElement(mapEntry.getKey());
+ createXmlElements(document, childElement, (Map<String, Object>) mapEntry.getValue());
+ parentNode.appendChild(childElement);
+ }
+
+ private static void appendObject(final Document document, final Node parentNode,
+ final Map.Entry<String, Object> mapEntry) {
+ final Element element = document.createElement(mapEntry.getKey());
+ element.appendChild(document.createTextNode(mapEntry.getValue().toString()));
+ parentNode.appendChild(element);
+ }
+
+ private static String transformFragmentToString(final DocumentFragment documentFragment)
+ throws TransformerException {
+ final Transformer transformer = getTransformerFactory().newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ final StringWriter writer = new StringWriter();
+ final StreamResult result = new StreamResult(writer);
+ transformer.transform(new DOMSource(documentFragment), result);
+ return writer.toString();
+ }
+
private static DocumentBuilderFactory getDocumentBuilderFactory() {
if (isNewDocumentBuilderFactoryInstance) {
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java
index dc23c6bc4a..168e0999d5 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java
@@ -21,6 +21,9 @@
package org.onap.cps.utils;
+import static org.onap.cps.utils.YangParserHelper.VALIDATE_AND_PARSE;
+import static org.onap.cps.utils.YangParserHelper.VALIDATE_ONLY;
+
import io.micrometer.core.annotation.Timed;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@@ -57,11 +60,12 @@ public class YangParser {
final String parentNodeXpath) {
final SchemaContext schemaContext = getSchemaContext(anchor);
try {
- return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+ return yangParserHelper
+ .parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE);
} catch (final DataValidationException e) {
invalidateCache(anchor);
}
- return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+ return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE);
}
/**
@@ -78,7 +82,31 @@ public class YangParser {
final Map<String, String> yangResourcesNameToContentMap,
final String parentNodeXpath) {
final SchemaContext schemaContext = getSchemaContext(yangResourcesNameToContentMap);
- return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+ return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_AND_PARSE);
+ }
+
+ /**
+ * Parses data to validate it, using the schema context for given anchor.
+ *
+ * @param anchor the anchor used for node data validation
+ * @param parentNodeXpath the xpath of the parent node
+ * @param nodeData JSON or XML data string to validate
+ * @param contentType the content type of the data (e.g., JSON or XML)
+ * @throws DataValidationException if validation fails
+ */
+ public void validateData(final ContentType contentType,
+ final String nodeData,
+ final Anchor anchor,
+ final String parentNodeXpath) {
+ final SchemaContext schemaContext = getSchemaContext(anchor);
+ try {
+ yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY);
+ } catch (final DataValidationException e) {
+ invalidateCache(anchor);
+ log.error("Data validation failed for anchor: {}, xpath: {}, details: {}", anchor, parentNodeXpath,
+ e.getMessage());
+ }
+ yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath, VALIDATE_ONLY);
}
private SchemaContext getSchemaContext(final Anchor anchor) {
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
index 597164598a..5612945ea9 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java
@@ -22,6 +22,7 @@ package org.onap.cps.utils;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.StringReader;
import java.net.URISyntaxException;
@@ -29,6 +30,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
@@ -70,6 +72,9 @@ public class YangParserHelper {
static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0";
static final String DATA_ROOT_NODE_TAG_NAME = "data";
+ static final String DATA_VALIDATION_FAILURE_MESSAGE = "Data Validation Failed";
+ static final boolean VALIDATE_ONLY = true;
+ static final boolean VALIDATE_AND_PARSE = false;
/**
* Parses data into NormalizedNode according to given schema context.
@@ -83,11 +88,20 @@ public class YangParserHelper {
public ContainerNode parseData(final ContentType contentType,
final String nodeData,
final SchemaContext schemaContext,
- final String parentNodeXpath) {
+ final String parentNodeXpath,
+ final boolean validateOnly) {
if (contentType == ContentType.JSON) {
- return parseJsonData(nodeData, schemaContext, parentNodeXpath);
+ final ContainerNode validatedAndParsedJson = parseJsonData(nodeData, schemaContext, parentNodeXpath);
+ if (validateOnly) {
+ return null;
+ }
+ return validatedAndParsedJson;
+ }
+ final NormalizedNodeResult normalizedNodeResult = parseXmlData(nodeData, schemaContext, parentNodeXpath);
+ if (validateOnly) {
+ return null;
}
- return parseXmlData(nodeData, schemaContext, parentNodeXpath);
+ return buildContainerNodeFormNormalizedNodeResult(normalizedNodeResult);
}
private ContainerNode parseJsonData(final String jsonData,
@@ -122,12 +136,13 @@ public class YangParserHelper {
jsonParserStream.parse(jsonReader);
} catch (final IOException | JsonSyntaxException | IllegalStateException | IllegalArgumentException exception) {
throw new DataValidationException(
- "Data Validation Failed", "Failed to parse json data. " + exception.getMessage(), exception);
+ DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse json data. " + exception.getMessage(), exception);
}
return dataContainerNodeBuilder.build();
}
- private ContainerNode parseXmlData(final String xmlData,
+ @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "Problem originates in 3PP code")
+ private NormalizedNodeResult parseXmlData(final String xmlData,
final SchemaContext schemaContext,
final String parentNodeXpath) {
final XMLInputFactory factory = XMLInputFactory.newInstance();
@@ -164,12 +179,17 @@ public class YangParserHelper {
} catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException
| ParserConfigurationException | TransformerException exception) {
throw new DataValidationException(
- "Data Validation Failed", "Failed to parse xml data: " + exception.getMessage(), exception);
+ DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse xml data: " + exception.getMessage(), exception);
}
+ return normalizedNodeResult;
+ }
+
+ private ContainerNode buildContainerNodeFormNormalizedNodeResult(final NormalizedNodeResult normalizedNodeResult) {
+
final DataContainerChild dataContainerChild =
- (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
+ (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
final YangInstanceIdentifier.NodeIdentifier nodeIdentifier =
- new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType());
+ new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType());
return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build();
}
@@ -181,12 +201,12 @@ public class YangParserHelper {
private static Map<String, Object> getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath,
final SchemaContext schemaContext) {
- final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath);
+ final List<String> xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath);
return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(),
new ArrayList<>());
}
- private static String[] xpathToNodeIdSequence(final String xpath) {
+ private static List<String> xpathToNodeIdSequence(final String xpath) {
try {
return CpsPathUtil.getXpathNodeIdSequence(xpath);
} catch (final PathParsingException pathParsingException) {
@@ -196,17 +216,16 @@ public class YangParserHelper {
}
private static Map<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(
- final String[] xpathNodeIdSequence,
+ final List<String> xpathNodeIdSequence,
final Collection<? extends DataSchemaNode> dataSchemaNodes,
final Collection<QName> dataSchemaNodeIdentifiers) {
- final String currentXpathNodeId = xpathNodeIdSequence[0];
+ final String currentXpathNodeId = xpathNodeIdSequence.get(0);
final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream()
.filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName()))
.findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId));
dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName());
- if (xpathNodeIdSequence.length <= 1) {
- final Map<String, Object> dataSchemaNodeAndIdentifiers =
- new HashMap<>();
+ if (xpathNodeIdSequence.size() <= 1) {
+ final Map<String, Object> dataSchemaNodeAndIdentifiers = new HashMap<>();
dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode);
dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers);
return dataSchemaNodeAndIdentifiers;
@@ -217,13 +236,11 @@ public class YangParserHelper {
((DataNodeContainer) currentDataSchemaNode).getChildNodes(),
dataSchemaNodeIdentifiers);
}
- throw schemaNodeNotFoundException(xpathNodeIdSequence[1]);
+ throw schemaNodeNotFoundException(xpathNodeIdSequence.get(1));
}
- private static String[] getNextLevelXpathNodeIdSequence(final String[] xpathNodeIdSequence) {
- final String[] nextXpathNodeIdSequence = new String[xpathNodeIdSequence.length - 1];
- System.arraycopy(xpathNodeIdSequence, 1, nextXpathNodeIdSequence, 0, nextXpathNodeIdSequence.length);
- return nextXpathNodeIdSequence;
+ private static List<String> getNextLevelXpathNodeIdSequence(final List<String> xpathNodeIdSequence) {
+ return xpathNodeIdSequence.subList(1, xpathNodeIdSequence.size());
}
private static DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) {
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index 9846b30158..8c208a1cf8 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -546,6 +546,20 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockDataUpdateEventsService.publishCpsDataUpdateEvent(anchor2, '/', DELETE, observedTimestamp)
}
+ def "Validating #scenario when dry run is enabled."() {
+ given: 'schema set for given anchors and dataspace references bookstore model'
+ setupSchemaSetMocks('bookstore.yang')
+ when: 'validating the data with the given parameters'
+ objectUnderTest.validateData(dataspaceName, anchorName, parentNodeXpath, data,contentType)
+ then: 'the appropriate yang parser method is invoked with correct parameters'
+ yangParser.validateData(contentType, data, anchor, xpath)
+ where: 'the following parameters were used'
+ scenario | parentNodeXpath | xpath | contentType | data
+ 'JSON data with root node xpath' | '/' | '' | ContentType.JSON | '{"bookstore":{"bookstore-name":"Easons"}}'
+ 'JSON data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.JSON | '{"bookstore-name":"Easons"}'
+ 'XML data with specific xpath' | '/bookstore' | '/bookstore' | ContentType.XML | '<bookstore-name>Easons</bookstore-name>'
+ }
+
def 'Start session.'() {
when: 'start session method is called'
objectUnderTest.startSession()
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
index 05c8983fc2..9f3456280e 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
@@ -171,6 +171,6 @@ class E2ENetworkSliceSpec extends Specification {
expect: 'schema context is built with no exception indicating the schema set being valid '
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
and: 'data is parsed with no exception indicating the model match'
- new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '') != null
+ new YangParserHelper().parseData(ContentType.JSON, jsonData, schemaContext, '', false) != null
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy
deleted file mode 100644
index 010308c42f..0000000000
--- a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 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.cache
-
-import com.hazelcast.config.Config
-import com.hazelcast.core.Hazelcast
-import com.hazelcast.map.IMap
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.ContextConfiguration
-import spock.lang.Specification
-
-@SpringBootTest
-@ContextConfiguration(classes = [AnchorDataCacheConfig])
-class AnchorDataCacheConfigSpec extends Specification {
-
- @Autowired
- private IMap<String, AnchorDataCacheEntry> anchorDataCache
-
- def 'Embedded (hazelcast) cache for Anchor Data.'() {
- expect: 'system is able to create an instance of the Anchor data cache'
- assert null != anchorDataCache
- and: 'there is at least 1 instance'
- assert Hazelcast.allHazelcastInstances.size() > 0
- and: 'anchorDataCache is present'
- assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCpsCore')
- }
-
- def 'Verify configs for Distributed Caches'(){
- given: 'the Anchor Data Cache config'
- def anchorDataCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config
- def anchorDataCacheMapConfig = anchorDataCacheConfig.mapConfigs.get('anchorDataCacheMapConfig')
- expect: 'system created instance with correct config'
- assert anchorDataCacheConfig.clusterName == 'cps-and-ncmp-test-caches'
- assert anchorDataCacheMapConfig.backupCount == 1
- assert anchorDataCacheMapConfig.asyncBackupCount == 0
- }
-
- def 'Verify deployment network configs for Distributed Caches'() {
- given: 'the Anchor Data Cache config'
- def anchorDataCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config.networkConfig
- expect: 'system created instance with correct config'
- assert anchorDataCacheNetworkConfig.join.autoDetectionConfig.enabled
- assert !anchorDataCacheNetworkConfig.join.kubernetesConfig.enabled
- }
-
- def 'Verify network config'() {
- given: 'Synchronization config object and test configuration'
- def objectUnderTest = new AnchorDataCacheConfig()
- def testConfig = new Config()
- when: 'kubernetes properties are enabled'
- objectUnderTest.cacheKubernetesEnabled = true
- objectUnderTest.cacheKubernetesServiceName = 'test-service-name'
- and: 'method called to update the discovery mode'
- objectUnderTest.updateDiscoveryMode(testConfig)
- then: 'applied properties are reflected'
- assert testConfig.networkConfig.join.kubernetesConfig.enabled
- assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name'
-
- }
-
-}
diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy
deleted file mode 100644
index 022cd74ea6..0000000000
--- a/cps-service/src/test/groovy/org/onap/cps/cache/HazelcastCacheConfigSpec.groovy
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 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.cache
-
-import com.hazelcast.config.Config
-import com.hazelcast.config.RestEndpointGroup
-import spock.lang.Specification
-
-class HazelcastCacheConfigSpec extends Specification {
-
- def objectUnderTest = new HazelcastCacheConfig()
-
- def 'Create Hazelcast instance with a #scenario'() {
- given: 'a cluster name'
- objectUnderTest.clusterName = 'my cluster'
- when: 'an hazelcast instance is created (name has to be unique)'
- def result = objectUnderTest.createHazelcastInstance(scenario, config)
- then: 'the instance is created and has the correct name'
- assert result.name == scenario
- and: 'if applicable it has a map config with the expected name'
- if (expectMapConfig) {
- assert result.config.mapConfigs.values()[0].name == 'my map config'
- } else {
- assert result.config.mapConfigs.isEmpty()
- }
- and: 'if applicable it has a queue config with the expected name'
- if (expectQueueConfig) {
- assert result.config.queueConfigs.values()[0].name == 'my queue config'
- } else {
- assert result.config.queueConfigs.isEmpty()
- }
- and: 'if applicable it has a set config with the expected name'
- if (expectSetConfig) {
- assert result.config.setConfigs.values()[0].name == 'my set config'
- } else {
- assert result.config.setConfigs.isEmpty()
- }
- where: 'the following configs are used'
- scenario | config || expectMapConfig | expectQueueConfig | expectSetConfig
- 'Map Config' | HazelcastCacheConfig.createMapConfig('my map config') || true | false | false
- 'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false | true | false
- 'Set Config' | HazelcastCacheConfig.createSetConfig('my set config') || false | false | true
- }
-
- def 'Verify Hazelcast Cluster Information'() {
- given: 'a test configuration'
- def testConfig = new Config()
- when: 'cluster information is exposed'
- objectUnderTest.exposeClusterInformation(testConfig)
- then: 'REST api configs are enabled'
- assert testConfig.networkConfig.restApiConfig.enabled
- and: 'only health check and cluster read are enabled'
- def enabledGroups = testConfig.networkConfig.restApiConfig.enabledGroups
- assert enabledGroups.size() == 2
- assert enabledGroups.containsAll([RestEndpointGroup.CLUSTER_READ, RestEndpointGroup.HEALTH_CHECK])
- }
-
-}
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 e305abee86..f028d5d5d9 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
@@ -35,6 +35,7 @@ class DataNodeBuilderSpec extends Specification {
def objectUnderTest = new DataNodeBuilder()
def yangParserHelper = new YangParserHelper()
+ def validateAndParse = false
def expectedLeavesByXpathMap = [
'/test-tree' : [],
@@ -60,7 +61,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('test-tree.json')
- def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
when: 'the container node is converted to a data node'
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -80,7 +81,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data parsed into container node object'
def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }'
- def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree', validateAndParse)
when: 'the container node is converted to a data node with parent node xpath defined'
def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -96,7 +97,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json')
- def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
when: 'the container node is converted to a data node '
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -129,7 +130,7 @@ class DataNodeBuilderSpec extends Specification {
def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']"
and: 'the json data fragment parsed into container node object for given parent node xpath'
def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}'
- def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath, validateAndParse)
when: 'the container node is converted to a data node with given parent node xpath'
def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build()
then: 'the resulting data node represents a child of augmentation node'
@@ -144,7 +145,7 @@ class DataNodeBuilderSpec extends Specification {
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'the json data fragment parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json')
- def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
when: 'the container node is converted to a data node'
def result = objectUnderTest.withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -162,7 +163,7 @@ class DataNodeBuilderSpec extends Specification {
and: 'parent node xpath referencing parent of list element'
def parentNodeXpath = '/test-tree'
and: 'the json data fragment (list element) parsed into container node object'
- def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath)
+ def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath, validateAndParse)
when: 'the container node is converted to a data node collection'
def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection()
def resultXpaths = result.collect { it.getXpath() }
diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy
index f38b45d17c..cada33ef06 100644
--- a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheEntrySpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy
@@ -1,40 +1,37 @@
-/*
- * ============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.cache
-
-
-import spock.lang.Specification
-
-class AnchorDataCacheEntrySpec extends Specification {
-
- def objectUnderTest = new AnchorDataCacheEntry()
-
- def 'Anchor Data Cache Properties Management.'() {
- when: 'a property named "sample" is added to the cache'
- objectUnderTest.setProperty('sample', 123)
- then: 'the cache has that property'
- assert objectUnderTest.hasProperty('sample')
- and: 'the value is correct'
- assert objectUnderTest.getProperty('sample') == 123
- and: 'the cache does not have an an object called "something else"'
- assert objectUnderTest.hasProperty('something else') == false
- }
-}
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 TechMahindra Ltd
+ * ================================================================================
+ * 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 spock.lang.Specification;
+import org.springframework.http.MediaType
+
+
+class ContentTypeSpec extends Specification {
+
+ def 'Should return correct ContentType based on given input.'() {
+ given: 'contentType fromString method converts the input string as expectedContentType'
+ ContentType.fromString(contentTypeString) == expectedContentType
+ where:
+ contentTypeString || expectedContentType
+ MediaType.APPLICATION_XML_VALUE || ContentType.XML
+ MediaType.APPLICATION_JSON_VALUE || ContentType.JSON
+ }
+
+}
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
index b975de6555..13b042f1ae 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
+ * Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
* ================================================================================
@@ -22,11 +22,8 @@
package org.onap.cps.utils
-import com.hazelcast.map.IMap
import org.onap.cps.TestUtils
-import org.onap.cps.api.CpsAnchorService
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
@@ -34,13 +31,9 @@ import spock.lang.Specification
class PrefixResolverSpec extends Specification {
- def mockCpsAnchorService = Mock(CpsAnchorService)
-
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
- def mockAnchorDataCache = Mock(IMap<String, AnchorDataCacheEntry>)
-
- def objectUnderTest = new PrefixResolver(mockCpsAnchorService, mockYangTextSchemaSourceSetCache, mockAnchorDataCache)
+ def objectUnderTest = new PrefixResolver(mockYangTextSchemaSourceSetCache)
def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
@@ -48,29 +41,16 @@ class PrefixResolverSpec extends Specification {
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'
- mockCpsAnchorService.getAnchor('testDataspace', 'testAnchor') >> anchor
- and: 'the schema source cache contains the schema context for the test-tree module'
- mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
- }
+ def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor')
def 'get xpath prefix using node schema context'() {
+ given: 'the schema source cache contains the schema context for the test-tree module'
+ mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+ mockYangTextSchemaSourceSetCache.get(*_) >> mockYangTextSchemaSourceSet
when: 'the prefix of the yang module is retrieved'
- def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', xpath)
+ def result = objectUnderTest.getPrefix(anchor, 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'
@@ -82,37 +62,4 @@ class PrefixResolverSpec extends Specification {
'/not-defined' || ''
}
- 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/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
index dc6027de25..3b21145293 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Deutsche Telekom AG
* Modifications Copyright (c) 2023-2024 Nordix Foundation
+ * Modifications Copyright (C) 2024 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,10 +22,14 @@
package org.onap.cps.utils
import org.onap.cps.TestUtils
+import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import org.w3c.dom.DOMException
import org.xml.sax.SAXParseException
import spock.lang.Specification
+import static org.onap.cps.utils.XmlFileUtils.convertDataMapsToXml
+
class XmlFileUtilsSpec extends Specification {
def 'Parse a valid xml content #scenario'(){
@@ -68,4 +73,56 @@ class XmlFileUtilsSpec extends Specification {
'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || '<?xml version="1.0" encoding="UTF-8"?><branch xmlns="org:onap:cps:test:test-tree"><name>Branch</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>'
}
+ def 'Convert data maps to XML #scenario'() {
+ when: 'data maps are converted to XML'
+ def result = convertDataMapsToXml(dataMaps)
+ then: 'the result contains the expected XML'
+ assert result == expectedXmlOutput
+ where:
+ scenario | dataMaps || expectedXmlOutput
+ 'single XML branch' | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': ['Sparrow', 'Owl']]]]] || '<branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds><birds>Owl</birds></nest></branch>'
+ 'nested XML branch' | [['test-tree': [branch: [name: 'Left', nest: [name: 'Small', birds: 'Sparrow']]]]] || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>'
+ 'list of branch within a test tree' | [['test-tree': [branch: [[name: 'Left', nest: [name: 'Small', birds: 'Sparrow']], [name: 'Right', nest: [name: 'Big', birds: 'Owl']]]]]] || '<test-tree><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch><branch><name>Right</name><nest><name>Big</name><birds>Owl</birds></nest></branch></test-tree>'
+ 'list of birds under a nest' | [['nest': ['name': 'Small', 'birds': ['Sparrow']]]] || '<nest><name>Small</name><birds>Sparrow</birds></nest>'
+ 'XML Content map with null key/value' | [['test-tree': [branch: [name: 'Left', nest: []]]]] || '<test-tree><branch><name>Left</name><nest/></branch></test-tree>'
+ 'XML Content list is empty' | [['nest': ['name': 'Small', 'birds': []]]] || '<nest><name>Small</name><birds/></nest>'
+ 'XML with mixed content in list' | [['branch': ['name': 'Left', 'nest': ['name': 'Small', 'birds': ['', 'Sparrow']]]]] || '<branch><name>Left</name><nest><name>Small</name><birds/><birds>Sparrow</birds></nest></branch>'
+ }
+
+ def 'Convert data maps to XML with null or empty maps and lists'() {
+ when: 'data maps with empty content are converted to XML'
+ def result = convertDataMapsToXml(dataMaps)
+ then: 'the result contains the expected XML or handles nulls correctly'
+ assert result == expectedXmlOutput
+ where:
+ scenario | dataMaps || expectedXmlOutput
+ 'null entry in map' | [['branch': []]] || '<branch/>'
+ 'list with null object' | [['branch': [name: 'Left', nest: [name: 'Small', birds: []]]]] || '<branch><name>Left</name><nest><name>Small</name><birds/></nest></branch>'
+ 'list containing null list' | [['test-tree': [branch: '']]] || '<test-tree><branch/></test-tree>'
+ 'nested map with null values' | [['test-tree': [branch: [name: 'Left', nest: '']]]] || '<test-tree><branch><name>Left</name><nest/></branch></test-tree>'
+ }
+
+ def 'Converting data maps to xml with no data'() {
+ given: 'A list of maps where entry is null'
+ def dataMapWithNull = [null]
+ when: 'convert the dataMaps to XML'
+ convertDataMapsToXml(dataMapWithNull)
+ then: 'a validation exception is thrown'
+ def exception = thrown(DataValidationException)
+ and:'the cause is a null pointer exception'
+ assert exception.cause instanceof NullPointerException
+ }
+
+ def 'Converting data maps to xml with document syntax error'() {
+ given: 'A list of maps with an invalid entry'
+ def dataMap = [['invalid<tag>': 'value']]
+ when: 'convert the dataMaps to XML'
+ convertDataMapsToXml(dataMap)
+ then: 'a validation exception is thrown'
+ def exception = thrown(DataValidationException)
+ and:'the cause is a document object model exception'
+ assert exception.cause instanceof DOMException
+
+ }
+
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
index 073383113d..e1490c28ab 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserHelperSpec.groovy
@@ -30,6 +30,8 @@ import spock.lang.Specification
class YangParserHelperSpec extends Specification {
def objectUnderTest = new YangParserHelper()
+ def validateOnly = true
+ def validateAndParse = false
def 'Parsing a valid multicontainer Json String.'() {
given: 'a yang model (file)'
@@ -38,7 +40,7 @@ class YangParserHelperSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'the json data is parsed'
- def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '')
+ def result = objectUnderTest.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse)
then: 'a ContainerNode holding collection of normalized nodes is returned'
result.body().getAt(index) instanceof NormalizedNode == true
then: 'qualified name of children created is as expected'
@@ -56,7 +58,7 @@ class YangParserHelperSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'the data is parsed'
- NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '')
+ NormalizedNode result = objectUnderTest.parseData(contentType, fileData, schemaContext, '', validateAndParse)
then: 'the result is a normalized node of the correct type'
if (revision) {
result.identifier.nodeType == QName.create(namespace, revision, localName)
@@ -74,7 +76,7 @@ class YangParserHelperSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'invalid data is parsed'
- objectUnderTest.parseData(contentType, invalidData, schemaContext, '')
+ objectUnderTest.parseData(contentType, invalidData, schemaContext, '', validateAndParse)
then: 'an exception is thrown'
thrown(DataValidationException)
where: 'the following invalid data is provided'
@@ -92,7 +94,7 @@ class YangParserHelperSpec extends Specification {
def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
when: 'json string is parsed'
- def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath)
+ def result = objectUnderTest.parseData(contentType, nodeData, schemaContext, parentNodeXpath, validateAndParse)
then: 'a ContainerNode holding collection of normalized nodes is returned'
result.body().getAt(0) instanceof NormalizedNode == true
then: 'result represents a node of expected type'
@@ -112,7 +114,7 @@ class YangParserHelperSpec extends Specification {
def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
when: 'json string is parsed'
- objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath)
+ objectUnderTest.parseData(ContentType.JSON, '{"nest": {"name" : "Nest", "birds": ["bird"]}}', schemaContext, parentNodeXpath, validateAndParse)
then: 'expected exception is thrown'
thrown(DataValidationException)
where:
@@ -129,7 +131,7 @@ class YangParserHelperSpec extends Specification {
def yangResourcesMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
when: 'malformed json string is parsed'
- objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '')
+ objectUnderTest.parseData(ContentType.JSON, invalidJson, schemaContext, '', validateAndParse)
then: 'an exception is thrown'
thrown(DataValidationException)
where: 'the following malformed json is provided'
@@ -145,7 +147,7 @@ class YangParserHelperSpec extends Specification {
and: 'some json data with space in the array elements'
def jsonDataWithSpacesInArrayElement = TestUtils.getResourceFileContent('bookstore.json')
when: 'that json data is parsed'
- objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '')
+ objectUnderTest.parseData(ContentType.JSON, jsonDataWithSpacesInArrayElement, schemaContext, '', validateAndParse)
then: 'no exception thrown'
noExceptionThrown()
}
@@ -162,5 +164,22 @@ class YangParserHelperSpec extends Specification {
'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories']
}
+ def 'Validating #scenario xpath String.'() {
+ given: 'a data model (file) is provided'
+ def fileData = TestUtils.getResourceFileContent(contentFile)
+ and: 'the schema context is built for that data model'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'the data is parsed to be validated'
+ objectUnderTest.parseData(contentType, fileData, schemaContext, parentNodeXpath, validateOnly)
+ then: 'no exception is thrown'
+ noExceptionThrown()
+ where:
+ scenario | parentNodeXpath | contentFile | contentType
+ 'JSON without parent node' | '' | 'bookstore.json' | ContentType.JSON
+ 'JSON with parent node' | '/bookstore' | 'bookstore-categories-data.json' | ContentType.JSON
+ 'XML without parent node' | '' | 'bookstore.xml' | ContentType.XML
+ 'XML with parent node' | '/bookstore' | 'bookstore-categories-data.xml' | ContentType.XML
+ }
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
index 18d0502e30..6c52becbe1 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangParserSpec.groovy
@@ -26,7 +26,6 @@ import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.Anchor
import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
import org.onap.cps.yang.YangTextSchemaSourceSet
-import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
import org.opendaylight.yangtools.yang.model.api.SchemaContext
import spock.lang.Specification
@@ -47,6 +46,8 @@ class YangParserSpec extends Specification {
def containerNodeFromYangUtils = Mock(ContainerNode)
def noParent = ''
+ def validateOnly = true
+ def validateAndParse = false
def setup() {
mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet
@@ -55,7 +56,7 @@ class YangParserSpec extends Specification {
def 'Parsing data.'() {
given: 'the yang parser (utility) always returns a container node'
- mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils
when: 'parsing some json data'
def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent)
then: 'the schema source set for the correct dataspace and schema set is retrieved form the cache'
@@ -68,7 +69,7 @@ class YangParserSpec extends Specification {
def 'Parsing data with exception on first attempt.'() {
given: 'the yang parser throws an exception on the first attempt only'
- mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { throw new DataValidationException(noParent, noParent) } >> containerNodeFromYangUtils
when: 'attempt to parse some data'
def result = objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent)
then: 'the cache is cleared for the correct dataspace and schema'
@@ -79,7 +80,7 @@ class YangParserSpec extends Specification {
def 'Parsing data with exception on all attempts.'() {
given: 'the yang parser always throws an exception'
- mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> { throw new DataValidationException(noParent, noParent) }
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> { throw new DataValidationException(noParent, noParent) }
when: 'attempt to parse some data'
objectUnderTest.parseData(ContentType.JSON, 'some json', anchor, noParent)
then: 'a data validation exception is thrown'
@@ -94,9 +95,46 @@ class YangParserSpec extends Specification {
when: 'parsing some json data'
def result = objectUnderTest.parseData(ContentType.JSON, 'some json', yangResourcesNameToContentMap, noParent)
then: 'the yang parser helper always returns a container node'
- 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent) >> containerNodeFromYangUtils
+ 1 * mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateAndParse) >> containerNodeFromYangUtils
and: 'the result is the same container node as return from yang utils'
assert result == containerNodeFromYangUtils
}
+ def 'Validating #scenario data using Yang parser with cache retrieval.'() {
+ given: 'the yang parser (utility) is set up and schema context is available'
+ mockYangParserHelper.parseData(contentType, 'some json', mockSchemaContext, noParent, validateOnly)
+ when: 'attempt to parse data with no parent node xpath'
+ objectUnderTest.validateData(contentType, 'some json or xml data', anchor, noParent)
+ then: 'the correct schema set is retrieved from the cache for the dataspace and schema'
+ 1 * mockYangTextSchemaSourceSetCache.get('my dataspace', 'my schema') >> mockYangTextSchemaSourceSet
+ and: 'no cache entries are removed during validation'
+ 0 * mockYangTextSchemaSourceSetCache.removeFromCache(*_)
+ where:
+ scenario | contentType
+ 'JSON' | ContentType.JSON
+ 'XML' | ContentType.XML
+ }
+
+ def 'Validating data when parsing fails on first attempt and recovers.'() {
+ given: 'the Yang parser throws an exception on the first attempt but succeeds on the second'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) } >> null
+ when: 'attempting to parse JSON data'
+ objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent)
+ then: 'the cache is cleared for the correct dataspace and schema after the first failure'
+ 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
+ and: 'no exceptions are thrown after the second attempt'
+ noExceptionThrown()
+ }
+
+ def 'Validating data with repeated parsing failures leading to exception.'() {
+ given: 'the yang parser throws an exception on the first attempt only'
+ mockYangParserHelper.parseData(ContentType.JSON, 'some json', mockSchemaContext, noParent, validateOnly) >> { throw new DataValidationException(noParent, noParent) }
+ when: 'attempting to parse JSON data'
+ objectUnderTest.validateData(ContentType.JSON, 'some json', anchor, noParent)
+ then: 'a data validation exception is thrown'
+ thrown(DataValidationException)
+ and: 'the cache is cleared for the correct dataspace and schema after the failure'
+ 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my dataspace', 'my schema')
+ }
+
}
diff --git a/cps-service/src/test/resources/application.yml b/cps-service/src/test/resources/application.yml
index b666885f22..be71d37d2a 100644
--- a/cps-service/src/test/resources/application.yml
+++ b/cps-service/src/test/resources/application.yml
@@ -1,6 +1,6 @@
# ============LICENSE_START=======================================================
# Copyright (c) 2021 Bell Canada.
-# Modification Copyright (C) 2022 Nordix Foundation.
+# Modification Copyright (C) 2022-2024 Nordix Foundation.
# ================================================================================
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -41,11 +41,3 @@ spring:
logging:
level:
org.apache.kafka: ERROR
-
-# Custom Hazelcast Config.
-hazelcast:
- cluster-name: "cps-and-ncmp-test-caches"
- mode:
- kubernetes:
- enabled: false
- service-name: "cps-and-ncmp-service"
diff --git a/cps-service/src/test/resources/bookstore-categories-data.json b/cps-service/src/test/resources/bookstore-categories-data.json
new file mode 100644
index 0000000000..7dc22b17f7
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore-categories-data.json
@@ -0,0 +1,49 @@
+{
+ "categories": [
+ {
+ "code": "01/1",
+ "name": "SciFi",
+ "books": [
+ {
+ "authors": [
+ "Iain M. Banks"
+ ],
+ "lang": "en/it",
+ "price": "895",
+ "pub_year": "1994",
+ "title": "Feersum Endjinn/Endjinn Feersum"
+ },
+ {
+ "authors": [
+ "Ursula K. Le Guin",
+ "Joe Haldeman",
+ "Orson Scott Card",
+ "david Brin",
+ "Rober Silverberg",
+ "Dan Simmons",
+ "Greg Bear"
+ ],
+ "lang": "en",
+ "price": "1099",
+ "pub_year": "1999",
+ "title": "Far Horizons"
+ }
+ ]
+ },
+ {
+ "name": "kids",
+ "code": "02",
+ "books": [
+ {
+ "authors": [
+ "Philip Pullman"
+ ],
+ "lang": "en",
+ "price": "699",
+ "pub_year": "1995",
+ "title": "The Golden Compass"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/cps-service/src/test/resources/bookstore-categories-data.xml b/cps-service/src/test/resources/bookstore-categories-data.xml
new file mode 100644
index 0000000000..c8592c1f90
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore-categories-data.xml
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<categories>
+ <code>1</code>
+ <name>SciFi</name>
+ <books>
+ <title>2001: A Space Odyssey</title>
+ <lang>en</lang>
+ <authors>
+ Iain M. Banks
+ </authors>
+ <pub_year>1994</pub_year>
+ <price>895</price>
+ </books>
+</categories> \ No newline at end of file
diff --git a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang
index 32517398a3..2dabd79e8e 100644
--- a/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang
+++ b/cps-service/src/test/resources/e2e/basic/cps-cavsta-onap-internal2021-01-28.yang
@@ -36,7 +36,7 @@ module cps-cavsta-onap-internal {
description
"RAN Network YANG Model for ONAP/O-RAN POC";
reference
- "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
+ "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
}
typedef Tac {
diff --git a/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang b/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang
index c16a682512..2401409443 100644
--- a/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang
+++ b/cps-service/src/test/resources/e2e/basic/cps-ran-inventory@2021-01-28.yang
@@ -34,7 +34,7 @@ module cps-ran-inventory {
description
"RAN Network YANG Model for ONAP/O-RAN POC";
reference
- "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
+ "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
}
typedef Mcc {
diff --git a/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang b/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang
index 5fd292a99d..3223b15e65 100644
--- a/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang
+++ b/cps-service/src/test/resources/e2e/basic/cps-ran-schema-model@2021-05-19.yang
@@ -43,14 +43,14 @@ module cps-ran-schema-model {
description
"Added support for OOF PCI SON Use case";
reference
- "https://wiki.onap.org/display/DW/CPS+APIs";
+ "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16456851/CPS+APIs";
}
revision 2021-01-28 {
description
"CPS RAN Network YANG Model for ONAP/O-RAN POC";
reference
- "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
+ "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
}
typedef usageState {
diff --git a/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang b/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang
index 5065659307..a4612e73fb 100755
--- a/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang
+++ b/cps-service/src/test/resources/e2e/basic/ran-network2020-08-06.yang
@@ -43,7 +43,7 @@ module ran-network {
description
"RAN Network YANG Model for ONAP/O-RAN POC";
reference
- "https://wiki.onap.org/display/DW/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
+ "https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16414819/E2E+Network+Slicing+Use+Case+in+R7+Guilin";
}
typedef usageState {