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