26ce72fdea1d4b0893324195a8837154e4ad64d3
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / 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 package org.opendaylight.netconf.mdsal.connector.ops.get;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import java.io.IOException;
12 import java.util.Optional;
13 import java.util.stream.Collectors;
14 import javax.xml.stream.XMLOutputFactory;
15 import javax.xml.stream.XMLStreamException;
16 import javax.xml.stream.XMLStreamWriter;
17 import javax.xml.transform.dom.DOMResult;
18 import org.opendaylight.netconf.api.DocumentedException;
19 import org.opendaylight.netconf.api.xml.XmlElement;
20 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
21 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
22 import org.opendaylight.netconf.mdsal.connector.ops.Datastore;
23 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
24 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
25 import org.opendaylight.yangtools.yang.common.ErrorTag;
26 import org.opendaylight.yangtools.yang.common.ErrorType;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
37 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
38 import org.opendaylight.yangtools.yang.data.api.schema.stream.YangInstanceIdentifierWriter;
39 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
40 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
41 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
43 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.Node;
47
48 public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
49     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
50     private static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.empty();
51     private static final String FILTER = "filter";
52
53     static {
54         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
55         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
56     }
57
58     protected final CurrentSchemaContext schemaContext;
59     private final FilterContentValidator validator;
60
61     public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) {
62         super(netconfSessionIdForReporting);
63         this.schemaContext = schemaContext;
64         this.validator = new FilterContentValidator(schemaContext);
65     }
66
67     protected Node transformNormalizedNode(final Document document, final NormalizedNode data,
68                                            final YangInstanceIdentifier dataRoot) {
69
70         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
71
72         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
73
74         final SchemaPath schemaPath = getSchemaPath(dataRoot);
75         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
76                 schemaContext.getCurrentContext(), schemaPath);
77
78         final DataSchemaNode dataSchemaNode;
79         if (dataRoot.isEmpty()) {
80             dataSchemaNode = schemaContext.getCurrentContext();
81         } else {
82             final Optional<DataSchemaNode> dataTreeChild =
83                     schemaContext.getCurrentContext().findDataTreeChild(schemaPath.getPathFromRoot());
84             dataSchemaNode = dataTreeChild.orElseThrow(
85                     () -> new IllegalArgumentException("Unable to find schema node for " + dataRoot));
86         }
87
88         try (var yiidWriter = YangInstanceIdentifierWriter.open(nnStreamWriter,
89                 (DataNodeContainer) dataSchemaNode, dataRoot)) {
90             try (var nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true)) {
91                 if (data instanceof ContainerNode) {
92                     writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
93                 } else if (data instanceof MapNode) {
94                     writeRootElement(xmlWriter, nnWriter, (MapNode) data);
95                 } else {
96                     throw new IllegalArgumentException("Unable to transform node of type: "
97                             + data.getClass().toString() + " offending node: " + data);
98                 }
99             }
100         } catch (IOException e) {
101             throw new RuntimeException(e);
102         }
103         return result.getNode();
104     }
105
106     private static XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
107         try {
108             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
109         } catch (final XMLStreamException e) {
110             throw new RuntimeException(e);
111         }
112     }
113
114     private static SchemaPath getSchemaPath(final YangInstanceIdentifier dataRoot) {
115
116         return SchemaPath.create(dataRoot.getPathArguments().stream()
117                 .filter(p -> !(p instanceof NodeIdentifierWithPredicates))
118                 .filter(p -> !(p instanceof AugmentationIdentifier))
119                 .map(PathArgument::getNodeType)
120                 .collect(Collectors.toList()), true);
121     }
122
123     private static void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter,
124                                          final ContainerNode data) throws IOException {
125         if (data.getIdentifier().getNodeType().equals(SchemaContext.NAME)) {
126             for (final DataContainerChild child : data.body()) {
127                 nnWriter.write(child);
128             }
129         } else {
130             nnWriter.write(data);
131         }
132     }
133
134     private static void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter,
135                                          final MapNode data) throws IOException {
136         if (data.getIdentifier().getNodeType().equals(SchemaContext.NAME)) {
137             for (final MapEntryNode child : data.body()) {
138                 nnWriter.write(child);
139             }
140         } else {
141             nnWriter.write(data);
142         }
143     }
144
145     protected Element serializeNodeWithParentStructure(final Document document, final YangInstanceIdentifier dataRoot,
146                                                        final NormalizedNode node) {
147         if (!dataRoot.equals(ROOT)) {
148             return (Element) transformNormalizedNode(document, node, dataRoot);
149         }
150         return (Element) transformNormalizedNode(document, node, ROOT);
151     }
152
153     /**
154      * Obtain data root according to filter from operation element.
155      *
156      * @param operationElement operation element
157      * @return if filter is present and not empty returns Optional of the InstanceIdentifier to the read location
158      *      in datastore. Empty filter returns Optional.absent() which should equal an empty &lt;data/&gt;
159      *      container in the response. If filter is not present we want to read the entire datastore - return ROOT.
160      * @throws DocumentedException if not possible to get identifier from filter
161      */
162     protected Optional<YangInstanceIdentifier> getDataRootFromFilter(final XmlElement operationElement)
163             throws DocumentedException {
164         final Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
165         if (filterElement.isPresent()) {
166             if (filterElement.get().getChildElements().size() == 0) {
167                 return Optional.empty();
168             }
169             return Optional.of(getInstanceIdentifierFromFilter(filterElement.get()));
170         }
171
172         return Optional.of(ROOT);
173     }
174
175     @VisibleForTesting
176     protected YangInstanceIdentifier getInstanceIdentifierFromFilter(final XmlElement filterElement)
177             throws DocumentedException {
178
179         if (filterElement.getChildElements().size() != 1) {
180             throw new DocumentedException("Multiple filter roots not supported yet",
181                     ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
182         }
183
184         final XmlElement element = filterElement.getOnlyChildElement();
185         return validator.validate(element);
186     }
187
188     protected static final class GetConfigExecution {
189         private final Optional<Datastore> datastore;
190
191         GetConfigExecution(final Optional<Datastore> datastore) {
192             this.datastore = datastore;
193         }
194
195         static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws DocumentedException {
196             try {
197                 validateInputRpc(xml, operationName);
198             } catch (final DocumentedException e) {
199                 throw new DocumentedException("Incorrect RPC: " + e.getMessage(), e, e.getErrorType(), e.getErrorTag(),
200                         e.getErrorSeverity(), e.getErrorInfo());
201             }
202
203             final Optional<Datastore> sourceDatastore;
204             try {
205                 sourceDatastore = parseSource(xml);
206             } catch (final DocumentedException e) {
207                 throw new DocumentedException("Get-config source attribute error: " + e.getMessage(), e,
208                         e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
209             }
210
211             return new GetConfigExecution(sourceDatastore);
212         }
213
214         private static Optional<Datastore> parseSource(final XmlElement xml) throws DocumentedException {
215             final Optional<XmlElement> sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY,
216                     XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
217             return sourceElement.isPresent()
218                     ? Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName()))
219                     : Optional.empty();
220         }
221
222         private static void validateInputRpc(final XmlElement xml, final String operationName) throws
223                 DocumentedException {
224             xml.checkName(operationName);
225             xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
226         }
227
228         public Optional<Datastore> getDatastore() {
229             return datastore;
230         }
231     }
232 }