NPE in RaftActor#onGetOnDemandRaftStats
[controller.git] / opendaylight / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / controller / netconf / mdsal / connector / ops / get / AbstractGet.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.get;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Function;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Throwables;
15 import com.google.common.collect.Iterables;
16 import com.google.common.collect.Lists;
17 import java.io.IOException;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.util.Collections;
21 import javax.xml.stream.XMLOutputFactory;
22 import javax.xml.stream.XMLStreamException;
23 import javax.xml.stream.XMLStreamWriter;
24 import javax.xml.transform.dom.DOMResult;
25 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
26 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity;
27 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag;
28 import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType;
29 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
30 import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext;
31 import org.opendaylight.controller.netconf.mdsal.connector.ops.Datastore;
32 import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation;
33 import org.opendaylight.controller.netconf.util.xml.XmlElement;
34 import org.opendaylight.controller.sal.connect.netconf.util.InstanceIdToNodes;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
38 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
40 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
43 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
44 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
45 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
46 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
47 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.Module;
51 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
52 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.w3c.dom.Document;
56 import org.w3c.dom.Element;
57 import org.w3c.dom.Node;
58
59 public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
60
61     private static final Logger LOG = LoggerFactory.getLogger(AbstractGet.class);
62
63     protected static final String FILTER = "filter";
64     static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
65     protected final CurrentSchemaContext schemaContext;
66
67     public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
68         super(netconfSessionIdForReporting);
69         this.schemaContext = schemaContext;
70     }
71
72     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
73
74     static {
75         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
76         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
77     }
78
79     protected Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data, final YangInstanceIdentifier dataRoot) {
80
81         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
82
83         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
84
85         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
86                 schemaContext.getCurrentContext(), getSchemaPath(dataRoot));
87
88         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter);
89
90         writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
91         return result.getNode();
92     }
93
94
95     private XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
96         try {
97             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
98         } catch (final XMLStreamException e) {
99             throw new RuntimeException(e);
100         }
101     }
102
103     private static final Function<PathArgument, QName> PATH_ARG_TO_QNAME = new Function<YangInstanceIdentifier.PathArgument, QName>() {
104         @Override
105         public QName apply(final YangInstanceIdentifier.PathArgument input) {
106             return input.getNodeType();
107         }
108     };
109
110     private SchemaPath getSchemaPath(final YangInstanceIdentifier dataRoot) {
111         return SchemaPath.create(Iterables.transform(dataRoot.getPathArguments(), PATH_ARG_TO_QNAME), dataRoot.equals(ROOT));
112     }
113
114     // TODO this code is located in Restconf already
115     private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
116         try {
117             if (data.getNodeType().equals(SchemaContext.NAME)) {
118                 for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
119                     nnWriter.write(child);
120                 }
121             } else {
122                 nnWriter.write(data);
123             }
124             nnWriter.flush();
125             xmlWriter.flush();
126         } catch (XMLStreamException | IOException e) {
127             Throwables.propagate(e);
128         }
129     }
130
131     private DataSchemaNode getSchemaNodeFromNamespace(final XmlElement element) throws NetconfDocumentedException {
132
133         try {
134             final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(element.getNamespace()), null);
135             DataSchemaNode dataSchemaNode = module.getDataChildByName(element.getName());
136             if (dataSchemaNode != null) {
137                 return dataSchemaNode;
138             }
139         } catch (URISyntaxException e) {
140             LOG.debug("Error during parsing of element namespace, this should not happen since namespace of an xml " +
141                     "element is valid and if the xml was parsed then the URI should be as well");
142             throw new IllegalArgumentException("Unable to parse element namespace, this should not happen since " +
143                     "namespace of an xml element is valid and if the xml was parsed then the URI should be as well");
144         }
145         throw new NetconfDocumentedException("Unable to find node with namespace: " + element.getNamespace() + "in schema context: " + schemaContext.getCurrentContext().toString(),
146                 ErrorType.application,
147                 ErrorTag.unknown_namespace,
148                 ErrorSeverity.error);
149     }
150
151     protected Element serializeNodeWithParentStructure(Document document, YangInstanceIdentifier dataRoot, NormalizedNode node) {
152         if (!dataRoot.equals(ROOT)) {
153             return (Element) transformNormalizedNode(document,
154                     InstanceIdToNodes.serialize(schemaContext.getCurrentContext(), dataRoot, node),
155                     ROOT);
156         }
157         return  (Element) transformNormalizedNode(document, node, ROOT);
158     }
159
160     /**
161      *
162      * @param operationElement operation element
163      * @return if Filter is present and not empty returns Optional of the InstanceIdentifier to the read location in datastore.
164      *          empty filter returns Optional.absent() which should equal an empty <data/> container in the response.
165      *         if filter is not present we want to read the entire datastore - return ROOT.
166      * @throws NetconfDocumentedException
167      */
168     protected Optional<YangInstanceIdentifier> getDataRootFromFilter(XmlElement operationElement) throws NetconfDocumentedException {
169         Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
170         if (filterElement.isPresent()) {
171             if (filterElement.get().getChildElements().size() == 0) {
172                 return Optional.absent();
173             }
174             return Optional.of(getInstanceIdentifierFromFilter(filterElement.get()));
175         } else {
176             return Optional.of(ROOT);
177         }
178     }
179
180     @VisibleForTesting
181     protected YangInstanceIdentifier getInstanceIdentifierFromFilter(XmlElement filterElement) throws NetconfDocumentedException {
182
183         if (filterElement.getChildElements().size() != 1) {
184             throw new NetconfDocumentedException("Multiple filter roots not supported yet",
185                     ErrorType.application, ErrorTag.operation_not_supported, ErrorSeverity.error);
186         }
187
188         XmlElement element = filterElement.getOnlyChildElement();
189         DataSchemaNode schemaNode = getSchemaNodeFromNamespace(element);
190
191         return getReadPointFromNode(YangInstanceIdentifier.builder().build(), filterToNormalizedNode(element, schemaNode));
192     }
193
194     private YangInstanceIdentifier getReadPointFromNode(final YangInstanceIdentifier pathArg, final NormalizedNode nNode) {
195         final YangInstanceIdentifier path = pathArg.node(nNode.getIdentifier());
196         if (nNode instanceof DataContainerNode) {
197             DataContainerNode node = (DataContainerNode) nNode;
198             if (node.getValue().size() == 1) {
199                 return getReadPointFromNode(path, (NormalizedNode) Lists.newArrayList(node.getValue()).get(0));
200             }
201         }
202         return path;
203     }
204
205     private NormalizedNode filterToNormalizedNode(XmlElement element, DataSchemaNode schemaNode) throws NetconfDocumentedException {
206         DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory
207                 .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext());
208
209         final NormalizedNode parsedNode;
210
211         if (schemaNode instanceof ContainerSchemaNode) {
212             parsedNode = parserFactory.getContainerNodeParser().parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
213         } else if (schemaNode instanceof ListSchemaNode) {
214             parsedNode = parserFactory.getMapNodeParser().parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
215         } else {
216             throw new NetconfDocumentedException("Schema node of the top level element is not an instance of container or list",
217                     ErrorType.application, ErrorTag.unknown_element, ErrorSeverity.error);
218         }
219         return parsedNode;
220     }
221
222     protected static final class GetConfigExecution {
223
224         private final Optional<Datastore> datastore;
225         public GetConfigExecution(final Optional<Datastore> datastore) {
226             this.datastore = datastore;
227         }
228
229         public Optional<Datastore> getDatastore() {
230             return datastore;
231         }
232
233         static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws NetconfDocumentedException {
234             try {
235                 validateInputRpc(xml, operationName);
236             } catch (final NetconfDocumentedException e) {
237                 throw new NetconfDocumentedException("Incorrect RPC: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
238             }
239
240             final Optional<Datastore> sourceDatastore;
241             try {
242                 sourceDatastore = parseSource(xml);
243             } catch (final NetconfDocumentedException e) {
244                 throw new NetconfDocumentedException("Get-config source attribute error: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
245             }
246
247             return new GetConfigExecution(sourceDatastore);
248         }
249
250         private static Optional<Datastore> parseSource(final XmlElement xml) throws NetconfDocumentedException {
251             final Optional<XmlElement> sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY,
252                     XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
253
254             return  sourceElement.isPresent() ?
255                     Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName())) : Optional.<Datastore>absent();
256         }
257
258         private static void validateInputRpc(final XmlElement xml, String operationName) throws NetconfDocumentedException{
259             xml.checkName(operationName);
260             xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
261         }
262
263     }
264
265 }