X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;f=cps-service%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Fcps%2Futils%2FYangUtils.java;h=f00f9442ce3d9218d73487040133a47ba0864a68;hb=0ba8fbf7aa8d30faad72ca20bfab142bdc1816da;hp=a35533c8f2de59958f7ef169f996520cbee519f4;hpb=966f3ab710bd1bebaa81e2394627df47a3f98909;p=cps.git 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 a35533c8f..f00f9442c 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java @@ -1,12 +1,17 @@ /* - * ============LICENSE_START======================================================= - * Copyright (C) 2020 Nordix Foundation + * ============LICENSE_START======================================================= + * Copyright (C) 2020-2023 Nordix Foundation + * 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. * 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. @@ -19,181 +24,238 @@ package org.onap.cps.utils; -import static com.google.common.base.Preconditions.checkArgument; -import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION; - -import com.google.common.base.Charsets; -import com.google.common.io.Files; +import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonReader; -import java.io.File; import java.io.IOException; import java.io.StringReader; +import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; -import java.util.logging.Logger; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; -import org.onap.cps.api.impl.Fragment; -import org.onap.cps.yang.YangTextSchemaSourceSet; -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.TransformerException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.cpspath.parser.CpsPathUtil; +import org.onap.cps.cpspath.parser.PathParsingException; +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.YangInstanceIdentifier.NodeIdentifierWithPredicates; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +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.ValueNode; +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.Module; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.parser.api.YangParserException; -import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; +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) public class YangUtils { - private static final Logger LOGGER = Logger.getLogger(YangUtils.class.getName()); - private YangUtils() { - throw new IllegalStateException("Utility class"); - } + public static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; + public static final String DATA_ROOT_NODE_TAG_NAME = "data"; /** - * Parse a file containing yang modules. + * Parses data into Collection of NormalizedNode according to given schema context. * - * @param yangModelFiles list of files containing one or more yang modules. The file has to have a .yang extension. - * @return a SchemaContext representing the yang model - * @throws IOException when the system as an IO issue - * @throws YangParserException when the file does not contain a valid yang structure + * @param nodeData data string + * @param schemaContext schema context describing associated data model + * @return the NormalizedNode object */ - @Deprecated - public static YangTextSchemaSourceSet parseYangModelFiles(final List yangModelFiles) - throws IOException, YangParserException, ReactorException { - final YangTextSchemaSourceSetBuilder yangModelsMapBuilder = new YangTextSchemaSourceSetBuilder(); - for (final File file :yangModelFiles) { - final String fileNameWithExtension = file.getName(); - checkArgument(fileNameWithExtension.endsWith(RFC6020_YANG_FILE_EXTENSION), - "Filename %s does not end with '%s'", RFC6020_YANG_FILE_EXTENSION, - fileNameWithExtension); - final String content = Files.asCharSource(file, Charsets.UTF_8).read(); - yangModelsMapBuilder.put(fileNameWithExtension, content); + static ContainerNode parseData(final ContentType contentType, + final String nodeData, + final SchemaContext schemaContext) { + if (contentType == ContentType.JSON) { + return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.empty()); } - return yangModelsMapBuilder.build(); + return parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.empty()); } /** - * Parse a file containing json data for a certain model (schemaContext). + * Parses data into NormalizedNode according to given schema context. * - * @param jsonData a string containing json data for the given model - * @param schemaContext the SchemaContext for the given data - * @return the NormalizedNode representing the json data + * @param nodeData data string + * @param schemaContext schema context describing associated data model + * @return the NormalizedNode object */ - public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext) - throws IOException { - final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 - .getShared(schemaContext); - final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult(); - final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter - .from(normalizedNodeResult); - try (final JsonParserStream jsonParserStream = JsonParserStream - .create(normalizedNodeStreamWriter, jsonCodecFactory)) { - final JsonReader jsonReader = new JsonReader(new StringReader(jsonData)); - jsonParserStream.parse(jsonReader); + static ContainerNode parseData(final ContentType contentType, + final String nodeData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + if (contentType == ContentType.JSON) { + return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath)); } - return normalizedNodeResult.getResult(); + return parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath)); } /** - * Break a Normalized Node tree into fragments that can be stored by the persistence service. + * Parses data into Collection of NormalizedNode according to given schema context. * - * @param tree the normalized node tree - * @param module the module applicable for the data in the normalized node - * @return the 'root' Fragment for the tree contain all relevant children etc. + * @param jsonData json data as string + * @param schemaContext schema context describing associated data model + * @return the Collection of NormalizedNode object */ - public static Fragment fragmentNormalizedNode( - final NormalizedNode tree, - final Module module) { - final QName[] nodeTypes = {tree.getNodeType()}; - final String xpath = buildXpathId(tree.getIdentifier()); - final Fragment rootFragment = Fragment.createRootFragment(module, nodeTypes, xpath); - fragmentNormalizedNode(rootFragment, tree); - return rootFragment; + public static ContainerNode parseJsonData(final String jsonData, final SchemaContext schemaContext) { + return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.empty()); } - private static void fragmentNormalizedNode(final Fragment currentFragment, - final NormalizedNode normalizedNode) { - if (normalizedNode instanceof DataContainerNode) { - inspectContainer(currentFragment, (DataContainerNode) normalizedNode); - } else if (normalizedNode instanceof MapNode) { - inspectKeyedList(currentFragment, (MapNode) normalizedNode); - } else if (normalizedNode instanceof ValueNode) { - inspectLeaf(currentFragment, (ValueNode) normalizedNode); - } else if (normalizedNode instanceof LeafSetNode) { - inspectLeafList(currentFragment, (LeafSetNode) normalizedNode); - } else { - LOGGER.warning("Cannot normalize " + normalizedNode.getClass()); - } + /** + * Parses jsonData into Collection of NormalizedNode according to given schema context. + * + * @param jsonData json data fragment as string + * @param schemaContext schema context describing associated data model + * @param parentNodeXpath the xpath referencing the parent node current data fragment belong to + * @return the NormalizedNode object + */ + public static ContainerNode parseJsonData(final String jsonData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.of(parentNodeXpath)); } - private static void inspectLeaf(final Fragment currentFragment, - final ValueNode valueNode) { - final Object value = valueNode.getValue(); - currentFragment.addLeafValue(valueNode.getNodeType().getLocalName(), value); - } + /** + * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument). + * + * @param nodeIdentifier the NodeIdentifier + * @return a xpath + */ + public static String buildXpath(final YangInstanceIdentifier.PathArgument nodeIdentifier) { + final StringBuilder xpathBuilder = new StringBuilder(); + xpathBuilder.append("/").append(nodeIdentifier.getNodeType().getLocalName()); - private static void inspectLeafList(final Fragment currentFragment, - final LeafSetNode leafSetNode) { - currentFragment.addLeafListName(leafSetNode.getNodeType().getLocalName()); - for (final NormalizedNode value : (Collection) leafSetNode.getValue()) { - fragmentNormalizedNode(currentFragment, value); + if (nodeIdentifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) { + xpathBuilder.append(getKeyAttributesStatement( + (YangInstanceIdentifier.NodeIdentifierWithPredicates) nodeIdentifier)); } + return xpathBuilder.toString(); } - private static void inspectContainer(final Fragment currentFragment, - final DataContainerNode dataContainerNode) { - final Collection leaves = (Collection) dataContainerNode.getValue(); - for (final NormalizedNode leaf : leaves) { - fragmentNormalizedNode(currentFragment, leaf); - } - } + private static ContainerNode parseJsonDataWithOptionalParent(final String jsonData, + final SchemaContext schemaContext, + final Optional parentNodeXpath) { + final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 + .getShared((EffectiveModelContext) schemaContext); + final DataContainerNodeBuilder dataContainerNodeBuilder = + Builders.containerBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier( + QName.create(DATA_ROOT_NODE_NAMESPACE, DATA_ROOT_NODE_TAG_NAME) + )); + final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter + .from(dataContainerNodeBuilder); + final JsonReader jsonReader = new JsonReader(new StringReader(jsonData)); + final JsonParserStream jsonParserStream; - private static void inspectKeyedList(final Fragment currentFragment, - final MapNode mapNode) { - createNodeForEachListElement(currentFragment, mapNode); - } + if (parentNodeXpath.isPresent()) { + final Collection dataSchemaNodeIdentifiers + = getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath.get()); + final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext); + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); + jsonParserStream = + JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference); + } else { + jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory); + } - private static void createNodeForEachListElement(final Fragment currentFragment, final MapNode mapNode) { - final Collection mapEntryNodes = mapNode.getValue(); - for (final MapEntryNode mapEntryNode : mapEntryNodes) { - final String xpathId = buildXpathId(mapEntryNode.getIdentifier()); - final Fragment listElementFragment = - currentFragment.createChildFragment(mapNode.getNodeType(), xpathId); - fragmentNormalizedNode(listElementFragment, mapEntryNode); + try (jsonParserStream) { + jsonParserStream.parse(jsonReader); + } catch (final IOException | JsonSyntaxException exception) { + throw new DataValidationException( + "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, exception + .getMessage(), exception); } + return dataContainerNodeBuilder.build(); } - private static String buildXpathId(final YangInstanceIdentifier.PathArgument nodeIdentifier) { - final StringBuilder xpathIdBuilder = new StringBuilder(); - xpathIdBuilder.append("/").append(nodeIdentifier.getNodeType().getLocalName()); + private static ContainerNode parseXmlDataWithOptionalParent(final String xmlData, + final SchemaContext schemaContext, + final Optional parentNodeXpath) { + final XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); + final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter + .from(normalizedNodeResult); + + final EffectiveModelContext effectiveModelContext = (EffectiveModelContext) schemaContext; + final XmlParserStream xmlParserStream; + final String preparedXmlContent; + try { + if (parentNodeXpath.isPresent()) { + final DataSchemaNode parentSchemaNode = + (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath.get(), schemaContext) + .get("dataSchemaNode"); + final Collection dataSchemaNodeIdentifiers = + getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath.get()); + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); + preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, parentNodeXpath.get()); + xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference); + } else { + preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext); + xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext); + } - if (nodeIdentifier instanceof NodeIdentifierWithPredicates) { - xpathIdBuilder.append(getKeyAttributesStatement((NodeIdentifierWithPredicates) nodeIdentifier)); + try (xmlParserStream; + StringReader stringReader = new StringReader(preparedXmlContent)) { + final XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(stringReader); + xmlParserStream.parse(xmlStreamReader); + } + } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException + | ParserConfigurationException | TransformerException exception) { + throw new DataValidationException( + "Failed to parse xml data: " + xmlData, exception.getMessage(), exception); } - return xpathIdBuilder.toString(); + final DataContainerChild dataContainerChild = + (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult()); + final YangInstanceIdentifier.NodeIdentifier nodeIdentifier = + new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType()); + return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build(); + } + + private static Collection getDataSchemaNodeIdentifiers(final SchemaContext schemaContext, + final String parentNodeXpath) { + return (Collection) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNodeIdentifiers"); } - private static String getKeyAttributesStatement(final NodeIdentifierWithPredicates nodeIdentifier) { + private static String getKeyAttributesStatement( + 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()) { @@ -203,4 +265,71 @@ public class YangUtils { return "[" + String.join(" and ", keyAttributes) + "]"; } } + + private static Map getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath, + final SchemaContext schemaContext) { + final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); + return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(), + new ArrayList<>()); + } + + private static String[] xpathToNodeIdSequence(final String xpath) { + try { + return CpsPathUtil.getXpathNodeIdSequence(xpath); + } catch (final PathParsingException pathParsingException) { + throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), + pathParsingException); + } + } + + private static Map findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( + final String[] xpathNodeIdSequence, + final Collection dataSchemaNodes, + final Collection dataSchemaNodeIdentifiers) { + final String currentXpathNodeId = xpathNodeIdSequence[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 dataSchemaNodeAndIdentifiers = + new HashMap<>(); + dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode); + dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers); + return dataSchemaNodeAndIdentifiers; + } + if (currentDataSchemaNode instanceof DataNodeContainer) { + return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( + getNextLevelXpathNodeIdSequence(xpathNodeIdSequence), + ((DataNodeContainer) currentDataSchemaNode).getChildNodes(), + dataSchemaNodeIdentifiers); + } + throw schemaNodeNotFoundException(xpathNodeIdSequence[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 DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) { + 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); + } }