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