Bug 5824: Migrate netconf to the new XML parser #2
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / RuntimeRpc.java
1 /*
2  * Copyright (c) 2015 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
9 package org.opendaylight.netconf.mdsal.connector.ops;
10
11 import com.google.common.base.Optional;
12 import com.google.common.base.Throwables;
13 import com.google.common.util.concurrent.CheckedFuture;
14 import java.io.IOException;
15 import java.net.URI;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Map;
19 import javax.annotation.Nullable;
20 import javax.xml.stream.XMLOutputFactory;
21 import javax.xml.stream.XMLStreamException;
22 import javax.xml.stream.XMLStreamWriter;
23 import javax.xml.transform.dom.DOMResult;
24 import javax.xml.transform.dom.DOMSource;
25 import org.opendaylight.controller.config.util.xml.DocumentedException;
26 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
27 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
28 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
29 import org.opendaylight.controller.config.util.xml.XmlElement;
30 import org.opendaylight.controller.config.util.xml.XmlMappingConstants;
31 import org.opendaylight.controller.config.util.xml.XmlUtil;
32 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
33 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
34 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
35 import org.opendaylight.netconf.api.NetconfDocumentedException;
36 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
37 import org.opendaylight.netconf.mapping.api.HandlingPriority;
38 import org.opendaylight.netconf.mapping.api.NetconfOperationChainedExecution;
39 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
40 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
41 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
45 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
46 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
47 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
48 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
49 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaOrderedNormalizedNodeWriter;
50 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.Module;
52 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
53 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56 import org.w3c.dom.Attr;
57 import org.w3c.dom.Document;
58 import org.w3c.dom.Element;
59 import org.w3c.dom.Node;
60 import org.w3c.dom.NodeList;
61
62 public class RuntimeRpc extends AbstractSingletonNetconfOperation {
63
64     private static final Logger LOG = LoggerFactory.getLogger(RuntimeRpc.class);
65
66     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
67
68     static {
69         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
70         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
71     }
72
73     private final CurrentSchemaContext schemaContext;
74     private final DOMRpcService rpcService;
75
76     public RuntimeRpc(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
77                       final DOMRpcService rpcService) {
78         super(netconfSessionIdForReporting);
79         this.schemaContext = schemaContext;
80         this.rpcService = rpcService;
81     }
82
83     @Override
84     protected HandlingPriority canHandle(final String netconfOperationName, final String namespace) {
85         final URI namespaceURI = createNsUri(namespace);
86         final Optional<Module> module = getModule(namespaceURI);
87
88         if (!module.isPresent()) {
89             LOG.debug("Cannot handle rpc: {}, {}", netconfOperationName, namespace);
90             return HandlingPriority.CANNOT_HANDLE;
91         }
92
93         getRpcDefinitionFromModule(module.get(), namespaceURI, netconfOperationName);
94         return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY;
95
96     }
97
98     @Override
99     protected String getOperationName() {
100         throw new UnsupportedOperationException("Runtime rpc does not have a stable name");
101     }
102
103     private static URI createNsUri(final String namespace) {
104         // May throw IllegalArgumentException, but that should never happen, as the namespace comes from parsed XML
105         return URI.create(namespace);
106     }
107
108     //this returns module with the newest revision if more then 1 module with same namespace is found
109     private Optional<Module> getModule(final URI namespaceURI) {
110         return Optional.fromNullable(
111                 schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(namespaceURI, null));
112     }
113
114     private static Optional<RpcDefinition> getRpcDefinitionFromModule(final Module module, final URI namespaceURI,
115             final String name) {
116         for (final RpcDefinition rpcDef : module.getRpcs()) {
117             if (rpcDef.getQName().getNamespace().equals(namespaceURI)
118                     && rpcDef.getQName().getLocalName().equals(name)) {
119                 return Optional.of(rpcDef);
120             }
121         }
122         return Optional.absent();
123     }
124
125     @Override
126     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
127             throws DocumentedException {
128
129         final String netconfOperationName = operationElement.getName();
130         final String netconfOperationNamespace;
131         try {
132             netconfOperationNamespace = operationElement.getNamespace();
133         } catch (final DocumentedException e) {
134             LOG.debug("Cannot retrieve netconf operation namespace from message due to ", e);
135             throw new DocumentedException("Cannot retrieve netconf operation namespace from message",
136                     ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR);
137         }
138
139         final URI namespaceURI = createNsUri(netconfOperationNamespace);
140         final Optional<Module> moduleOptional = getModule(namespaceURI);
141
142         if (!moduleOptional.isPresent()) {
143             throw new DocumentedException("Unable to find module in Schema Context with namespace and name : "
144                         + namespaceURI + " " + netconfOperationName + schemaContext.getCurrentContext(),
145                     ErrorType.APPLICATION, ErrorTag.BAD_ELEMENT, ErrorSeverity.ERROR);
146         }
147
148         final Optional<RpcDefinition> rpcDefinitionOptional = getRpcDefinitionFromModule(moduleOptional.get(),
149                 namespaceURI, netconfOperationName);
150
151         if (!rpcDefinitionOptional.isPresent()) {
152             throw new DocumentedException(
153                     "Unable to find RpcDefinition with namespace and name : "
154                         + namespaceURI + " " + netconfOperationName,
155                     ErrorType.APPLICATION, ErrorTag.BAD_ELEMENT, ErrorSeverity.ERROR);
156         }
157
158         final RpcDefinition rpcDefinition = rpcDefinitionOptional.get();
159         final SchemaPath schemaPath = SchemaPath.create(Collections.singletonList(rpcDefinition.getQName()), true);
160         final NormalizedNode<?, ?> inputNode = rpcToNNode(operationElement, rpcDefinition.getInput());
161
162         final CheckedFuture<DOMRpcResult, DOMRpcException> rpcFuture = rpcService.invokeRpc(schemaPath, inputNode);
163         try {
164             final DOMRpcResult result = rpcFuture.checkedGet();
165             if (result.getResult() == null) {
166                 return XmlUtil.createElement(document, XmlNetconfConstants.OK,
167                         Optional.of(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0));
168             }
169             return (Element) transformNormalizedNode(document, result.getResult(), rpcDefinition.getOutput().getPath());
170         } catch (final DOMRpcException e) {
171             throw DocumentedException.wrap(e);
172         }
173     }
174
175     @Override
176     public Document handle(final Document requestMessage,
177                            final NetconfOperationChainedExecution subsequentOperation) throws DocumentedException {
178
179         final XmlElement requestElement = getRequestElementWithCheck(requestMessage);
180
181         final Document document = XmlUtil.newDocument();
182
183         final XmlElement operationElement = requestElement.getOnlyChildElement();
184         final Map<String, Attr> attributes = requestElement.getAttributes();
185
186         final Element response = handle(document, operationElement, subsequentOperation);
187         final Element rpcReply = XmlUtil.createElement(document, XmlMappingConstants.RPC_REPLY_KEY,
188                 Optional.of(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0));
189
190         if (XmlElement.fromDomElement(response).hasNamespace()) {
191             rpcReply.appendChild(response);
192         } else {
193             final NodeList list = response.getChildNodes();
194             if (list.getLength() == 0) {
195                 rpcReply.appendChild(response);
196             } else {
197                 while (list.getLength() != 0) {
198                     rpcReply.appendChild(list.item(0));
199                 }
200             }
201         }
202
203         for (final Attr attribute : attributes.values()) {
204             rpcReply.setAttributeNode((Attr) document.importNode(attribute, true));
205         }
206         document.appendChild(rpcReply);
207         return document;
208     }
209
210     private Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data,
211                                          final SchemaPath rpcOutputPath) {
212         final DOMResult result = new DOMResult(document.createElement(XmlMappingConstants.RPC_REPLY_KEY));
213
214         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
215
216         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
217                 schemaContext.getCurrentContext(), rpcOutputPath);
218
219         final SchemaOrderedNormalizedNodeWriter nnWriter =
220                 new SchemaOrderedNormalizedNodeWriter(nnStreamWriter, schemaContext.getCurrentContext(), rpcOutputPath);
221
222         writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
223         try {
224             nnStreamWriter.close();
225             xmlWriter.close();
226         } catch (IOException | XMLStreamException e) {
227             LOG.warn("Error while closing streams", e);
228         }
229
230         return result.getNode();
231     }
232
233     private static XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
234         try {
235             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
236         } catch (final XMLStreamException e) {
237             throw new RuntimeException(e);
238         }
239     }
240
241     private static void writeRootElement(final XMLStreamWriter xmlWriter,
242             final SchemaOrderedNormalizedNodeWriter nnWriter, final ContainerNode data) {
243         try {
244             final Collection<DataContainerChild<?, ?>> value = data.getValue();
245             nnWriter.write(value);
246             nnWriter.flush();
247             xmlWriter.flush();
248         } catch (XMLStreamException | IOException e) {
249             Throwables.propagate(e);
250         }
251     }
252
253     /**
254      * Parses xml element rpc input into normalized node or null if rpc does not take any input.
255      *
256      * @param element rpc xml element
257      * @param input   input container schema node, or null if rpc does not take any input
258      * @return parsed rpc into normalized node, or null if input schema is null
259      */
260     @SuppressWarnings("checkstyle:IllegalCatch")
261     @Nullable
262     private NormalizedNode<?, ?> rpcToNNode(final XmlElement element, @Nullable final ContainerSchemaNode input)
263             throws DocumentedException {
264         if (input.getChildNodes().isEmpty()) {
265             return null;
266         }
267
268         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
269         final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
270         final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext.getCurrentContext(), input);
271
272         try {
273             xmlParser.traverse(new DOMSource(element.getDomElement()));
274         } catch (final Exception ex) {
275             throw new NetconfDocumentedException("Error parsing input: " + ex.getMessage(), ex, ErrorType.PROTOCOL,
276                     ErrorTag.MALFORMED_MESSAGE, ErrorSeverity.ERROR);
277         }
278
279         return resultHolder.getResult();
280     }
281
282 }