diff options
author | ToineSiebelink <toine.siebelink@est.tech> | 2024-01-16 08:40:51 +0000 |
---|---|---|
committer | ToineSiebelink <toine.siebelink@est.tech> | 2024-01-17 14:56:53 +0000 |
commit | 6229cfeafade160ed281fc410454c7498b8a21dc (patch) | |
tree | 714247ebb871c00fce2b60fd5f0dcbdcebf1a233 /cps-service/src/main | |
parent | 2dce7ab2fd627450550666e3993e28e10f6a8548 (diff) |
Clear instance based Schema Context Cache upon validation errors
- retry yang parser exceptions one time by clearing cache
- split yangUtils into YangParserHelper (instance based) and YangUtils for non-parsering static methods
- removed public methods only used from test. Refactored to use variation with (empty) parent path
- removed use of optional for parentPath, easier to handle with just an empty string!
- make methods no longer used in production code in YangUtils are private
- udpate testware to use proper public methods instead of private methods of yang utils / parser (helper)
Issue-ID:CPS-2000
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Change-Id: I0c7590a5e1495d047006e7136f1bd873be37f7b0
Diffstat (limited to 'cps-service/src/main')
6 files changed, 330 insertions, 339 deletions
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 e49714b66d..b3f42279df 100644 --- 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 @@ -48,9 +48,8 @@ import org.onap.cps.spi.model.DataNodeBuilder; import org.onap.cps.spi.model.DeltaReport; import org.onap.cps.spi.utils.CpsValidator; import org.onap.cps.utils.ContentType; -import org.onap.cps.utils.TimedYangParser; +import org.onap.cps.utils.YangParser; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.springframework.stereotype.Service; @Service @@ -63,9 +62,8 @@ public class CpsDataServiceImpl implements CpsDataService { private final CpsDataPersistenceService cpsDataPersistenceService; private final CpsAnchorService cpsAnchorService; - private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; private final CpsValidator cpsValidator; - private final TimedYangParser timedYangParser; + private final YangParser yangParser; private final CpsDeltaService cpsDeltaService; @Override @@ -309,10 +307,9 @@ public class CpsDataServiceImpl implements CpsDataService { private Collection<DataNode> buildDataNodes(final Anchor anchor, final String parentNodeXpath, final String nodeData, final ContentType contentType) { - final SchemaContext schemaContext = getSchemaContext(anchor); if (ROOT_NODE_XPATH.equals(parentNodeXpath)) { - final ContainerNode containerNode = timedYangParser.parseData(contentType, nodeData, schemaContext); + final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, ""); final Collection<DataNode> dataNodes = new DataNodeBuilder() .withContainerNode(containerNode) .buildCollection(); @@ -323,7 +320,7 @@ public class CpsDataServiceImpl implements CpsDataService { } final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath); final ContainerNode containerNode = - timedYangParser.parseData(contentType, nodeData, schemaContext, normalizedParentNodeXpath); + yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath); final Collection<DataNode> dataNodes = new DataNodeBuilder() .withParentNodeXpath(normalizedParentNodeXpath) .withContainerNode(containerNode) @@ -334,10 +331,6 @@ public class CpsDataServiceImpl implements CpsDataService { return dataNodes; } - private SchemaContext getSchemaContext(final Anchor anchor) { - return yangTextSchemaSourceSetCache - .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext(); - } private static boolean isRootNodeXpath(final String xpath) { return ROOT_NODE_XPATH.equals(xpath); diff --git a/cps-service/src/main/java/org/onap/cps/utils/TimedYangParser.java b/cps-service/src/main/java/org/onap/cps/utils/TimedYangParser.java deleted file mode 100644 index 4640593dc5..0000000000 --- a/cps-service/src/main/java/org/onap/cps/utils/TimedYangParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. - * ================================================================================ - * 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 io.micrometer.core.annotation.Timed; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.springframework.stereotype.Service; - -@Service -public class TimedYangParser { - - /** - * 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 - */ - @Timed(value = "cps.utils.yangparser.nodedata.parse", - description = "Time taken to parse node data without a parent") - public ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext) { - return YangUtils.parseData(contentType, nodeData, schemaContext); - } - - /** - * 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 - */ - @Timed(value = "cps.utils.yangparser.nodedata.with.parent.parse", - description = "Time taken to parse node data with a parent") - public ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext, - final String parentNodeXpath) { - return YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath); - } -} 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 98c7947e1c..7a6d0bb3d5 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 @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Deutsche Telekom AG - * Modifications Copyright (C) 2023 Nordix Foundation. + * Modifications Copyright (C) 2023-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ public class XmlFileUtils { public static String prepareXmlContent(final String xmlContent, final SchemaContext schemaContext) throws IOException, ParserConfigurationException, TransformerException, SAXException { return addRootNodeToXmlContent(xmlContent, schemaContext.getModules().iterator().next().getName(), - YangUtils.DATA_ROOT_NODE_NAMESPACE); + YangParserHelper.DATA_ROOT_NODE_NAMESPACE); } /** @@ -106,7 +106,7 @@ public class XmlFileUtils { documentBuilder.parse(new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8))); final Element root = document.getDocumentElement(); if (!root.getTagName().equals(rootNodeTagName) - && !root.getTagName().equals(YangUtils.DATA_ROOT_NODE_TAG_NAME)) { + && !root.getTagName().equals(YangParserHelper.DATA_ROOT_NODE_TAG_NAME)) { final Document documentWithRootNode = addDataRootNode(root, rootNodeTagName, namespace, rootNodeProperty); documentWithRootNode.setXmlStandalone(true); final Transformer transformer = getTransformerFactory().newTransformer(); diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParser.java b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java new file mode 100644 index 0000000000..6299ef39f4 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParser.java @@ -0,0 +1,70 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation. + * ================================================================================ + * 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 io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import org.onap.cps.api.impl.YangTextSchemaSourceSetCache; +import org.onap.cps.spi.exceptions.DataValidationException; +import org.onap.cps.spi.model.Anchor; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class YangParser { + + private final YangParserHelper yangParserHelper; + private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; + + /** + * Parses data into (normalized) ContainerNode according to schema context for the given anchor. + * + * @param nodeData data string + * @param anchor the anchor for the node data + * @return the NormalizedNode object + */ + @Timed(value = "cps.utils.yangparser.nodedata.with.parent.parse", + description = "Time taken to parse node data with a parent") + public ContainerNode parseData(final ContentType contentType, + final String nodeData, + final Anchor anchor, + final String parentNodeXpath) { + final SchemaContext schemaContext = getSchemaContext(anchor); + try { + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + } catch (final DataValidationException e) { + invalidateCache(anchor); + } + return yangParserHelper.parseData(contentType, nodeData, schemaContext, parentNodeXpath); + } + + private SchemaContext getSchemaContext(final Anchor anchor) { + return yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), + anchor.getSchemaSetName()).getSchemaContext(); + } + + private void invalidateCache(final Anchor anchor) { + yangTextSchemaSourceSetCache.removeFromCache(anchor.getDataspaceName(), anchor.getSchemaSetName()); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java new file mode 100644 index 0000000000..5cadd29368 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/YangParserHelper.java @@ -0,0 +1,252 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * 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 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.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +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.RequiredArgsConstructor; +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.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; +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.springframework.stereotype.Service; +import org.xml.sax.SAXException; + +@Service +@RequiredArgsConstructor +public class YangParserHelper { + + static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0"; + static final String DATA_ROOT_NODE_TAG_NAME = "data"; + + /** + * Parses data into NormalizedNode according to given schema context. + * + * @param contentType the type of the node data (json or xml) + * @param nodeData data 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 ContainerNode parseData(final ContentType contentType, + final String nodeData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + if (contentType == ContentType.JSON) { + return parseJsonData(nodeData, schemaContext, parentNodeXpath); + } + return parseXmlData(nodeData, schemaContext, parentNodeXpath); + } + + private ContainerNode parseJsonData(final String jsonData, + final SchemaContext schemaContext, + final String parentNodeXpath) { + final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 + .getShared((EffectiveModelContext) schemaContext); + final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> 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; + + if (parentNodeXpath.isEmpty()) { + jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory); + } else { + final Collection<QName> dataSchemaNodeIdentifiers + = getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath); + final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext); + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); + jsonParserStream = + JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference); + } + + 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 ContainerNode parseXmlData(final String xmlData, + final SchemaContext schemaContext, + final String 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.isEmpty()) { + preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext); + xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext); + } else { + final DataSchemaNode parentSchemaNode = + (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNode"); + final Collection<QName> dataSchemaNodeIdentifiers = + getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath); + final EffectiveStatementInference effectiveStatementInference = + SchemaInferenceStack.of(effectiveModelContext, + SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference(); + preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, parentNodeXpath); + xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference); + } + + 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); + } + 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<QName> getDataSchemaNodeIdentifiers(final SchemaContext schemaContext, + final String parentNodeXpath) { + return (Collection<QName>) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) + .get("dataSchemaNodeIdentifiers"); + } + + private static Map<String, Object> 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<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( + final String[] xpathNodeIdSequence, + final Collection<? extends DataSchemaNode> dataSchemaNodes, + final Collection<QName> 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<String, Object> 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<DataContainerChild> children = (Collection<DataContainerChild>) parent.body(); + final Iterator<DataContainerChild> 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); + } +} 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 f00f9442ce..96841bf5c8 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,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020-2023 Nordix Foundation + * Copyright (C) 2020-2024 Nordix Foundation * Modifications Copyright (C) 2021 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -24,120 +24,16 @@ 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.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.Map; -import java.util.Optional; import java.util.stream.Collectors; -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.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; -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) public class YangUtils { - 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"; - - /** - * 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 - */ - static ContainerNode parseData(final ContentType contentType, - final String nodeData, - final SchemaContext schemaContext) { - if (contentType == ContentType.JSON) { - return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.empty()); - } - return parseXmlDataWithOptionalParent(nodeData, 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 - */ - 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 parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath)); - } - - /** - * 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 - * @return the Collection of NormalizedNode object - */ - public static ContainerNode parseJsonData(final String jsonData, final SchemaContext schemaContext) { - return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.empty()); - } - - /** - * 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)); - } - /** * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument). * @@ -155,99 +51,6 @@ public class YangUtils { return xpathBuilder.toString(); } - private static ContainerNode parseJsonDataWithOptionalParent(final String jsonData, - final SchemaContext schemaContext, - final Optional<String> parentNodeXpath) { - final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02 - .getShared((EffectiveModelContext) schemaContext); - final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> 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; - - if (parentNodeXpath.isPresent()) { - final Collection<QName> 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); - } - - 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 ContainerNode parseXmlDataWithOptionalParent(final String xmlData, - final SchemaContext schemaContext, - final Optional<String> 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<QName> 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); - } - - 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); - } - 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<QName> getDataSchemaNodeIdentifiers(final SchemaContext schemaContext, - final String parentNodeXpath) { - return (Collection<QName>) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext) - .get("dataSchemaNodeIdentifiers"); - } - private static String getKeyAttributesStatement( final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) { final List<String> keyAttributes = nodeIdentifier.entrySet().stream().map( @@ -266,70 +69,4 @@ public class YangUtils { } } - private static Map<String, Object> 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<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence( - final String[] xpathNodeIdSequence, - final Collection<? extends DataSchemaNode> dataSchemaNodes, - final Collection<QName> 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<String, Object> 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<DataContainerChild> children = (Collection<DataContainerChild>) parent.body(); - final Iterator<DataContainerChild> 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); - } } |