Remove dependency on controller config-util
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / util / SchemalessRpcStructureTransformer.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.sal.connect.netconf.util;
9
10 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME;
11
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Locale;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import javax.xml.transform.dom.DOMSource;
20 import org.opendaylight.netconf.api.DocumentedException;
21 import org.opendaylight.netconf.api.xml.XmlElement;
22 import org.opendaylight.netconf.api.xml.XmlUtil;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
28 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
29 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
30 import org.w3c.dom.Attr;
31 import org.w3c.dom.Document;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Node;
34
35 /**
36  * Transforms rpc structures to anyxml and vice versa. Provides support for devices, which don't expose their schema.
37  */
38 class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
39
40     /**
41      * Selects elements in anyxml data node, which matches path arguments QNames. Since class in not context aware,
42      * method searches for all elements as they are named in path.
43      * @param data data, must be of type {@link AnyXmlNode}
44      * @param path path to select
45      * @return selected data
46      */
47     @Override
48     public Optional<NormalizedNode<?, ?>> selectFromDataStructure(
49             final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> data,
50             final YangInstanceIdentifier path) {
51         Preconditions.checkArgument(data instanceof AnyXmlNode);
52         final List<XmlElement> xmlElements = selectMatchingNodes(getSourceElement(((AnyXmlNode)data).getValue()), path);
53         final Document result = XmlUtil.newDocument();
54         final QName dataQName = NetconfMessageTransformUtil.NETCONF_DATA_QNAME;
55         final Element dataElement =
56                 result.createElementNS(dataQName.getNamespace().toString(), dataQName.getLocalName());
57         result.appendChild(dataElement);
58         for (XmlElement xmlElement : xmlElements) {
59             dataElement.appendChild(result.importNode(xmlElement.getDomElement(), true));
60         }
61         final AnyXmlNode resultAnyxml = Builders.anyXmlBuilder()
62                 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(dataQName))
63                 .withValue(new DOMSource(result))
64                 .build();
65         return Optional.of(resultAnyxml);
66     }
67
68     /**
69      * This class in not context aware. All elements are present in resulting structure, which are present in data path.
70      * @see RpcStructureTransformer#createEditConfigStructure(Optional, YangInstanceIdentifier, Optional)
71      * @param data data
72      * @param dataPath path, where data will be written
73      * @param operation operation
74      * @return config structure
75      */
76     @Override
77     public AnyXmlNode createEditConfigStructure(final Optional<NormalizedNode<?, ?>> data,
78                                                 final YangInstanceIdentifier dataPath,
79                                                 final Optional<ModifyAction> operation) {
80         Preconditions.checkArgument(data.isPresent());
81         Preconditions.checkArgument(data.get() instanceof AnyXmlNode);
82
83         final AnyXmlNode anxmlData = (AnyXmlNode) data.get();
84         final Document document = XmlUtil.newDocument();
85         final Element dataNode = (Element) document.importNode(getSourceElement(anxmlData.getValue()), true);
86         checkDataValidForPath(dataPath, dataNode);
87
88         final Element configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
89                 NETCONF_CONFIG_QNAME.getLocalName());
90         document.appendChild(configElement);
91
92         final Element parentXmlStructure;
93         if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
94             parentXmlStructure = dataNode;
95             configElement.appendChild(parentXmlStructure);
96         } else {
97             final List<YangInstanceIdentifier.PathArgument> pathArguments = dataPath.getPathArguments();
98             //last will be appended later
99             final List<YangInstanceIdentifier.PathArgument> pathWithoutLast =
100                     pathArguments.subList(0, pathArguments.size() - 1);
101             parentXmlStructure = instanceIdToXmlStructure(pathWithoutLast, configElement);
102         }
103         if (operation.isPresent()) {
104             setOperationAttribute(operation, document, dataNode);
105         }
106         //append data
107         parentXmlStructure.appendChild(document.importNode(dataNode, true));
108         return Builders.anyXmlBuilder()
109                 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NETCONF_CONFIG_QNAME))
110                 .withValue(new DOMSource(document.getDocumentElement()))
111                 .build();
112     }
113
114     /**
115      * This class in not context aware. All elements are present in resulting structure, which are present in data path.
116      * @see RpcStructureTransformer#toFilterStructure(YangInstanceIdentifier)
117      * @param path path
118      * @return filter structure
119      */
120     @Override
121     public DataContainerChild<?, ?> toFilterStructure(final YangInstanceIdentifier path) {
122         final Document document = XmlUtil.newDocument();
123         final QName filterQname = NetconfMessageTransformUtil.NETCONF_FILTER_QNAME;
124         final Element filter =
125                 document.createElementNS(filterQname.getNamespace().toString(), filterQname.getLocalName());
126         final Attr a = document.createAttributeNS(filterQname.getNamespace().toString(), "type");
127         a.setTextContent("subtree");
128         filter.setAttributeNode(a);
129         document.appendChild(filter);
130         instanceIdToXmlStructure(path.getPathArguments(), filter);
131         return Builders.anyXmlBuilder()
132                 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(filterQname))
133                 .withValue(new DOMSource(document.getDocumentElement()))
134                 .build();
135     }
136
137     private static void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
138         //if datapath is empty, consider dataNode to be a root node
139         if (dataPath.equals(YangInstanceIdentifier.EMPTY)) {
140             return;
141         }
142         final XmlElement dataElement = XmlElement.fromDomElement(dataNode);
143         final YangInstanceIdentifier.PathArgument lastPathArgument = dataPath.getLastPathArgument();
144         final QName nodeType = lastPathArgument.getNodeType();
145         if (!nodeType.getNamespace().toString().equals(dataNode.getNamespaceURI())
146                 || !nodeType.getLocalName().equals(dataElement.getName())) {
147             throw new IllegalStateException(
148                     String.format("Can't write data '%s' to path %s", dataNode.getTagName(), dataPath));
149         }
150         if (lastPathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
151             checkKeyValuesValidForPath(dataElement, lastPathArgument);
152         }
153
154     }
155
156     private static void checkKeyValuesValidForPath(final XmlElement dataElement,
157                                             final YangInstanceIdentifier.PathArgument lastPathArgument) {
158         final YangInstanceIdentifier.NodeIdentifierWithPredicates keyedId =
159                 (YangInstanceIdentifier.NodeIdentifierWithPredicates) lastPathArgument;
160         final Map<QName, Object> keyValues = keyedId.getKeyValues();
161         for (Entry<QName, Object> entry : keyValues.entrySet()) {
162             QName qualifiedName = entry.getKey();
163             final List<XmlElement> key =
164                     dataElement.getChildElementsWithinNamespace(qualifiedName.getLocalName(),
165                             qualifiedName.getNamespace().toString());
166             if (key.isEmpty()) {
167                 throw new IllegalStateException("No key present in xml");
168             }
169             if (key.size() > 1) {
170                 throw new IllegalStateException("Multiple values for same key present");
171             }
172             final String textContent;
173             try {
174                 textContent = key.get(0).getTextContent();
175             } catch (DocumentedException e) {
176                 throw new IllegalStateException("Key value not present in key element", e);
177             }
178             if (!entry.getValue().equals(textContent)) {
179                 throw new IllegalStateException("Key value in path not equal to key value in xml");
180             }
181         }
182     }
183
184     private static void setOperationAttribute(final Optional<ModifyAction> operation, final Document document,
185                                        final Element dataNode) {
186         final QName operationQname = NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
187         final Attr operationAttribute =
188                 document.createAttributeNS(operationQname.getNamespace().toString(), operationQname.getLocalName());
189         operationAttribute.setTextContent(toOperationString(operation.get()));
190         dataNode.setAttributeNode(operationAttribute);
191     }
192
193     private static Element instanceIdToXmlStructure(final List<YangInstanceIdentifier.PathArgument> pathArguments,
194                                                     final Element data) {
195         final Document doc = data.getOwnerDocument();
196         Element parent = data;
197         for (YangInstanceIdentifier.PathArgument pathArgument : pathArguments) {
198             final QName nodeType = pathArgument.getNodeType();
199             final Element element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
200             parent.appendChild(element);
201             //if path argument is list id, add also keys to resulting xml
202             if (pathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
203                 YangInstanceIdentifier.NodeIdentifierWithPredicates listNode =
204                         (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument;
205                 for (Map.Entry<QName, Object> key : listNode.getKeyValues().entrySet()) {
206                     final Element keyElement =
207                             doc.createElementNS(key.getKey().getNamespace().toString(), key.getKey().getLocalName());
208                     keyElement.setTextContent(key.getValue().toString());
209                     element.appendChild(keyElement);
210                 }
211             }
212             parent = element;
213         }
214         return parent;
215     }
216
217     private static List<XmlElement> selectMatchingNodes(final Element domElement, final YangInstanceIdentifier path) {
218         XmlElement element = XmlElement.fromDomElement(domElement);
219         for (YangInstanceIdentifier.PathArgument pathArgument : path.getPathArguments()) {
220             List<XmlElement> childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
221             if (childElements.size() == 1) {
222                 element = childElements.get(0);
223             } else {
224                 return childElements;
225             }
226         }
227         return Collections.singletonList(element);
228     }
229
230     private static String toOperationString(final ModifyAction operation) {
231         return operation.name().toLowerCase(Locale.ROOT);
232     }
233
234     private static Element getSourceElement(final DOMSource source) {
235         final Node node = source.getNode();
236         switch (node.getNodeType()) {
237             case Node.DOCUMENT_NODE:
238                 return ((Document)node).getDocumentElement();
239             case Node.ELEMENT_NODE:
240                 return (Element) node;
241             default:
242                 throw new IllegalStateException("DOMSource node must be document or element.");
243         }
244     }
245
246 }