Add netconf.api.NamespaceURN
[netconf.git] / plugins / netconf-server-mdsal / src / main / java / org / opendaylight / netconf / server / mdsal / operations / 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.server.mdsal.operations;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import java.io.IOException;
14 import java.util.Optional;
15 import javax.xml.stream.XMLOutputFactory;
16 import javax.xml.stream.XMLStreamException;
17 import javax.xml.stream.XMLStreamWriter;
18 import javax.xml.transform.dom.DOMResult;
19 import org.opendaylight.netconf.api.DocumentedException;
20 import org.opendaylight.netconf.api.NamespaceURN;
21 import org.opendaylight.netconf.api.xml.XmlElement;
22 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
23 import org.opendaylight.netconf.server.api.operations.AbstractSingletonNetconfOperation;
24 import org.opendaylight.netconf.server.mdsal.CurrentSchemaContext;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.SessionIdType;
26 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
27 import org.opendaylight.yangtools.yang.common.ErrorTag;
28 import org.opendaylight.yangtools.yang.common.ErrorType;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
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.w3c.dom.Document;
38 import org.w3c.dom.Element;
39 import org.w3c.dom.Node;
40
41 // FIXME: seal when we have JDK17+
42 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     private final CurrentSchemaContext schemaContext;
52     private final FilterContentValidator validator;
53
54     AbstractGet(final SessionIdType sessionId, final CurrentSchemaContext schemaContext) {
55         super(sessionId);
56         this.schemaContext = schemaContext;
57         validator = new FilterContentValidator(schemaContext);
58     }
59
60     // FIXME: throw a DocumentedException
61     private Node transformNormalizedNode(final Document document, final NormalizedNode data,
62                                          final YangInstanceIdentifier dataRoot) {
63         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
64         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
65         final EffectiveModelContext currentContext = schemaContext.getCurrentContext();
66
67         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
68             currentContext);
69
70         try {
71             if (dataRoot.isEmpty()) {
72                 writeRoot(nnStreamWriter, data);
73             } else {
74                 write(nnStreamWriter, currentContext, dataRoot.coerceParent(), data);
75             }
76         } catch (IOException e) {
77             // FIXME: throw DocumentedException
78             throw new IllegalStateException(e);
79         }
80
81         return result.getNode();
82     }
83
84     private static void write(final NormalizedNodeStreamWriter nnStreamWriter,
85             final EffectiveModelContext currentContext, final YangInstanceIdentifier parent, final NormalizedNode data)
86                 throws IOException {
87         try (var yiidWriter = YangInstanceIdentifierWriter.open(nnStreamWriter, currentContext, parent)) {
88             try (var nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true)) {
89                 nnWriter.write(data);
90             }
91         }
92     }
93
94     private static void writeRoot(final NormalizedNodeStreamWriter nnStreamWriter, final NormalizedNode data)
95             throws IOException {
96         checkArgument(data instanceof ContainerNode, "Unexpected root data %s", data);
97
98         try (var nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true)) {
99             for (var child : ((ContainerNode) data).body()) {
100                 nnWriter.write(child);
101             }
102         }
103     }
104
105     private static XMLStreamWriter getXmlStreamWriter(final DOMResult result) {
106         try {
107             return XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
108         } catch (final XMLStreamException e) {
109             // FIXME: throw DocumentedException
110             throw new IllegalStateException(e);
111         }
112     }
113
114     final Element serializeNodeWithParentStructure(final Document document, final YangInstanceIdentifier dataRoot,
115                                                    final NormalizedNode node) {
116         return (Element) transformNormalizedNode(document, node, dataRoot);
117     }
118
119     /**
120      * Obtain data root according to filter from operation element.
121      *
122      * @param operationElement operation element
123      * @return if filter is present and not empty returns Optional of the InstanceIdentifier to the read location
124      *      in datastore. Empty filter returns Optional.absent() which should equal an empty <data/>
125      *      container in the response. If filter is not present we want to read the entire datastore - return ROOT.
126      * @throws DocumentedException if not possible to get identifier from filter
127      */
128     final Optional<YangInstanceIdentifier> getDataRootFromFilter(final XmlElement operationElement)
129             throws DocumentedException {
130         final var optFilterElement = operationElement.getOnlyChildElementOptionally(FILTER);
131         if (optFilterElement.isEmpty()) {
132             return Optional.of(YangInstanceIdentifier.empty());
133         }
134
135         final var filterElement = optFilterElement.orElseThrow();
136         if (filterElement.getChildElements().isEmpty()) {
137             return Optional.empty();
138         }
139         return Optional.of(getInstanceIdentifierFromFilter(filterElement));
140     }
141
142     @VisibleForTesting
143     protected final YangInstanceIdentifier getInstanceIdentifierFromFilter(final XmlElement filterElement)
144             throws DocumentedException {
145
146         if (filterElement.getChildElements().size() != 1) {
147             throw new DocumentedException("Multiple filter roots not supported yet",
148                     ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
149         }
150
151         final XmlElement element = filterElement.getOnlyChildElement();
152         return validator.validate(element);
153     }
154
155     protected static final class GetConfigExecution {
156         private final Optional<Datastore> datastore;
157
158         GetConfigExecution(final Optional<Datastore> datastore) {
159             this.datastore = datastore;
160         }
161
162         static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws DocumentedException {
163             try {
164                 validateInputRpc(xml, operationName);
165             } catch (final DocumentedException e) {
166                 throw new DocumentedException("Incorrect RPC: " + e.getMessage(), e, e.getErrorType(), e.getErrorTag(),
167                         e.getErrorSeverity(), e.getErrorInfo());
168             }
169
170             final Optional<Datastore> sourceDatastore;
171             try {
172                 sourceDatastore = parseSource(xml);
173             } catch (final DocumentedException e) {
174                 throw new DocumentedException("Get-config source attribute error: " + e.getMessage(), e,
175                         e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
176             }
177
178             return new GetConfigExecution(sourceDatastore);
179         }
180
181         private static Optional<Datastore> parseSource(final XmlElement xml) throws DocumentedException {
182             final Optional<XmlElement> sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY,
183                 NamespaceURN.BASE);
184             return sourceElement.isEmpty() ? Optional.empty()
185                 : Optional.of(Datastore.valueOf(sourceElement.orElseThrow().getOnlyChildElement().getName()));
186         }
187
188         private static void validateInputRpc(final XmlElement xml, final String operationName)
189                 throws DocumentedException {
190             xml.checkName(operationName);
191             xml.checkNamespace(NamespaceURN.BASE);
192         }
193
194         public Optional<Datastore> getDatastore() {
195             return datastore;
196         }
197     }
198 }