2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.netconf.mdsal.connector.ops;
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;
16 import java.net.URISyntaxException;
17 import java.util.Collections;
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;
60 public class RuntimeRpc extends AbstractSingletonNetconfOperation {
62 private static final Logger LOG = LoggerFactory.getLogger(RuntimeRpc.class);
64 private final CurrentSchemaContext schemaContext;
65 private static final XMLOutputFactory XML_OUTPUT_FACTORY;
68 XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
69 XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
72 private final DOMRpcService rpcService;
74 public RuntimeRpc(final String netconfSessionIdForReporting, CurrentSchemaContext schemaContext, DOMRpcService rpcService) {
75 super(netconfSessionIdForReporting);
76 this.schemaContext = schemaContext;
77 this.rpcService = rpcService;
81 protected HandlingPriority canHandle(final String netconfOperationName, final String namespace) {
82 final URI namespaceURI = createNsUri(namespace);
83 final Optional<Module> module = getModule(namespaceURI);
85 if (!module.isPresent()) {
86 LOG.debug("Cannot handle rpc: {}, {}", netconfOperationName, namespace);
87 return HandlingPriority.CANNOT_HANDLE;
90 getRpcDefinitionFromModule(module.get(), namespaceURI, netconfOperationName);
91 return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY;
96 protected String getOperationName() {
97 throw new UnsupportedOperationException("Runtime rpc does not have a stable name");
100 private URI createNsUri(final String namespace) {
101 final URI namespaceURI;
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);
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));
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);
123 return Optional.absent();
127 protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException {
129 final String netconfOperationName = operationElement.getName();
130 final String netconfOperationNamespace;
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);
139 final URI namespaceURI = createNsUri(netconfOperationNamespace);
140 final Optional<Module> moduleOptional = getModule(namespaceURI);
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);
148 final Optional<RpcDefinition> rpcDefinitionOptional = getRpcDefinitionFromModule(moduleOptional.get(), namespaceURI, netconfOperationName);
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);
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());
159 final CheckedFuture<DOMRpcResult, DOMRpcException> rpcFuture = rpcService.invokeRpc(schemaPath, inputNode);
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));
165 return (Element) transformNormalizedNode(document, result.getResult(), rpcDefinition.getOutput().getPath());
166 } catch (DOMRpcException e) {
167 throw NetconfDocumentedException.wrap(e);
172 public Document handle(final Document requestMessage,
173 final NetconfOperationChainedExecution subsequentOperation) throws NetconfDocumentedException {
175 final XmlElement requestElement = getRequestElementWithCheck(requestMessage);
177 final Document document = XmlUtil.newDocument();
179 final XmlElement operationElement = requestElement.getOnlyChildElement();
180 final Map<String, Attr> attributes = requestElement.getAttributes();
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));
185 if(XmlElement.fromDomElement(response).hasNamespace()) {
186 rpcReply.appendChild(response);
188 final NodeList list = response.getChildNodes();
189 if (list.getLength() == 0) {
190 rpcReply.appendChild(response);
192 while (list.getLength() != 0) {
193 rpcReply.appendChild(list.item(0));
198 for (Attr attribute : attributes.values()) {
199 rpcReply.setAttributeNode((Attr) document.importNode(attribute, true));
201 document.appendChild(rpcReply);
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));
209 final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
211 final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
212 schemaContext.getCurrentContext(), rpcOutputPath);
214 final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter);
216 writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
218 nnStreamWriter.close();
220 } catch (IOException | XMLStreamException e) {
221 LOG.warn("Error while closing streams", e);
224 return result.getNode();
227 private XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
229 return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
230 } catch (final XMLStreamException e) {
231 throw new RuntimeException(e);
235 private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
237 for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
238 nnWriter.write(child);
242 } catch (XMLStreamException | IOException e) {
243 Throwables.propagate(e);
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
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);