BUG 2743 - Added support for runtime RPC's to netconf mdsal northbound.
[controller.git] / opendaylight / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / controller / 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.controller.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.net.URISyntaxException;
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 org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
25 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
26 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
27 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
28 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity;
29 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag;
30 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType;
31 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
32 import org.opendaylight.controller.netconf.mapping.api.HandlingPriority;
33 import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution;
34 import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext;
35 import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException;
36 import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation;
37 import org.opendaylight.controller.netconf.util.xml.XmlElement;
38 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
40 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
44 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
45 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
46 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
47 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
48 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.Module;
50 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
51 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.w3c.dom.Attr;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57 import org.w3c.dom.Node;
58 import org.w3c.dom.NodeList;
59
60 public class RuntimeRpc extends AbstractSingletonNetconfOperation {
61
62     private static final Logger LOG = LoggerFactory.getLogger(RuntimeRpc.class);
63
64     private final CurrentSchemaContext schemaContext;
65     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
66
67     static {
68         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
69         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
70     }
71
72     private final DOMRpcService rpcService;
73
74     public RuntimeRpc(final String netconfSessionIdForReporting, CurrentSchemaContext schemaContext, DOMRpcService rpcService) {
75         super(netconfSessionIdForReporting);
76         this.schemaContext = schemaContext;
77         this.rpcService = rpcService;
78     }
79
80     @Override
81     protected HandlingPriority canHandle(final String netconfOperationName, final String namespace) {
82         final URI namespaceURI = createNsUri(namespace);
83         final Optional<Module> module = getModule(namespaceURI);
84
85         if (!module.isPresent()) {
86             LOG.debug("Cannot handle rpc: {}, {}", netconfOperationName, namespace);
87             return HandlingPriority.CANNOT_HANDLE;
88         }
89
90         getRpcDefinitionFromModule(module.get(), namespaceURI, netconfOperationName);
91         return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY;
92
93     }
94
95     @Override
96     protected String getOperationName() {
97         throw new UnsupportedOperationException("Runtime rpc does not have a stable name");
98     }
99
100     private URI createNsUri(final String namespace) {
101         final URI namespaceURI;
102         try {
103             namespaceURI = new URI(namespace);
104         } catch (URISyntaxException e) {
105             // Cannot occur, namespace in parsed XML cannot be invalid URI
106             throw new IllegalStateException("Unable to parse URI " + namespace, e);
107         }
108         return namespaceURI;
109     }
110
111     //this returns module with the newest revision if more then 1 module with same namespace is found
112     private Optional<Module> getModule(final URI namespaceURI) {
113         return Optional.of(schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(namespaceURI, null));
114     }
115
116     private Optional<RpcDefinition> getRpcDefinitionFromModule(Module module, URI namespaceURI, String name) {
117         for (RpcDefinition rpcDef : module.getRpcs()) {
118             if (rpcDef.getQName().getNamespace().equals(namespaceURI)
119                     && rpcDef.getQName().getLocalName().equals(name)) {
120                 return Optional.of(rpcDef);
121             }
122         }
123         return Optional.absent();
124     }
125
126     @Override
127     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException {
128
129         final String netconfOperationName = operationElement.getName();
130         final String netconfOperationNamespace;
131         try {
132             netconfOperationNamespace = operationElement.getNamespace();
133         } catch (MissingNameSpaceException e) {
134             LOG.debug("Cannot retrieve netconf operation namespace from message due to ", e);
135             throw new NetconfDocumentedException("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 NetconfDocumentedException("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(), namespaceURI, netconfOperationName);
149
150         if (!rpcDefinitionOptional.isPresent()) {
151             throw new NetconfDocumentedException("Unable to find RpcDefinition with namespace and name : " + namespaceURI + " " + netconfOperationName,
152                     ErrorType.application, ErrorTag.bad_element, ErrorSeverity.error);
153         }
154
155         final RpcDefinition rpcDefinition = rpcDefinitionOptional.get();
156         final SchemaPath schemaPath = SchemaPath.create(Collections.singletonList(rpcDefinition.getQName()), true);
157         final NormalizedNode<?, ?> inputNode = rpcToNNode(operationElement, rpcDefinition.getInput());
158
159         final CheckedFuture<DOMRpcResult, DOMRpcException> rpcFuture = rpcService.invokeRpc(schemaPath, inputNode);
160         try {
161             final DOMRpcResult result = rpcFuture.checkedGet();
162             if (result.getResult() == null) {
163                 return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.of(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0));
164             }
165             return (Element) transformNormalizedNode(document, result.getResult(), rpcDefinition.getOutput().getPath());
166         } catch (DOMRpcException e) {
167             throw NetconfDocumentedException.wrap(e);
168         }
169     }
170
171     @Override
172     public Document handle(final Document requestMessage,
173                            final NetconfOperationChainedExecution subsequentOperation) throws NetconfDocumentedException {
174
175         final XmlElement requestElement = getRequestElementWithCheck(requestMessage);
176
177         final Document document = XmlUtil.newDocument();
178
179         final XmlElement operationElement = requestElement.getOnlyChildElement();
180         final Map<String, Attr> attributes = requestElement.getAttributes();
181
182         final Element response = handle(document, operationElement, subsequentOperation);
183         final Element rpcReply = XmlUtil.createElement(document, XmlNetconfConstants.RPC_REPLY_KEY, Optional.of(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0));
184
185         if(XmlElement.fromDomElement(response).hasNamespace()) {
186             rpcReply.appendChild(response);
187         } else {
188             final NodeList list = response.getChildNodes();
189             if (list.getLength() == 0) {
190                 rpcReply.appendChild(response);
191             } else {
192                 while (list.getLength() != 0) {
193                     rpcReply.appendChild(list.item(0));
194                 }
195             }
196         }
197
198         for (Attr attribute : attributes.values()) {
199             rpcReply.setAttributeNode((Attr) document.importNode(attribute, true));
200         }
201         document.appendChild(rpcReply);
202         return document;
203     }
204
205     //TODO move all occurences of this method in mdsal netconf(and xml factories) to a utility class
206     private Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data, final SchemaPath rpcOutputPath) {
207         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.RPC_REPLY_KEY));
208
209         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
210
211         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
212                 schemaContext.getCurrentContext(), rpcOutputPath);
213
214         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter);
215
216         writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
217         try {
218             nnStreamWriter.close();
219             xmlWriter.close();
220         } catch (IOException | XMLStreamException e) {
221             LOG.warn("Error while closing streams", e);
222         }
223
224         return result.getNode();
225     }
226
227     private XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
228         try {
229             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
230         } catch (final XMLStreamException e) {
231             throw new RuntimeException(e);
232         }
233     }
234
235     private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
236         try {
237             for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
238                 nnWriter.write(child);
239             }
240             nnWriter.flush();
241             xmlWriter.flush();
242         } catch (XMLStreamException | IOException e) {
243             Throwables.propagate(e);
244         }
245     }
246
247     /**
248      * Parses xml element rpc input into normalized node or null if rpc does not take any input
249      * @param oElement rpc xml element
250      * @param input input container schema node, or null if rpc does not take any input
251      * @return parsed rpc into normalized node, or null if input schema is null
252      */
253     @Nullable
254     private NormalizedNode<?, ?> rpcToNNode(final XmlElement oElement, @Nullable final ContainerSchemaNode input) {
255         return input == null ? null : DomToNormalizedNodeParserFactory
256                 .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext())
257                 .getContainerNodeParser()
258                 .parse(Collections.singletonList(oElement.getDomElement()), input);
259     }
260
261 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.