diff options
Diffstat (limited to 'cps-service')
5 files changed, 195 insertions, 1 deletions
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 + + } + } |