2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.client.mdsal.impl;
10 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_CONFIG_NODEID;
11 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_DATA_NODEID;
12 import static org.opendaylight.netconf.common.mdsal.NormalizedDataUtil.appendListKeyNodes;
13 import static org.opendaylight.netconf.common.mdsal.NormalizedDataUtil.writeSchemalessFilter;
15 import java.util.List;
16 import java.util.Optional;
17 import javax.xml.transform.dom.DOMSource;
18 import org.opendaylight.netconf.api.DocumentedException;
19 import org.opendaylight.netconf.api.EffectiveOperation;
20 import org.opendaylight.netconf.api.NamespaceURN;
21 import org.opendaylight.netconf.api.xml.XmlElement;
22 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
23 import org.opendaylight.netconf.api.xml.XmlUtil;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.get.input.Filter;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
34 import org.w3c.dom.Document;
35 import org.w3c.dom.Element;
36 import org.w3c.dom.Node;
39 * Transforms rpc structures to anyxml and vice versa. Provides support for devices, which don't expose their schema.
41 class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
44 * Selects elements in anyxml data node, which matches path arguments QNames. Since class in not context aware,
45 * method searches for all elements as they are named in path.
46 * @param data data, must be of type {@link DOMSourceAnyxmlNode}
47 * @param path path to select
48 * @return selected data
51 public Optional<NormalizedNode> selectFromDataStructure(final DataContainerChild data,
52 final YangInstanceIdentifier path) {
53 if (!(data instanceof DOMSourceAnyxmlNode anyxml)) {
54 throw new IllegalArgumentException("Unexpected data " + data.prettyTree());
57 final var result = XmlUtil.newDocument();
58 final var dataElement = result.createElementNS(NamespaceURN.BASE, "data");
59 result.appendChild(dataElement);
60 for (var xmlElement : selectMatchingNodes(getSourceElement(anyxml.body()), path)) {
61 dataElement.appendChild(result.importNode(xmlElement.getDomElement(), true));
63 return Optional.of(Builders.anyXmlBuilder()
64 .withNodeIdentifier(NETCONF_DATA_NODEID)
65 .withValue(new DOMSource(result))
70 * This class in not context aware. All elements are present in resulting structure, which are present in data path.
71 * @see RpcStructureTransformer#createEditConfigStructure(Optional, YangInstanceIdentifier, Optional)
73 * @param dataPath path, where data will be written
74 * @param operation operation
75 * @return config structure
78 public AnyxmlNode<DOMSource> createEditConfigStructure(final Optional<NormalizedNode> data,
79 final YangInstanceIdentifier dataPath, final Optional<EffectiveOperation> operation) {
80 final var dataValue = data.orElseThrow();
81 if (!(dataValue instanceof DOMSourceAnyxmlNode anxmlData)) {
82 throw new IllegalArgumentException("Unexpected data " + dataValue.prettyTree());
85 final var document = XmlUtil.newDocument();
86 final var dataNode = (Element) document.importNode(getSourceElement(anxmlData.body()), true);
87 checkDataValidForPath(dataPath, dataNode);
89 final var configElement = document.createElementNS(NamespaceURN.BASE, XmlNetconfConstants.CONFIG_KEY);
90 document.appendChild(configElement);
92 final Element parentXmlStructure;
93 if (dataPath.isEmpty()) {
94 parentXmlStructure = dataNode;
95 configElement.appendChild(parentXmlStructure);
97 final var pathArguments = dataPath.getPathArguments();
98 // last will be appended later
99 parentXmlStructure = instanceIdToXmlStructure(pathArguments.subList(0, pathArguments.size() - 1),
102 operation.ifPresent(modifyAction -> setOperationAttribute(modifyAction, document, dataNode));
104 parentXmlStructure.appendChild(document.importNode(dataNode, true));
105 return Builders.anyXmlBuilder()
106 .withNodeIdentifier(NETCONF_CONFIG_NODEID)
107 .withValue(new DOMSource(document.getDocumentElement()))
112 * This class in not context aware. All elements are present in resulting structure, which are present in data path.
113 * @see RpcStructureTransformer#toFilterStructure(YangInstanceIdentifier)
115 * @return filter structure
118 public AnyxmlNode<?> toFilterStructure(final YangInstanceIdentifier path) {
119 final var document = XmlUtil.newDocument();
120 instanceIdToXmlStructure(path.getPathArguments(), prepareFilterElement(document));
121 return buildFilterXmlNode(document);
125 public AnyxmlNode<?> toFilterStructure(final List<FieldsFilter> fieldsFilters) {
126 final var document = XmlUtil.newDocument();
127 final var filterElement = prepareFilterElement(document);
128 for (var filter : fieldsFilters) {
129 writeSchemalessFilter(filter.path(), filter.fields(), filterElement);
131 return buildFilterXmlNode(document);
134 private static Element prepareFilterElement(final Document document) {
135 final var filter = document.createElementNS(NamespaceURN.BASE, "filter");
136 filter.setAttribute("type", "subtree");
137 document.appendChild(filter);
141 private static AnyxmlNode<?> buildFilterXmlNode(final Document document) {
142 return Builders.anyXmlBuilder()
143 .withNodeIdentifier(new NodeIdentifier(Filter.QNAME))
144 .withValue(new DOMSource(document.getDocumentElement()))
148 private static void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
149 //if datapath is empty, consider dataNode to be a root node
150 if (dataPath.isEmpty()) {
153 final var dataElement = XmlElement.fromDomElement(dataNode);
154 final var lastPathArgument = dataPath.getLastPathArgument();
155 final var nodeType = lastPathArgument.getNodeType();
156 if (!nodeType.getNamespace().toString().equals(dataNode.getNamespaceURI())
157 || !nodeType.getLocalName().equals(dataElement.getName())) {
158 throw new IllegalStateException(
159 String.format("Can't write data '%s' to path %s", dataNode.getTagName(), dataPath));
161 if (lastPathArgument instanceof NodeIdentifierWithPredicates) {
162 checkKeyValuesValidForPath(dataElement, lastPathArgument);
166 private static void checkKeyValuesValidForPath(final XmlElement dataElement, final PathArgument lastPathArgument) {
167 for (var entry : ((NodeIdentifierWithPredicates) lastPathArgument).entrySet()) {
168 final var qname = entry.getKey();
169 final var key = dataElement.getChildElementsWithinNamespace(qname.getLocalName(),
170 qname.getNamespace().toString());
172 throw new IllegalStateException("No key present in xml");
174 if (key.size() > 1) {
175 throw new IllegalStateException("Multiple values for same key present");
177 final String textContent;
179 textContent = key.get(0).getTextContent();
180 } catch (DocumentedException e) {
181 throw new IllegalStateException("Key value not present in key element", e);
183 if (!entry.getValue().equals(textContent)) {
184 throw new IllegalStateException("Key value in path not equal to key value in xml");
189 private static void setOperationAttribute(final EffectiveOperation operation, final Document document,
190 final Element dataNode) {
191 final var operationAttribute = document.createAttributeNS(NamespaceURN.BASE,
192 XmlNetconfConstants.OPERATION_ATTR_KEY);
193 operationAttribute.setTextContent(operation.xmlValue());
194 dataNode.setAttributeNode(operationAttribute);
197 private static Element instanceIdToXmlStructure(final List<PathArgument> pathArguments, final Element data) {
198 final var doc = data.getOwnerDocument();
200 for (var pathArgument : pathArguments) {
201 final var nodeType = pathArgument.getNodeType();
202 final var element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
203 parent.appendChild(element);
204 //if path argument is list id, add also keys to resulting xml
205 if (pathArgument instanceof NodeIdentifierWithPredicates nip) {
206 appendListKeyNodes(element, nip);
213 private static List<XmlElement> selectMatchingNodes(final Element domElement, final YangInstanceIdentifier path) {
214 var element = XmlElement.fromDomElement(domElement);
215 for (var pathArgument : path.getPathArguments()) {
216 var childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
217 if (childElements.size() == 1) {
218 element = childElements.get(0);
220 return childElements;
223 return List.of(element);
226 private static Element getSourceElement(final DOMSource source) {
227 final var node = source.getNode();
228 return switch (node.getNodeType()) {
229 case Node.DOCUMENT_NODE -> ((Document) node).getDocumentElement();
230 case Node.ELEMENT_NODE -> (Element) node;
231 default -> throw new IllegalStateException("DOMSource node must be document or element.");