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