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_QNAME;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import java.util.Collections;
15 import java.util.List;
17 import javax.xml.transform.dom.DOMSource;
18 import org.opendaylight.controller.config.util.xml.DocumentedException;
19 import org.opendaylight.controller.config.util.xml.XmlElement;
20 import org.opendaylight.controller.config.util.xml.XmlUtil;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
27 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
28 import org.w3c.dom.Attr;
29 import org.w3c.dom.Document;
30 import org.w3c.dom.Element;
31 import org.w3c.dom.Node;
34 * Transforms rpc structures to anyxml and vice versa. Provides support for devices, which don't expose their schema.
36 class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
39 * Selects elements in anyxml data node, which matches path arguments QNames. Since class in not context aware,
40 * method searches for all elements as they are named in path.
41 * @param data data, must be of type {@link AnyXmlNode}
42 * @param path path to select
43 * @return selected data
46 public Optional<NormalizedNode<?, ?>> selectFromDataStructure(final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> data,
47 final YangInstanceIdentifier path) {
48 Preconditions.checkArgument(data instanceof AnyXmlNode);
49 final List<XmlElement> xmlElements = selectMatchingNodes(getSourceElement(((AnyXmlNode)data).getValue()), path);
50 final Document result = XmlUtil.newDocument();
51 final QName dataQName = NetconfMessageTransformUtil.NETCONF_DATA_QNAME;
52 final Element dataElement = result.createElementNS(dataQName.getNamespace().toString(), dataQName.getLocalName());
53 result.appendChild(dataElement);
54 for (XmlElement xmlElement : xmlElements) {
55 dataElement.appendChild(result.importNode(xmlElement.getDomElement(), true));
57 final AnyXmlNode resultAnyxml = Builders.anyXmlBuilder()
58 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(dataQName))
59 .withValue(new DOMSource(result))
61 return Optional.of(resultAnyxml);
65 * This class in not context aware. All elements are present in resulting structure, which are present in data path.
66 * @see RpcStructureTransformer#createEditConfigStructure(Optional, YangInstanceIdentifier, Optional)
68 * @param dataPath path, where data will be written
69 * @param operation operation
70 * @return config structure
73 public AnyXmlNode createEditConfigStructure(final Optional<NormalizedNode<?, ?>> data,
74 final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation) {
75 Preconditions.checkArgument(data.isPresent());
76 Preconditions.checkArgument(data.get() instanceof AnyXmlNode);
78 final AnyXmlNode anxmlData = (AnyXmlNode) data.get();
79 final Document document = XmlUtil.newDocument();
80 final Element dataNode = (Element) document.importNode(getSourceElement(anxmlData.getValue()), true);
81 checkDataValidForPath(dataPath, dataNode);
83 final Element configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
84 NETCONF_CONFIG_QNAME.getLocalName());
85 document.appendChild(configElement);
87 final Element parentXmlStructure;
88 if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
89 parentXmlStructure = dataNode;
90 configElement.appendChild(parentXmlStructure);
92 final List<YangInstanceIdentifier.PathArgument> pathArguments = dataPath.getPathArguments();
93 //last will be appended later
94 final List<YangInstanceIdentifier.PathArgument> pathWithoutLast = pathArguments.subList(0, pathArguments.size() - 1);
95 parentXmlStructure = instanceIdToXmlStructure(pathWithoutLast, configElement);
97 if (operation.isPresent()) {
98 setOperationAttribute(operation, document, dataNode);
101 parentXmlStructure.appendChild(document.importNode(dataNode, true));
102 return Builders.anyXmlBuilder()
103 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NETCONF_CONFIG_QNAME))
104 .withValue(new DOMSource(document.getDocumentElement()))
109 * This class in not context aware. All elements are present in resulting structure, which are present in data path.
110 * @see RpcStructureTransformer#toFilterStructure(YangInstanceIdentifier)
112 * @return filter structure
115 public DataContainerChild<?, ?> toFilterStructure(final YangInstanceIdentifier path) {
116 final Document document = XmlUtil.newDocument();
117 final QName filterQname = NetconfMessageTransformUtil.NETCONF_FILTER_QNAME;
118 final Element filter = document.createElementNS(filterQname.getNamespace().toString(), filterQname.getLocalName());
119 final Attr a = document.createAttributeNS(filterQname.getNamespace().toString(), "type");
120 a.setTextContent("subtree");
121 filter.setAttributeNode(a);
122 document.appendChild(filter);
123 instanceIdToXmlStructure(path.getPathArguments(), filter);
124 return Builders.anyXmlBuilder()
125 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(filterQname))
126 .withValue(new DOMSource(document.getDocumentElement()))
130 private static void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
131 //if datapath is empty, consider dataNode to be a root node
132 if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
135 final XmlElement dataElement = XmlElement.fromDomElement(dataNode);
136 final YangInstanceIdentifier.PathArgument lastPathArgument = dataPath.getLastPathArgument();
137 final QName nodeType = lastPathArgument.getNodeType();
138 if (!nodeType.getNamespace().toString().equals(dataNode.getNamespaceURI()) ||
139 !nodeType.getLocalName().equals(dataElement.getName())) {
140 throw new IllegalStateException(
141 String.format("Can't write data '%s' to path %s", dataNode.getTagName(), dataPath));
143 if (lastPathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
144 checkKeyValuesValidForPath(dataElement, lastPathArgument);
149 private static void checkKeyValuesValidForPath(final XmlElement dataElement,
150 final YangInstanceIdentifier.PathArgument lastPathArgument) {
151 final YangInstanceIdentifier.NodeIdentifierWithPredicates keyedId =
152 (YangInstanceIdentifier.NodeIdentifierWithPredicates) lastPathArgument;
153 final Map<QName, Object> keyValues = keyedId.getKeyValues();
154 for (QName qName : keyValues.keySet()) {
155 final List<XmlElement> key =
156 dataElement.getChildElementsWithinNamespace(qName.getLocalName(), qName.getNamespace().toString());
158 throw new IllegalStateException("No key present in xml");
160 if (key.size() > 1) {
161 throw new IllegalStateException("Multiple values for same key present");
163 final String textContent;
165 textContent = key.get(0).getTextContent();
166 } catch (DocumentedException e) {
167 throw new IllegalStateException("Key value not present in key element");
169 if (!keyValues.get(qName).equals(textContent)) {
170 throw new IllegalStateException("Key value in path not equal to key value in xml");
175 private static void setOperationAttribute(final Optional<ModifyAction> operation, final Document document,
176 final Element dataNode) {
177 final QName operationQname = NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
178 final Attr operationAttribute =
179 document.createAttributeNS(operationQname.getNamespace().toString(), operationQname.getLocalName());
180 operationAttribute.setTextContent(toOperationString(operation.get()));
181 dataNode.setAttributeNode(operationAttribute);
184 private static Element instanceIdToXmlStructure(final List<YangInstanceIdentifier.PathArgument> pathArguments,
185 final Element data) {
186 final Document doc = data.getOwnerDocument();
187 Element parent = data;
188 for (YangInstanceIdentifier.PathArgument pathArgument : pathArguments) {
189 final QName nodeType = pathArgument.getNodeType();
190 final Element element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
191 parent.appendChild(element);
192 //if path argument is list id, add also keys to resulting xml
193 if (pathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
194 YangInstanceIdentifier.NodeIdentifierWithPredicates listNode = (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument;
195 for (Map.Entry<QName, Object> key : listNode.getKeyValues().entrySet()) {
196 final Element keyElement = doc.createElementNS(key.getKey().getNamespace().toString(), key.getKey().getLocalName());
197 keyElement.setTextContent(key.getValue().toString());
198 element.appendChild(keyElement);
206 private static List<XmlElement> selectMatchingNodes(final Element domElement, final YangInstanceIdentifier path) {
207 XmlElement element = XmlElement.fromDomElement(domElement);
208 for (YangInstanceIdentifier.PathArgument pathArgument : path.getPathArguments()) {
209 List<XmlElement> childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
210 if (childElements.size() == 1) {
211 element = childElements.get(0);
213 return childElements;
216 return Collections.singletonList(element);
219 private static String toOperationString(final ModifyAction operation) {
220 return operation.name().toLowerCase();
223 private static Element getSourceElement(final DOMSource source) {
224 final Node node = source.getNode();
225 switch (node.getNodeType()) {
226 case Node.DOCUMENT_NODE:
227 return ((Document)node).getDocumentElement();
228 case Node.ELEMENT_NODE:
229 return (Element) node;
231 throw new IllegalStateException("DOMSource node must be document or element.");