summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-rest/docs/openapi/components.yml4
-rw-r--r--cps-rest/docs/openapi/cpsDataV2.yml9
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java28
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy21
-rw-r--r--cps-service/pom.xml5
-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/XmlFileUtils.java87
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy37
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy57
-rw-r--r--docs/api/swagger/cps/openapi.yaml52
-rw-r--r--docs/api/swagger/ncmp/openapi-inventory.yaml23
-rw-r--r--docs/api/swagger/ncmp/openapi.yaml20
12 files changed, 325 insertions, 28 deletions
diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml
index 40f0e170ff..728295fd31 100644
--- a/cps-rest/docs/openapi/components.yml
+++ b/cps-rest/docs/openapi/components.yml
@@ -293,7 +293,9 @@ components:
description: Content type in header
schema:
type: string
- example: 'application/json'
+ enum:
+ - application/json
+ - application/xml
required: true
descendantsInQuery:
name: descendants
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml
index d5a8ef3891..999c5b2c19 100644
--- a/cps-rest/docs/openapi/cpsDataV2.yml
+++ b/cps-rest/docs/openapi/cpsDataV2.yml
@@ -28,6 +28,7 @@ nodeByDataspaceAndAnchor:
- $ref: 'components.yml#/components/parameters/anchorNameInPath'
- $ref: 'components.yml#/components/parameters/xpathInQuery'
- $ref: 'components.yml#/components/parameters/descendantsInQuery'
+ - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
responses:
'200':
description: OK
@@ -38,6 +39,14 @@ nodeByDataspaceAndAnchor:
examples:
dataSample:
$ref: 'components.yml#/components/examples/dataSample'
+ application/xml:
+ schema:
+ type: object
+ xml:
+ name: stores
+ examples:
+ dataSample:
+ $ref: 'components.yml#/components/examples/dataSampleXml'
'400':
$ref: 'components.yml#/components/responses/BadRequest'
'403':
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 7390afcf98..6d22581845 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
@@ -48,8 +48,8 @@ import org.onap.cps.utils.ContentType;
import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
import org.onap.cps.utils.PrefixResolver;
+import org.onap.cps.utils.XmlFileUtils;
import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -75,7 +75,7 @@ public class DataRestController implements CpsDataApi {
final String contentTypeInHeader,
final String nodeData, final String parentNodeXpath,
final Boolean dryRunEnabled, final String observedTimestamp) {
- final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
+ final ContentType contentType = ContentType.fromString(contentTypeInHeader);
if (Boolean.TRUE.equals(dryRunEnabled)) {
cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
return ResponseEntity.ok().build();
@@ -105,7 +105,7 @@ public class DataRestController implements CpsDataApi {
final String anchorName, final String parentNodeXpath,
final String contentTypeInHeader, final String nodeData,
final String observedTimestamp) {
- final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
+ final ContentType contentType = ContentType.fromString(contentTypeInHeader);
cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath,
nodeData, toOffsetDateTime(observedTimestamp), contentType);
return new ResponseEntity<>(HttpStatus.CREATED);
@@ -129,8 +129,9 @@ public class DataRestController implements CpsDataApi {
@Timed(value = "cps.data.controller.datanode.get.v2",
description = "Time taken to get data node")
public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName,
- final String xpath,
+ final String contentTypeInHeader, final String xpath,
final String fetchDescendantsOptionAsString) {
+ final ContentType contentType = ContentType.fromString(contentTypeInHeader);
final FetchDescendantsOption fetchDescendantsOption =
FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
@@ -142,7 +143,7 @@ public class DataRestController implements CpsDataApi {
final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
dataMaps.add(dataMap);
}
- return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK);
+ return buildResponseEntity(dataMaps, contentType);
}
@Override
@@ -150,7 +151,7 @@ public class DataRestController implements CpsDataApi {
final String anchorName, final String contentTypeInHeader,
final String nodeData, final String parentNodeXpath,
final String observedTimestamp) {
- final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
+ final ContentType contentType = ContentType.fromString(contentTypeInHeader);
cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath,
nodeData, toOffsetDateTime(observedTimestamp), contentType);
return new ResponseEntity<>(HttpStatus.OK);
@@ -161,7 +162,7 @@ public class DataRestController implements CpsDataApi {
final String anchorName, final String contentTypeInHeader,
final String nodeData, final String parentNodeXpath,
final String observedTimestamp) {
- final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader);
+ final ContentType contentType = ContentType.fromString(contentTypeInHeader);
cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath,
nodeData, toOffsetDateTime(observedTimestamp), contentType);
return new ResponseEntity<>(HttpStatus.OK);
@@ -219,12 +220,19 @@ public class DataRestController implements CpsDataApi {
final List<DeltaReport> deltaBetweenAnchors =
cpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, sourceAnchorName,
- targetAnchorName, xpath, fetchDescendantsOption);
+ targetAnchorName, xpath, fetchDescendantsOption);
return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaBetweenAnchors), HttpStatus.OK);
}
- private static ContentType getContentTypeFromHeader(final String contentTypeInHeader) {
- return contentTypeInHeader.contains(MediaType.APPLICATION_XML_VALUE) ? ContentType.XML : ContentType.JSON;
+ ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataMaps,
+ final ContentType contentType) {
+ final String responseData;
+ if (contentType == ContentType.XML) {
+ responseData = XmlFileUtils.convertDataMapsToXml(dataMaps);
+ } else {
+ responseData = jsonObjectMapper.asJsonString(dataMaps);
+ }
+ return new ResponseEntity<>(responseData, HttpStatus.OK);
}
private static boolean isRootXpath(final String xpath) {
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 705c2fee91..27738b07c6 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
@@ -314,7 +314,9 @@ class DataRestControllerSpec extends Specification {
mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2]
when: 'V2 of get request is performed through REST API'
def response =
- mvc.perform(get(endpoint).param('xpath', xpath))
+ mvc.perform(get(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .param('xpath', xpath))
.andReturn().response
then: 'a success response is returned'
response.status == HttpStatus.OK.value()
@@ -326,6 +328,21 @@ class DataRestControllerSpec extends Specification {
assert numberOfDataTrees == 2
}
+ def 'Get all the data trees as XML with root node xPath using V2'() {
+ given: 'the service returns all data node leaves'
+ def xpath = '/'
+ def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
+ mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren]
+ when: 'V2 of get request is performed through REST API with XML content type'
+ def response =
+ mvc.perform(get(endpoint).contentType(MediaType.APPLICATION_XML).param('xpath', xpath))
+ .andReturn().response
+ then: 'a success response is returned'
+ response.status == HttpStatus.OK.value()
+ and: 'the response contains the datanode in XML format'
+ response.getContentAsString() == '<parent-1><leaf>value</leaf><leafList>leaveListElement1</leafList><leafList>leaveListElement2</leafList></parent-1>'
+ }
+
def 'Get data node with #scenario using V2.'() {
given: 'the service returns data nodes with #scenario'
def xpath = 'some xPath'
@@ -335,6 +352,7 @@ class DataRestControllerSpec extends Specification {
def response =
mvc.perform(
get(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
.param('xpath', xpath)
.param('descendants', includeDescendantsOption))
.andReturn().response
@@ -361,6 +379,7 @@ class DataRestControllerSpec extends Specification {
def response =
mvc.perform(
get(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
.param('xpath', xpath)
.param('descendants', '2'))
.andReturn().response
diff --git a/cps-service/pom.xml b/cps-service/pom.xml
index c7a3666a07..fdd6272660 100644
--- a/cps-service/pom.xml
+++ b/cps-service/pom.xml
@@ -5,6 +5,7 @@
Modifications Copyright (C) 2021 Bell Canada.
Modifications Copyright (C) 2021 Pantheon.tech
Modifications 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.
@@ -192,5 +193,9 @@
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
</dependencies>
</project>
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/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/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy
new file mode 100644
index 0000000000..cada33ef06
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/ContentTypeSpec.groovy
@@ -0,0 +1,37 @@
+/*
+ * ============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/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/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml
index 3069b18d1e..3f889c1e6c 100644
--- a/docs/api/swagger/cps/openapi.yaml
+++ b/docs/api/swagger/cps/openapi.yaml
@@ -1162,6 +1162,15 @@ paths:
default: none
example: "3"
type: string
+ - description: Content type in header
+ in: header
+ name: Content-Type
+ required: true
+ schema:
+ enum:
+ - application/json
+ - application/xml
+ type: string
responses:
"200":
content:
@@ -1172,6 +1181,15 @@ paths:
value: null
schema:
type: object
+ application/xml:
+ examples:
+ dataSample:
+ $ref: '#/components/examples/dataSampleXml'
+ value: null
+ schema:
+ type: object
+ xml:
+ name: stores
description: OK
"400":
content:
@@ -1347,7 +1365,9 @@ paths:
name: Content-Type
required: true
schema:
- example: application/json
+ enum:
+ - application/json
+ - application/xml
type: string
requestBody:
content:
@@ -1472,7 +1492,9 @@ paths:
name: Content-Type
required: true
schema:
- example: application/json
+ enum:
+ - application/json
+ - application/xml
type: string
requestBody:
content:
@@ -1597,7 +1619,9 @@ paths:
name: Content-Type
required: true
schema:
- example: application/json
+ enum:
+ - application/json
+ - application/xml
type: string
requestBody:
content:
@@ -1788,7 +1812,9 @@ paths:
name: Content-Type
required: true
schema:
- example: application/json
+ enum:
+ - application/json
+ - application/xml
type: string
requestBody:
content:
@@ -2543,6 +2569,16 @@ components:
default: none
example: "3"
type: string
+ contentTypeInHeader:
+ description: Content type in header
+ in: header
+ name: Content-Type
+ required: true
+ schema:
+ enum:
+ - application/json
+ - application/xml
+ type: string
observedTimestampInQuery:
description: observed-timestamp
in: query
@@ -2551,14 +2587,6 @@ components:
schema:
example: 2021-03-21T00:10:34.030-0100
type: string
- contentTypeInHeader:
- description: Content type in header
- in: header
- name: Content-Type
- required: true
- schema:
- example: application/json
- type: string
dryRunInQuery:
description: "Boolean flag to validate data, without persisting it. Default\
\ value is set to false."
diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml
index a2c7af6fda..8552ad53e3 100644
--- a/docs/api/swagger/ncmp/openapi-inventory.yaml
+++ b/docs/api/swagger/ncmp/openapi-inventory.yaml
@@ -131,9 +131,19 @@ paths:
- network-cm-proxy-inventory
/v1/ch/searches:
post:
- description: "Query and get CMHandleIds for additional properties, public properties\
- \ and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin)."
+ description: "Query and get CMHandle references for additional properties, public\
+ \ properties and registered DMI plugin (DMI plugin, DMI data plugin, DMI model\
+ \ plugin)."
operationId: searchCmHandleIds
+ parameters:
+ - description: Boolean parameter to determine if returned value(s) will be cmHandle
+ Ids or Alternate Ids for a given query
+ in: query
+ name: outputAlternateId
+ required: false
+ schema:
+ default: false
+ type: boolean
requestBody:
content:
application/json:
@@ -182,6 +192,15 @@ components:
schema:
example: my-dmi-plugin
type: string
+ outputAlternateIdOptionInQuery:
+ description: Boolean parameter to determine if returned value(s) will be cmHandle
+ Ids or Alternate Ids for a given query
+ in: query
+ name: outputAlternateId
+ required: false
+ schema:
+ default: false
+ type: boolean
responses:
NoContent:
content: {}
diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml
index f93395a6db..aa732c8566 100644
--- a/docs/api/swagger/ncmp/openapi.yaml
+++ b/docs/api/swagger/ncmp/openapi.yaml
@@ -1129,7 +1129,7 @@ paths:
- network-cm-proxy
/v1/ch/id-searches:
post:
- description: Execute cm handle query search and return a list of cm handle ids.
+ description: Execute cm handle query search and return a list of cm handle references.
Any number of conditions can be applied. To be included in the result a cm-handle
must fulfill ALL the conditions. An empty collection will be returned in the
case that the cm handle does not match a condition. For more on cm handle
@@ -1140,6 +1140,15 @@ paths:
Path Read the Docs</a>. The cm handle ancestor is automatically returned for
this query.
operationId: searchCmHandleIds
+ parameters:
+ - description: Boolean parameter to determine if returned value(s) will be cmHandle
+ Ids or Alternate Ids for a given query
+ in: query
+ name: outputAlternateId
+ required: false
+ schema:
+ default: false
+ type: boolean
requestBody:
content:
application/json:
@@ -1608,6 +1617,15 @@ components:
schema:
example: 2024-01-22
type: string
+ outputAlternateIdOptionInQuery:
+ description: Boolean parameter to determine if returned value(s) will be cmHandle
+ Ids or Alternate Ids for a given query
+ in: query
+ name: outputAlternateId
+ required: false
+ schema:
+ default: false
+ type: boolean
dataSyncEnabled:
description: Is used to enable or disable the data synchronization flag
in: query