Migrate XML output to yang-data-codec-xml
[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
9 package org.opendaylight.netconf.mdsal.connector.ops.get;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Throwables;
14 import com.google.common.collect.Iterables;
15 import java.io.IOException;
16 import javax.xml.stream.XMLOutputFactory;
17 import javax.xml.stream.XMLStreamException;
18 import javax.xml.stream.XMLStreamWriter;
19 import javax.xml.transform.dom.DOMResult;
20 import org.opendaylight.controller.config.util.xml.DocumentedException;
21 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
22 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
23 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
24 import org.opendaylight.controller.config.util.xml.XmlElement;
25 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
26 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
27 import org.opendaylight.netconf.mdsal.connector.ops.Datastore;
28 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
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.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         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
66
67         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
68
69         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
70                 schemaContext.getCurrentContext(), getSchemaPath(dataRoot));
71
72         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
73
74         writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
75         return result.getNode();
76     }
77
78     private XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
79         try {
80             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
81         } catch (final XMLStreamException e) {
82             throw new RuntimeException(e);
83         }
84     }
85
86     private SchemaPath getSchemaPath(final YangInstanceIdentifier dataRoot) {
87         return SchemaPath.create(
88                 Iterables.transform(dataRoot.getPathArguments(), PathArgument::getNodeType), dataRoot.equals(ROOT));
89     }
90
91     private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter,
92                                   final ContainerNode data) {
93         try {
94             if (data.getNodeType().equals(SchemaContext.NAME)) {
95                 for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
96                     nnWriter.write(child);
97                 }
98             } else {
99                 nnWriter.write(data);
100             }
101             nnWriter.flush();
102             xmlWriter.flush();
103         } catch (XMLStreamException | IOException e) {
104             Throwables.propagate(e);
105         }
106     }
107
108     protected Element serializeNodeWithParentStructure(final Document document, final YangInstanceIdentifier dataRoot,
109                                                        final NormalizedNode node) {
110         if (!dataRoot.equals(ROOT)) {
111             return (Element) transformNormalizedNode(document,
112                     ImmutableNodes.fromInstanceId(schemaContext.getCurrentContext(), dataRoot, node),
113                     ROOT);
114         }
115         return (Element) transformNormalizedNode(document, node, ROOT);
116     }
117
118     /**
119      * Obtain data root according to filter from operation element.
120      *
121      * @param operationElement operation element
122      * @return if filter is present and not empty returns Optional of the InstanceIdentifier to the read location
123      *      in datastore. Empty filter returns Optional.absent() which should equal an empty &lt;data/&gt;
124      *      container in the response. If filter is not present we want to read the entire datastore - return ROOT.
125      * @throws DocumentedException if not possible to get identifier from filter
126      */
127     protected Optional<YangInstanceIdentifier> getDataRootFromFilter(final XmlElement operationElement)
128             throws DocumentedException {
129         final Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
130         if (filterElement.isPresent()) {
131             if (filterElement.get().getChildElements().size() == 0) {
132                 return Optional.absent();
133             }
134             return Optional.of(getInstanceIdentifierFromFilter(filterElement.get()));
135         }
136
137         return Optional.of(ROOT);
138     }
139
140     @VisibleForTesting
141     protected YangInstanceIdentifier getInstanceIdentifierFromFilter(final XmlElement filterElement)
142             throws DocumentedException {
143
144         if (filterElement.getChildElements().size() != 1) {
145             throw new DocumentedException("Multiple filter roots not supported yet",
146                     ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
147         }
148
149         final XmlElement element = filterElement.getOnlyChildElement();
150         return validator.validate(element);
151     }
152
153     protected static final class GetConfigExecution {
154         private final Optional<Datastore> datastore;
155
156         GetConfigExecution(final Optional<Datastore> datastore) {
157             this.datastore = datastore;
158         }
159
160         static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws DocumentedException {
161             try {
162                 validateInputRpc(xml, operationName);
163             } catch (final DocumentedException e) {
164                 throw new DocumentedException("Incorrect RPC: " + e.getMessage(), e.getErrorType(), e.getErrorTag(),
165                         e.getErrorSeverity(), e.getErrorInfo());
166             }
167
168             final Optional<Datastore> sourceDatastore;
169             try {
170                 sourceDatastore = parseSource(xml);
171             } catch (final DocumentedException e) {
172                 throw new DocumentedException("Get-config source attribute error: " + e.getMessage(), e.getErrorType(),
173                         e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
174             }
175
176             return new GetConfigExecution(sourceDatastore);
177         }
178
179         private static Optional<Datastore> parseSource(final XmlElement xml) throws DocumentedException {
180             final Optional<XmlElement> sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY,
181                     XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
182
183             return sourceElement.isPresent()
184                     ? Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName()))
185                     : Optional.<Datastore>absent();
186         }
187
188         private static void validateInputRpc(final XmlElement xml, final String operationName) throws
189                 DocumentedException {
190             xml.checkName(operationName);
191             xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
192         }
193
194         public Optional<Datastore> getDatastore() {
195             return datastore;
196         }
197
198     }
199
200 }