32390607b8cab4feb40b78c5eed585c985fc6ad4
[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_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;
14
15 import com.google.common.base.Preconditions;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Map;
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.ModifyAction;
25 import org.opendaylight.netconf.api.xml.XmlElement;
26 import org.opendaylight.netconf.api.xml.XmlUtil;
27 import org.opendaylight.yangtools.yang.common.QName;
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;
37
38 /**
39  * Transforms rpc structures to anyxml and vice versa. Provides support for devices, which don't expose their schema.
40  */
41 class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
42
43     /**
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
49      */
50     @Override
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));
62         }
63         final AnyXmlNode resultAnyxml = Builders.anyXmlBuilder()
64                 .withNodeIdentifier(NETCONF_DATA_NODEID)
65                 .withValue(new DOMSource(result))
66                 .build();
67         return Optional.of(resultAnyxml);
68     }
69
70     /**
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)
73      * @param data data
74      * @param dataPath path, where data will be written
75      * @param operation operation
76      * @return config structure
77      */
78     @Override
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);
84
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);
89
90         final Element configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
91                 NETCONF_CONFIG_QNAME.getLocalName());
92         document.appendChild(configElement);
93
94         final Element parentXmlStructure;
95         if (dataPath.isEmpty()) {
96             parentXmlStructure = dataNode;
97             configElement.appendChild(parentXmlStructure);
98         } else {
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);
104         }
105         if (operation.isPresent()) {
106             setOperationAttribute(operation, document, dataNode);
107         }
108         //append data
109         parentXmlStructure.appendChild(document.importNode(dataNode, true));
110         return Builders.anyXmlBuilder().withNodeIdentifier(NETCONF_CONFIG_NODEID)
111                 .withValue(new DOMSource(document.getDocumentElement()))
112                 .build();
113     }
114
115     /**
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)
118      * @param path path
119      * @return filter structure
120      */
121     @Override
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()))
135                 .build();
136     }
137
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.isEmpty()) {
141             return;
142         }
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));
150         }
151         if (lastPathArgument instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
152             checkKeyValuesValidForPath(dataElement, lastPathArgument);
153         }
154
155     }
156
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());
167             if (key.isEmpty()) {
168                 throw new IllegalStateException("No key present in xml");
169             }
170             if (key.size() > 1) {
171                 throw new IllegalStateException("Multiple values for same key present");
172             }
173             final String textContent;
174             try {
175                 textContent = key.get(0).getTextContent();
176             } catch (DocumentedException e) {
177                 throw new IllegalStateException("Key value not present in key element", e);
178             }
179             if (!entry.getValue().equals(textContent)) {
180                 throw new IllegalStateException("Key value in path not equal to key value in xml");
181             }
182         }
183     }
184
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);
192     }
193
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);
211                 }
212             }
213             parent = element;
214         }
215         return parent;
216     }
217
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);
224             } else {
225                 return childElements;
226             }
227         }
228         return Collections.singletonList(element);
229     }
230
231     private static String toOperationString(final ModifyAction operation) {
232         return operation.name().toLowerCase(Locale.ROOT);
233     }
234
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;
242             default:
243                 throw new IllegalStateException("DOMSource node must be document or element.");
244         }
245     }
246
247 }