From 6ce84d98f68b45f02f16dc99423670f4a53fd946 Mon Sep 17 00:00:00 2001 From: Michal Jagiello Date: Tue, 13 Dec 2022 07:40:19 +0000 Subject: XML content on create anchors node support Add XML content type support on anchor node creation. Issue-ID: CPS-1257 Change-Id: I7e7a9a1961b6e81de93a4e32e842b47f8a163a09 Signed-off-by: Michal Jagiello Signed-off-by: Lee Anjella Macabuhay --- .../main/java/org/onap/cps/api/CpsDataService.java | 38 ++++- .../org/onap/cps/api/impl/CpsDataServiceImpl.java | 58 +++++--- .../main/java/org/onap/cps/utils/ContentType.java | 26 ++++ .../main/java/org/onap/cps/utils/XmlFileUtils.java | 165 +++++++++++++++++++++ .../main/java/org/onap/cps/utils/YangUtils.java | 154 ++++++++++++++++--- 5 files changed, 392 insertions(+), 49 deletions(-) create mode 100644 cps-service/src/main/java/org/onap/cps/utils/ContentType.java create mode 100644 cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java (limited to 'cps-service/src/main/java') 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 b2e8c5ba4..012d7f825 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 @@ -3,6 +3,7 @@ * Copyright (C) 2020-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ import java.util.Collection; import java.util.Map; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; +import org.onap.cps.utils.ContentType; /* * Datastore interface for handling CPS data. @@ -38,10 +40,22 @@ public interface CpsDataService { * * @param dataspaceName dataspace name * @param anchorName anchor name - * @param jsonData json data + * @param nodeData node data * @param observedTimestamp observedTimestamp */ - void saveData(String dataspaceName, String anchorName, String jsonData, OffsetDateTime observedTimestamp); + void saveData(String dataspaceName, String anchorName, String nodeData, OffsetDateTime observedTimestamp); + + /** + * Persists data for the given anchor and dataspace. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param nodeData node data + * @param observedTimestamp observedTimestamp + * @param contentType node data content type + */ + void saveData(String dataspaceName, String anchorName, String nodeData, OffsetDateTime observedTimestamp, + ContentType contentType); /** * Persists child data fragment under existing data node for the given anchor and dataspace. @@ -49,11 +63,25 @@ public interface CpsDataService { * @param dataspaceName dataspace name * @param anchorName anchor name * @param parentNodeXpath parent node xpath - * @param jsonData json data + * @param nodeData node data * @param observedTimestamp observedTimestamp */ - void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData, - OffsetDateTime observedTimestamp); + void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + OffsetDateTime observedTimestamp); + + /** + * Persists child data fragment under existing data node for the given anchor, dataspace and content type. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param parentNodeXpath parent node xpath + * @param nodeData node data + * @param observedTimestamp observedTimestamp + * @param contentType node data content type + * + */ + void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + OffsetDateTime observedTimestamp, ContentType contentType); /** * Persists child data fragment representing one or more list elements under existing data node for the 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 732b49499..c776e5bb3 100755 --- 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 @@ -4,6 +4,7 @@ * Modifications Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +47,7 @@ import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.utils.CpsValidator; +import org.onap.cps.utils.ContentType; import org.onap.cps.utils.YangUtils; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; @@ -66,21 +68,34 @@ public class CpsDataServiceImpl implements CpsDataService { private final CpsValidator cpsValidator; @Override - public void saveData(final String dataspaceName, final String anchorName, final String jsonData, + public void saveData(final String dataspaceName, final String anchorName, final String nodeData, final OffsetDateTime observedTimestamp) { + saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON); + } + + @Override + public void saveData(final String dataspaceName, final String anchorName, final String nodeData, + final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection dataNodes = - buildDataNodes(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData); + buildDataNodes(dataspaceName, anchorName, ROOT_NODE_XPATH, nodeData, contentType); cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes); processDataUpdatedEventAsync(dataspaceName, anchorName, ROOT_NODE_XPATH, CREATE, observedTimestamp); } @Override public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final String jsonData, final OffsetDateTime observedTimestamp) { + final String nodeData, final OffsetDateTime observedTimestamp) { + saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON); + } + + @Override + public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final String nodeData, final OffsetDateTime observedTimestamp, + final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection dataNodes = - buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData); + buildDataNodes(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType); cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes); processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, CREATE, observedTimestamp); } @@ -90,7 +105,7 @@ public class CpsDataServiceImpl implements CpsDataService { final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection listElementDataNodeCollection = - buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData); + buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON); cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath, listElementDataNodeCollection); processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp); @@ -101,7 +116,7 @@ public class CpsDataServiceImpl implements CpsDataService { final Collection jsonDataList, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection> listElementDataNodeCollections = - buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList); + buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList, ContentType.JSON); cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath, listElementDataNodeCollections); processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp); @@ -118,7 +133,7 @@ public class CpsDataServiceImpl implements CpsDataService { public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData); + final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON); cpsDataPersistenceService .updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves()); processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp); @@ -132,7 +147,7 @@ public class CpsDataServiceImpl implements CpsDataService { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection dataNodeUpdates = buildDataNodes(dataspaceName, anchorName, - parentNodeXpath, dataNodeUpdatesAsJson); + parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON); for (final DataNode dataNodeUpdate : dataNodeUpdates) { processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate); } @@ -166,7 +181,7 @@ public class CpsDataServiceImpl implements CpsDataService { final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection dataNodes = - buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData); + buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON); final ArrayList nodes = new ArrayList<>(dataNodes); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, nodes); processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp); @@ -189,7 +204,7 @@ public class CpsDataServiceImpl implements CpsDataService { final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Collection newListElements = - buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData); + buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON); replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp); } @@ -226,18 +241,20 @@ public class CpsDataServiceImpl implements CpsDataService { } private DataNode buildDataNode(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final String jsonData) { + final String parentNodeXpath, final String nodeData, + final ContentType contentType) { final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName()); if (ROOT_NODE_XPATH.equals(parentNodeXpath)) { - final ContainerNode containerNode = YangUtils.parseJsonData(jsonData, schemaContext); + final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext); return new DataNodeBuilder().withContainerNode(containerNode).build(); } final ContainerNode containerNode = YangUtils - .parseJsonData(jsonData, schemaContext, parentNodeXpath); + .parseData(contentType, nodeData, schemaContext, parentNodeXpath); + return new DataNodeBuilder() .withParentNodeXpath(parentNodeXpath) .withContainerNode(containerNode) @@ -248,18 +265,19 @@ public class CpsDataServiceImpl implements CpsDataService { final Map nodesJsonData) { return nodesJsonData.entrySet().stream().map(nodeJsonData -> buildDataNode(dataspaceName, anchorName, nodeJsonData.getKey(), - nodeJsonData.getValue())).collect(Collectors.toList()); + nodeJsonData.getValue(), ContentType.JSON)).collect(Collectors.toList()); } private Collection buildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final String jsonData) { + final String nodeData, + final ContentType contentType) { final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName()); if (ROOT_NODE_XPATH.equals(parentNodeXpath)) { - final ContainerNode containerNode = YangUtils.parseJsonData(jsonData, schemaContext); + final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext); final Collection dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); @@ -268,7 +286,7 @@ public class CpsDataServiceImpl implements CpsDataService { } return dataNodes; } - final ContainerNode containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath); + final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath); final Collection dataNodes = new DataNodeBuilder() .withParentNodeXpath(parentNodeXpath) .withContainerNode(containerNode) @@ -281,9 +299,9 @@ public class CpsDataServiceImpl implements CpsDataService { } private Collection> buildDataNodes(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final Collection jsonDataList) { - return jsonDataList.stream() - .map(jsonData -> buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData)) + final String parentNodeXpath, final Collection nodeDataList, final ContentType contentType) { + return nodeDataList.stream() + .map(nodeData -> buildDataNodes(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType)) .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 new file mode 100644 index 000000000..f88850484 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java @@ -0,0 +1,26 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Deutsche Telekom AG + * ================================================================================ + * 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; + +public enum ContentType { + JSON, + XML +} 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 new file mode 100644 index 000000000..0946ae3f6 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java @@ -0,0 +1,165 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Deutsche Telekom AG + * ================================================================================ + * 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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +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.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +public class XmlFileUtils { + + private static DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + private static final String DATA_ROOT_NODE_TAG_NAME = "data"; + private static final String ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; + private static final Pattern XPATH_PROPERTY_REGEX = Pattern.compile( + "\\[@(\\S{1,100})=['\\\"](\\S{1,100})['\\\"]\\]"); + + /** + * Prepare XML content. + * + * @param xmlContent XML content sent to store + * @param schemaContext schema context + * @return XML content wrapped by root node (if needed) + */ + public static String prepareXmlContent(final String xmlContent, final SchemaContext schemaContext) { + + return addRootNodeToXmlContent(xmlContent, schemaContext.getModules().iterator().next().getName(), + ROOT_NODE_NAMESPACE); + + } + + /** + * Prepare XML content. + * + * @param xmlContent XML content sent to store + * @param parentSchemaNode Parent schema node + * @return XML content wrapped by root node (if needed) + */ + public static String prepareXmlContent(final String xmlContent, final DataSchemaNode parentSchemaNode, + final String xpath) { + final String namespace = parentSchemaNode.getQName().getNamespace().toString(); + final String parentXpathPart = xpath.substring(xpath.lastIndexOf('/') + 1); + final Matcher regexMatcher = XPATH_PROPERTY_REGEX.matcher(parentXpathPart); + if (regexMatcher.find()) { + final HashMap rootNodePropertyMap = new HashMap(); + rootNodePropertyMap.put(regexMatcher.group(1), regexMatcher.group(2)); + return addRootNodeToXmlContent(xmlContent, parentSchemaNode.getQName().getLocalName(), namespace, + rootNodePropertyMap); + } + + return addRootNodeToXmlContent(xmlContent, parentSchemaNode.getQName().getLocalName(), namespace); + } + + /** + * Add root node to XML content. + * + * @param xmlContent xml content to add root node + * @param rootNodeTagName root node tag name + * @param namespace root node namespace + * @param rootNodeProperty root node properites map + * @return An edited content with added root node (if needed) + */ + public static String addRootNodeToXmlContent(final String xmlContent, final String rootNodeTagName, + final String namespace, + final HashMap rootNodeProperty) { + try { + final DocumentBuilder documentBuilder = dbFactory.newDocumentBuilder(); + final StringBuilder xmlStringBuilder = new StringBuilder(); + xmlStringBuilder.append(xmlContent); + Document xmlDoc = documentBuilder.parse( + new ByteArrayInputStream(xmlStringBuilder.toString().getBytes("utf-8"))); + final Element root = xmlDoc.getDocumentElement(); + if (!root.getTagName().equals(rootNodeTagName) && !root.getTagName().equals(DATA_ROOT_NODE_TAG_NAME)) { + xmlDoc = addDataRootNode(root, rootNodeTagName, namespace, rootNodeProperty); + xmlDoc.setXmlStandalone(true); + final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + final Transformer transformer = transformerFactory.newTransformer(); + final StringWriter stringWriter = new StringWriter(); + transformer.transform(new DOMSource(xmlDoc), new StreamResult(stringWriter)); + return stringWriter.toString(); + } + return xmlContent; + } catch (SAXException | IOException | ParserConfigurationException | TransformerException exception) { + throw new DataValidationException("Failed to parse XML data", "Invalid xml input " + exception.getMessage(), + exception); + } + } + + /** + * Add root node to XML content. + * + * @param xmlContent XML content to add root node into + * @param rootNodeTagName Root node tag name + * @return XML content with root node tag added (if needed) + */ + public static String addRootNodeToXmlContent(final String xmlContent, final String rootNodeTagName, + final String namespace) { + return addRootNodeToXmlContent(xmlContent, rootNodeTagName, namespace, new HashMap()); + } + + /** + * Add root node into DOM element. + * + * @param node DOM element to add root node into + * @param tagName Root tag name to add + * @return DOM element with a root node + */ + static Document addDataRootNode(final Element node, final String tagName, final String namespace, + final HashMap rootNodeProperty) { + try { + final DocumentBuilder docBuilder = dbFactory.newDocumentBuilder(); + final Document document = docBuilder.newDocument(); + final Element rootElement = document.createElementNS(namespace, tagName); + for (final Map.Entry entry : rootNodeProperty.entrySet()) { + final Element propertyElement = document.createElement(entry.getKey()); + propertyElement.setTextContent(entry.getValue()); + rootElement.appendChild(propertyElement); + } + rootElement.appendChild(document.adoptNode(node)); + document.appendChild(rootElement); + return document; + } catch (final ParserConfigurationException exception) { + throw new DataValidationException("Can't parse XML", "XML can't be parsed", exception); + } + } +} diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java index 9a61579b1..49d3a70f9 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java @@ -4,6 +4,7 @@ * Modifications Copyright (C) 2021 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,13 +28,20 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.StringReader; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -41,13 +49,18 @@ import org.onap.cps.spi.exceptions.DataValidationException; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory; import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier; import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream; +import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; @@ -55,6 +68,7 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; +import org.xml.sax.SAXException; @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -64,7 +78,45 @@ public class YangUtils { private static final String XPATH_NODE_KEY_ATTRIBUTES_REGEX = "\\[.*?\\]"; /** - * Parses jsonData into Collection of NormalizedNode according to given schema context. + * Parses data into Collection of NormalizedNode according to given schema context. + * + * @param nodeData data string + * @param schemaContext schema context describing associated data model + * @return the NormalizedNode object + */ + public static ContainerNode parseData(final ContentType contentType, final String nodeData, + final SchemaContext schemaContext) { + if (contentType == ContentType.JSON) { + return parseJsonData(nodeData, schemaContext, Optional.empty()); + } + return parseXmlData(XmlFileUtils.prepareXmlContent(nodeData, schemaContext), schemaContext, + Optional.empty()); + } + + /** + * Parses data into NormalizedNode according to given schema context. + * + * @param nodeData data string + * @param schemaContext schema context describing associated data model + * @return the NormalizedNode object + */ + public static ContainerNode parseData(final ContentType contentType, final String nodeData, + final SchemaContext schemaContext, final String parentNodeXpath) { + final DataSchemaNode parentSchemaNode = + (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNode"); + final Collection dataSchemaNodeIdentifiers = + (Collection) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNodeIdentifiers"); + if (contentType == ContentType.JSON) { + return parseJsonData(nodeData, schemaContext, Optional.of(dataSchemaNodeIdentifiers)); + } + return parseXmlData(XmlFileUtils.prepareXmlContent(nodeData, parentSchemaNode, parentNodeXpath), schemaContext, + Optional.of(dataSchemaNodeIdentifiers)); + } + + /** + * Parses data into Collection of NormalizedNode according to given schema context. * * @param jsonData json data as string * @param schemaContext schema context describing associated data model @@ -85,7 +137,8 @@ public class YangUtils { public static ContainerNode parseJsonData(final String jsonData, final SchemaContext schemaContext, final String parentNodeXpath) { final Collection dataSchemaNodeIdentifiers = - getDataSchemaNodeIdentifiersByXpath(parentNodeXpath, schemaContext); + (Collection) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNodeIdentifiers"); return parseJsonData(jsonData, schemaContext, Optional.of(dataSchemaNodeIdentifiers)); } @@ -105,7 +158,7 @@ public class YangUtils { final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext); final EffectiveStatementInference effectiveStatementInference = SchemaInferenceStack.of(effectiveModelContext, - SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers.get())).toInference(); + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers.get())).toInference(); jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference); } else { @@ -115,22 +168,56 @@ public class YangUtils { try { jsonParserStream.parse(jsonReader); jsonParserStream.close(); - } catch (final JsonSyntaxException exception) { + } catch (final IOException | JsonSyntaxException exception) { throw new DataValidationException( - "Failed to parse json data: " + jsonData, exception.getMessage(), exception); - } catch (final IOException | IllegalStateException illegalStateException) { + "Failed to parse json data: " + jsonData, exception.getMessage(), exception); + } catch (final IllegalStateException | IllegalArgumentException exception) { throw new DataValidationException( - "Failed to parse json data. Unsupported xpath or json data:" + jsonData, illegalStateException - .getMessage(), illegalStateException); + "Failed to parse json data. Unsupported xpath or json data:" + jsonData, exception + .getMessage(), exception); } return dataContainerNodeBuilder.build(); } + private static ContainerNode parseXmlData(final String xmlData, final SchemaContext schemaContext, + final Optional> dataSchemaNodeIdentifiers) { + final XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter + .from(normalizedNodeResult); + + final XmlParserStream xmlParser; + final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext); + + if (dataSchemaNodeIdentifiers.isPresent()) { + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers.get())).toInference(); + xmlParser = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference); + } else { + xmlParser = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext); + } + + try { + final XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xmlData)); + xmlParser.parse(reader); + xmlParser.close(); + } catch (final XMLStreamException | URISyntaxException | IOException + | SAXException | NullPointerException exception) { + throw new DataValidationException( + "Failed to parse xml data: " + xmlData, exception.getMessage(), exception); + } + final NormalizedNode normalizedNode = getFirstChildXmlRoot(normalizedNodeResult.getResult()); + return Builders.containerBuilder().withChild((DataContainerChild) normalizedNode) + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(schemaContext.getQName())).build(); + } + /** * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument). * * @param nodeIdentifier the NodeIdentifier - * @return an xpath + * @return a xpath */ public static String buildXpath(final YangInstanceIdentifier.PathArgument nodeIdentifier) { final StringBuilder xpathBuilder = new StringBuilder(); @@ -138,20 +225,20 @@ public class YangUtils { if (nodeIdentifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) { xpathBuilder.append(getKeyAttributesStatement( - (YangInstanceIdentifier.NodeIdentifierWithPredicates) nodeIdentifier)); + (YangInstanceIdentifier.NodeIdentifierWithPredicates) nodeIdentifier)); } return xpathBuilder.toString(); } private static String getKeyAttributesStatement( - final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) { + final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) { final List keyAttributes = nodeIdentifier.entrySet().stream().map( - entry -> { - final String name = entry.getKey().getLocalName(); - final String value = String.valueOf(entry.getValue()).replace("'", "\\'"); - return String.format("@%s='%s'", name, value); - } + entry -> { + final String name = entry.getKey().getLocalName(); + final String value = String.valueOf(entry.getValue()).replace("'", "\\'"); + return String.format("@%s='%s'", name, value); + } ).collect(Collectors.toList()); if (keyAttributes.isEmpty()) { @@ -162,10 +249,10 @@ public class YangUtils { } } - private static Collection getDataSchemaNodeIdentifiersByXpath(final String parentNodeXpath, - final SchemaContext schemaContext) { + private static Map getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath, + final SchemaContext schemaContext) { final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); - return findDataSchemaNodeIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), + return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), new ArrayList<>()); } @@ -181,7 +268,7 @@ public class YangUtils { return xpathNodeIdSequence; } - private static Collection findDataSchemaNodeIdentifiersByXpathNodeIdSequence( + private static Map findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( final String[] xpathNodeIdSequence, final Collection dataSchemaNodes, final Collection dataSchemaNodeIdentifiers) { @@ -191,11 +278,15 @@ public class YangUtils { .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId)); dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName()); if (xpathNodeIdSequence.length <= 1) { - return dataSchemaNodeIdentifiers; + final Map dataSchemaNodeAndIdentifiers = + new HashMap<>(); + dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode); + dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers); + return dataSchemaNodeAndIdentifiers; } if (currentDataSchemaNode instanceof DataNodeContainer) { - return findDataSchemaNodeIdentifiersByXpathNodeIdSequence( - getNextLevelXpathNodeIdSequence(xpathNodeIdSequence), + return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( + getNextLevelXpathNodeIdSequence(xpathNodeIdSequence), ((DataNodeContainer) currentDataSchemaNode).getChildNodes(), dataSchemaNodeIdentifiers); } @@ -212,4 +303,19 @@ public class YangUtils { return new DataValidationException("Invalid xpath.", String.format("No schema node was found for xpath identifier '%s'.", schemaNodeIdentifier)); } -} + + private static NormalizedNode getFirstChildXmlRoot(final NormalizedNode parent) { + final String rootNodeType = parent.getIdentifier().getNodeType().getLocalName(); + final Collection children = (Collection) parent.body(); + final Iterator iterator = children.iterator(); + NormalizedNode child = null; + while (iterator.hasNext()) { + child = iterator.next(); + if (!child.getIdentifier().getNodeType().getLocalName().equals(rootNodeType) + && !(child instanceof LeafNode)) { + return child; + } + } + return getFirstChildXmlRoot(child); + } +} \ No newline at end of file -- cgit 1.2.3-korg