Use keyed list as a selection node in filter
[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.Function;
13 import com.google.common.base.Optional;
14 import com.google.common.base.Throwables;
15 import com.google.common.collect.Iterables;
16 import java.io.IOException;
17 import javax.xml.stream.XMLOutputFactory;
18 import javax.xml.stream.XMLStreamException;
19 import javax.xml.stream.XMLStreamWriter;
20 import javax.xml.transform.dom.DOMResult;
21 import org.opendaylight.controller.config.util.xml.DocumentedException;
22 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
23 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
24 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
25 import org.opendaylight.controller.config.util.xml.XmlElement;
26 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
27 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
28 import org.opendaylight.netconf.mdsal.connector.ops.Datastore;
29 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
37 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
38 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
39 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
40 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
41 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.Node;
47
48 public abstract class AbstractGet extends AbstractSingletonNetconfOperation {
49
50     private static final Logger LOG = LoggerFactory.getLogger(AbstractGet.class);
51
52     protected static final String FILTER = "filter";
53     static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build();
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     private static final XMLOutputFactory XML_OUTPUT_FACTORY;
64
65     static {
66         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
67         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
68     }
69
70     protected Node transformNormalizedNode(final Document document, final NormalizedNode<?, ?> data, final YangInstanceIdentifier dataRoot) {
71
72         final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY));
73
74         final XMLStreamWriter xmlWriter = getXmlStreamWriter(result);
75
76         final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
77                 schemaContext.getCurrentContext(), getSchemaPath(dataRoot));
78
79         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
80
81         writeRootElement(xmlWriter, nnWriter, (ContainerNode) data);
82         return result.getNode();
83     }
84
85
86     private 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 final Function<PathArgument, QName> PATH_ARG_TO_QNAME = new Function<YangInstanceIdentifier.PathArgument, QName>() {
95         @Override
96         public QName apply(final YangInstanceIdentifier.PathArgument input) {
97             return input.getNodeType();
98         }
99     };
100
101     private SchemaPath getSchemaPath(final YangInstanceIdentifier dataRoot) {
102         return SchemaPath.create(Iterables.transform(dataRoot.getPathArguments(), PATH_ARG_TO_QNAME), dataRoot.equals(ROOT));
103     }
104
105     // TODO this code is located in Restconf already
106     private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) {
107         try {
108             if (data.getNodeType().equals(SchemaContext.NAME)) {
109                 for (final DataContainerChild<? extends PathArgument, ?> child : data.getValue()) {
110                     nnWriter.write(child);
111                 }
112             } else {
113                 nnWriter.write(data);
114             }
115             nnWriter.flush();
116             xmlWriter.flush();
117         } catch (XMLStreamException | IOException e) {
118             Throwables.propagate(e);
119         }
120     }
121
122     protected Element serializeNodeWithParentStructure(Document document, YangInstanceIdentifier dataRoot, NormalizedNode node) {
123         if (!dataRoot.equals(ROOT)) {
124             return (Element) transformNormalizedNode(document,
125                     ImmutableNodes.fromInstanceId(schemaContext.getCurrentContext(), dataRoot, node),
126                     ROOT);
127         }
128         return  (Element) transformNormalizedNode(document, node, ROOT);
129     }
130
131     /**
132      *
133      * @param operationElement operation element
134      * @return if Filter is present and not empty returns Optional of the InstanceIdentifier to the read location in datastore.
135      *          empty filter returns Optional.absent() which should equal an empty &lt;data/&gt; container in the response.
136      *         if filter is not present we want to read the entire datastore - return ROOT.
137      * @throws DocumentedException
138      */
139     protected Optional<YangInstanceIdentifier> getDataRootFromFilter(XmlElement operationElement) throws DocumentedException {
140         Optional<XmlElement> filterElement = operationElement.getOnlyChildElementOptionally(FILTER);
141         if (filterElement.isPresent()) {
142             if (filterElement.get().getChildElements().size() == 0) {
143                 return Optional.absent();
144             }
145             return Optional.of(getInstanceIdentifierFromFilter(filterElement.get()));
146         } else {
147             return Optional.of(ROOT);
148         }
149     }
150
151     @VisibleForTesting
152     protected YangInstanceIdentifier getInstanceIdentifierFromFilter(XmlElement filterElement) throws DocumentedException {
153
154         if (filterElement.getChildElements().size() != 1) {
155             throw new DocumentedException("Multiple filter roots not supported yet",
156                     ErrorType.application, ErrorTag.operation_not_supported, ErrorSeverity.error);
157         }
158
159         XmlElement element = filterElement.getOnlyChildElement();
160         return validator.validate(element);
161     }
162
163     protected static final class GetConfigExecution {
164
165         private final Optional<Datastore> datastore;
166         public GetConfigExecution(final Optional<Datastore> datastore) {
167             this.datastore = datastore;
168         }
169
170         public Optional<Datastore> getDatastore() {
171             return datastore;
172         }
173
174         static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws DocumentedException {
175             try {
176                 validateInputRpc(xml, operationName);
177             } catch (final DocumentedException e) {
178                 throw new DocumentedException("Incorrect RPC: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
179             }
180
181             final Optional<Datastore> sourceDatastore;
182             try {
183                 sourceDatastore = parseSource(xml);
184             } catch (final DocumentedException e) {
185                 throw new DocumentedException("Get-config source attribute error: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo());
186             }
187
188             return new GetConfigExecution(sourceDatastore);
189         }
190
191         private static Optional<Datastore> parseSource(final XmlElement xml) throws DocumentedException {
192             final Optional<XmlElement> sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY,
193                     XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
194
195             return  sourceElement.isPresent() ?
196                     Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName())) : Optional.<Datastore>absent();
197         }
198
199         private static void validateInputRpc(final XmlElement xml, String operationName) throws DocumentedException{
200             xml.checkName(operationName);
201             xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0);
202         }
203
204     }
205
206 }