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=b5c14a0721cf1d540e28550e0cc663ac61254681;hb=657a97124f0890f6bb5fec61a37978a6aa00a29d;hp=8077ed7d88618d0e52acbd08cedc29a9fb92396c;hpb=9afc8d1448a6a913db56304d3bc80cd92c141d0f;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 8077ed7d8..b5c14a072 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,6 +1,8 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020 Nordix Foundation + * Copyright (C) 2020-2021 Nordix Foundation + * Modifications Copyright (C) 2021 Bell Canada. All rights reserved. + * Modifications Copyright (C) 2021 Pantheon.tech * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,142 +21,109 @@ package org.onap.cps.utils; +import com.google.gson.JsonSyntaxException; import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.StringReader; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.impl.Fragment; -import org.opendaylight.yangtools.yang.common.QName; +import org.onap.cps.spi.exceptions.DataValidationException; 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.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.ValueNode; -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.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.SchemaContext; @Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class YangUtils { - private YangUtils() { - throw new IllegalStateException("Utility class"); - } + private static final String XPATH_DELIMITER_REGEX = "\\/"; + private static final String XPATH_NODE_KEY_ATTRIBUTES_REGEX = "\\[.+"; /** - * Parse a string containing json data for a certain model (schemaContext). + * Parses jsonData 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 jsonData json data as 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); - } - return normalizedNodeResult.getResult(); + @SuppressWarnings("squid:S1452") // Generic type is returned by external librray, opendaylight.yangtools + public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext) { + return parseJsonData(jsonData, schemaContext, Optional.empty()); } /** - * Break a Normalized Node tree into fragments that can be stored by the persistence service. + * Parses jsonData into 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 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 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; - } - - 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 { - log.warn("Cannot normalize {}", normalizedNode.getClass()); - } - } - - private static void inspectLeaf(final Fragment currentFragment, - final ValueNode valueNode) { - final Object value = valueNode.getValue(); - currentFragment.addLeafValue(valueNode.getNodeType().getLocalName(), value); + @SuppressWarnings("squid:S1452") // Generic type is returned by external librray, opendaylight.yangtools + public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext, + final String parentNodeXpath) { + final var parentSchemaNode = getDataSchemaNodeByXpath(parentNodeXpath, schemaContext); + return parseJsonData(jsonData, schemaContext, Optional.of(parentSchemaNode)); } - 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); - } - } - - 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 void inspectKeyedList(final Fragment currentFragment, - final MapNode mapNode) { - createNodeForEachListElement(currentFragment, mapNode); - } + private static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext, + final Optional optionalParentSchemaNode) { + final var jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 + .getShared(schemaContext); + final var normalizedNodeResult = new NormalizedNodeResult(); + final var normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter + .from(normalizedNodeResult); + + try (final JsonParserStream jsonParserStream = optionalParentSchemaNode.isPresent() + ? JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, optionalParentSchemaNode.get()) + : JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory) + ) { + final var jsonReader = new JsonReader(new StringReader(jsonData)); + jsonParserStream.parse(jsonReader); - 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); + } catch (final IOException | JsonSyntaxException exception) { + throw new DataValidationException( + "Failed to parse json data: " + jsonData, exception.getMessage(), exception); + } catch (final IllegalStateException illegalStateException) { + throw new DataValidationException( + "Failed to parse json data. Unsupported xpath or json data:" + jsonData, illegalStateException + .getMessage(), illegalStateException); } + return normalizedNodeResult.getResult(); } - private static String buildXpathId(final YangInstanceIdentifier.PathArgument nodeIdentifier) { - final StringBuilder xpathIdBuilder = new StringBuilder(); - xpathIdBuilder.append("/").append(nodeIdentifier.getNodeType().getLocalName()); + /** + * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument). + * + * @param nodeIdentifier the NodeIdentifier + * @return an xpath + */ + public static String buildXpath(final YangInstanceIdentifier.PathArgument nodeIdentifier) { + final var xpathBuilder = new StringBuilder(); + xpathBuilder.append("/").append(nodeIdentifier.getNodeType().getLocalName()); - if (nodeIdentifier instanceof NodeIdentifierWithPredicates) { - xpathIdBuilder.append(getKeyAttributesStatement((NodeIdentifierWithPredicates) nodeIdentifier)); + if (nodeIdentifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) { + xpathBuilder.append(getKeyAttributesStatement( + (YangInstanceIdentifier.NodeIdentifierWithPredicates) nodeIdentifier)); } - return xpathIdBuilder.toString(); + return xpathBuilder.toString(); } - 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(); @@ -170,4 +139,49 @@ public class YangUtils { return "[" + String.join(" and ", keyAttributes) + "]"; } } + + private static DataSchemaNode getDataSchemaNodeByXpath(final String parentNodeXpath, + final SchemaContext schemaContext) { + final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath); + return findDataSchemaNodeByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes()); + } + + private static String[] xpathToNodeIdSequence(final String xpath) { + final String[] xpathNodeIdSequence = Arrays.stream(xpath.split(XPATH_DELIMITER_REGEX)) + .map(identifier -> identifier.replaceFirst(XPATH_NODE_KEY_ATTRIBUTES_REGEX, "")) + .filter(identifier -> !identifier.isEmpty()) + .toArray(String[]::new); + if (xpathNodeIdSequence.length < 1) { + throw new DataValidationException("Invalid xpath.", "Xpath contains no node identifiers."); + } + return xpathNodeIdSequence; + } + + private static DataSchemaNode findDataSchemaNodeByXpathNodeIdSequence(final String[] xpathNodeIdSequence, + final Collection dataSchemaNodes) { + final String currentXpathNodeId = xpathNodeIdSequence[0]; + final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream() + .filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName())) + .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId)); + if (xpathNodeIdSequence.length <= 1) { + return currentDataSchemaNode; + } + if (currentDataSchemaNode instanceof DataNodeContainer) { + return findDataSchemaNodeByXpathNodeIdSequence( + getNextLevelXpathNodeIdSequence(xpathNodeIdSequence), + ((DataNodeContainer) currentDataSchemaNode).getChildNodes()); + } + throw schemaNodeNotFoundException(xpathNodeIdSequence[1]); + } + + private static String[] getNextLevelXpathNodeIdSequence(final String[] xpathNodeIdSequence) { + final var 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)); + } }