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.sal.connect.netconf.util;
10 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_NODEID;
11 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME;
12 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_NODEID;
13 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_QNAME;
15 import com.google.common.base.Preconditions;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Locale;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import javax.xml.transform.dom.DOMSource;
23 import org.opendaylight.netconf.api.DocumentedException;
24 import org.opendaylight.netconf.api.xml.XmlElement;
25 import org.opendaylight.netconf.api.xml.XmlUtil;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
31 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
32 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
33 import org.w3c.dom.Attr;
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 AnyXmlNode}
47 * @param path path to select
48 * @return selected data
51 public Optional<NormalizedNode<?, ?>> selectFromDataStructure(
52 final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> data,
53 final YangInstanceIdentifier path) {
54 Preconditions.checkArgument(data instanceof AnyXmlNode);
55 final List<XmlElement> xmlElements = selectMatchingNodes(getSourceElement(((AnyXmlNode)data).getValue()), path);
56 final Document result = XmlUtil.newDocument();
57 final Element dataElement =
58 result.createElementNS(NETCONF_DATA_QNAME.getNamespace().toString(), NETCONF_DATA_QNAME.getLocalName());
59 result.appendChild(dataElement);
60 for (XmlElement xmlElement : xmlElements) {
61 dataElement.appendChild(result.importNode(xmlElement.getDomElement(), true));
63 final AnyXmlNode resultAnyxml = Builders.anyXmlBuilder()
64 .withNodeIdentifier(NETCONF_DATA_NODEID)
65 .withValue(new DOMSource(result))
67 return Optional.of(resultAnyxml);
71 * This class in not context aware. All elements are present in resulting structure, which are present in data path.
72 * @see RpcStructureTransformer#createEditConfigStructure(Optional, YangInstanceIdentifier, Optional)
74 * @param dataPath path, where data will be written
75 * @param operation operation
76 * @return config structure
79 public AnyXmlNode createEditConfigStructure(final Optional<NormalizedNode<?, ?>> data,
80 final YangInstanceIdentifier dataPath,
81 final Optional<ModifyAction> operation) {
82 Preconditions.checkArgument(data.isPresent());
83 Preconditions.checkArgument(data.get() instanceof AnyXmlNode);
85 final AnyXmlNode anxmlData = (AnyXmlNode) data.get();
86 final Document document = XmlUtil.newDocument();
87 final Element dataNode = (Element) document.importNode(getSourceElement(anxmlData.getValue()), true);
88 checkDataValidForPath(dataPath, dataNode);
90 final Element configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
91 NETCONF_CONFIG_QNAME.getLocalName());
92 document.appendChild(configElement);
94 final Element parentXmlStructure;
95 if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
96 parentXmlStructure = dataNode;
97 configElement.appendChild(parentXmlStructure);
99 final List<YangInstanceIdentifier.PathArgument> pathArguments = dataPath.getPathArguments();
100 //last will be appended later
101 final List<YangInstanceIdentifier.PathArgument> pathWithoutLast =
102 pathArguments.subList(0, pathArguments.size() - 1);
103 parentXmlStructure = instanceIdToXmlStructure(pathWithoutLast, configElement);
105 if (operation.isPresent()) {
106 setOperationAttribute(operation, document, dataNode);
109 parentXmlStructure.appendChild(document.importNode(dataNode, true));
110 return Builders.anyXmlBuilder().withNodeIdentifier(NETCONF_CONFIG_NODEID)
111 .withValue(new DOMSource(document.getDocumentElement()))
116 * This class in not context aware. All elements are present in resulting structure, which are present in data path.
117 * @see RpcStructureTransformer#toFilterStructure(YangInstanceIdentifier)
119 * @return filter structure
122 public DataContainerChild<?, ?> toFilterStructure(final YangInstanceIdentifier path) {
123 final Document document = XmlUtil.newDocument();
124 final QName filterQname = NetconfMessageTransformUtil.NETCONF_FILTER_QNAME;
125 final Element filter =
126 document.createElementNS(filterQname.getNamespace().toString(), filterQname.getLocalName());
127 final Attr a = document.createAttributeNS(filterQname.getNamespace().toString(), "type");
128 a.setTextContent("subtree");
129 filter.setAttributeNode(a);
130 document.appendChild(filter);
131 instanceIdToXmlStructure(path.getPathArguments(), filter);
132 return Builders.anyXmlBuilder()
133 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(filterQname))
134 .withValue(new DOMSource(document.getDocumentElement()))
138 private static void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
139 //if datapath is empty, consider dataNode to be a root node
140 if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
143 final XmlElement dataElement = XmlElement.fromDomElement(dataNode);
144 final YangInstanceIdentifier.PathArgument lastPathArgument = dataPath.getLastPathArgument();
145 final QName nodeType = lastPathArgument.getNodeType();
146 if (!nodeType.getNamespace().toString().equals(dataNode.getNamespaceURI())
147 || !nodeType.getLocalName().equals(dataElement.getName())) {
148 throw new IllegalStateException(
149 String.format("Can't write data '%s' to path %s", dataNode.getTagName(), dataPath));
151 if (lastPathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
152 checkKeyValuesValidForPath(dataElement, lastPathArgument);
157 private static void checkKeyValuesValidForPath(final XmlElement dataElement,
158 final YangInstanceIdentifier.PathArgument lastPathArgument) {
159 final YangInstanceIdentifier.NodeIdentifierWithPredicates keyedId =
160 (YangInstanceIdentifier.NodeIdentifierWithPredicates) lastPathArgument;
161 final Map<QName, Object> keyValues = keyedId.getKeyValues();
162 for (Entry<QName, Object> entry : keyValues.entrySet()) {
163 QName qualifiedName = entry.getKey();
164 final List<XmlElement> key =
165 dataElement.getChildElementsWithinNamespace(qualifiedName.getLocalName(),
166 qualifiedName.getNamespace().toString());
168 throw new IllegalStateException("No key present in xml");
170 if (key.size() > 1) {
171 throw new IllegalStateException("Multiple values for same key present");
173 final String textContent;
175 textContent = key.get(0).getTextContent();
176 } catch (DocumentedException e) {
177 throw new IllegalStateException("Key value not present in key element", e);
179 if (!entry.getValue().equals(textContent)) {
180 throw new IllegalStateException("Key value in path not equal to key value in xml");
185 private static void setOperationAttribute(final Optional<ModifyAction> operation, final Document document,
186 final Element dataNode) {
187 final QName operationQname = NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
188 final Attr operationAttribute =
189 document.createAttributeNS(operationQname.getNamespace().toString(), operationQname.getLocalName());
190 operationAttribute.setTextContent(toOperationString(operation.get()));
191 dataNode.setAttributeNode(operationAttribute);
194 private static Element instanceIdToXmlStructure(final List<YangInstanceIdentifier.PathArgument> pathArguments,
195 final Element data) {
196 final Document doc = data.getOwnerDocument();
197 Element parent = data;
198 for (YangInstanceIdentifier.PathArgument pathArgument : pathArguments) {
199 final QName nodeType = pathArgument.getNodeType();
200 final Element element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
201 parent.appendChild(element);
202 //if path argument is list id, add also keys to resulting xml
203 if (pathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
204 YangInstanceIdentifier.NodeIdentifierWithPredicates listNode =
205 (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument;
206 for (Map.Entry<QName, Object> key : listNode.getKeyValues().entrySet()) {
207 final Element keyElement =
208 doc.createElementNS(key.getKey().getNamespace().toString(), key.getKey().getLocalName());
209 keyElement.setTextContent(key.getValue().toString());
210 element.appendChild(keyElement);
218 private static List<XmlElement> selectMatchingNodes(final Element domElement, final YangInstanceIdentifier path) {
219 XmlElement element = XmlElement.fromDomElement(domElement);
220 for (YangInstanceIdentifier.PathArgument pathArgument : path.getPathArguments()) {
221 List<XmlElement> childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
222 if (childElements.size() == 1) {
223 element = childElements.get(0);
225 return childElements;
228 return Collections.singletonList(element);
231 private static String toOperationString(final ModifyAction operation) {
232 return operation.name().toLowerCase(Locale.ROOT);
235 private static Element getSourceElement(final DOMSource source) {
236 final Node node = source.getNode();
237 switch (node.getNodeType()) {
238 case Node.DOCUMENT_NODE:
239 return ((Document)node).getDocumentElement();
240 case Node.ELEMENT_NODE:
241 return (Element) node;
243 throw new IllegalStateException("DOMSource node must be document or element.");