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