Split Restconf implementations (draft02 and RFC) - features
[netconf.git] / restconf / restconf-nb-bierman02 / src / main / java / org / opendaylight / netconf / sal / rest / impl / XmlNormalizedNodeBodyReader.java
1 /*
2  * Copyright (c) 2014 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.sal.rest.impl;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.Iterables;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.lang.annotation.Annotation;
15 import java.lang.reflect.Type;
16 import java.net.URISyntaxException;
17 import java.util.ArrayDeque;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Deque;
21 import java.util.List;
22 import javax.ws.rs.Consumes;
23 import javax.ws.rs.WebApplicationException;
24 import javax.ws.rs.core.MediaType;
25 import javax.ws.rs.core.MultivaluedMap;
26 import javax.ws.rs.ext.MessageBodyReader;
27 import javax.ws.rs.ext.Provider;
28 import javax.xml.parsers.ParserConfigurationException;
29 import javax.xml.stream.XMLStreamException;
30 import javax.xml.transform.dom.DOMSource;
31 import org.opendaylight.netconf.sal.rest.api.Draft02;
32 import org.opendaylight.netconf.sal.rest.api.RestconfService;
33 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
34 import org.opendaylight.restconf.common.context.NormalizedNodeContext;
35 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
36 import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
37 import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
38 import org.opendaylight.yangtools.util.xml.UntrustedXML;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
44 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
45 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
46 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
47 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
48 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
49 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
50 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
51 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
54 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
57 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
58 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.w3c.dom.Document;
62 import org.xml.sax.SAXException;
63
64 @Provider
65 @Consumes({ Draft02.MediaTypes.DATA + RestconfService.XML, Draft02.MediaTypes.OPERATION + RestconfService.XML,
66         MediaType.APPLICATION_XML, MediaType.TEXT_XML })
67 public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsProvider
68         implements MessageBodyReader<NormalizedNodeContext> {
69
70     private static final Logger LOG = LoggerFactory.getLogger(XmlNormalizedNodeBodyReader.class);
71
72     @Override
73     public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
74             final MediaType mediaType) {
75         return true;
76     }
77
78     @SuppressWarnings("checkstyle:IllegalCatch")
79     @Override
80     public NormalizedNodeContext readFrom(final Class<NormalizedNodeContext> type, final Type genericType,
81             final Annotation[] annotations, final MediaType mediaType,
82             final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException,
83             WebApplicationException {
84         try {
85             return readFrom(entityStream);
86         } catch (final RestconfDocumentedException e) {
87             throw e;
88         } catch (final Exception e) {
89             LOG.debug("Error parsing xml input", e);
90
91             throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
92                     ErrorTag.MALFORMED_MESSAGE, e);
93         }
94     }
95
96     private NormalizedNodeContext readFrom(final InputStream entityStream) throws IOException, SAXException,
97             XMLStreamException, ParserConfigurationException, URISyntaxException {
98         final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
99
100         if (entityStream.available() < 1) {
101             // represent empty nopayload input
102             return new NormalizedNodeContext(path, null);
103         }
104
105         final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
106         return parse(path, doc);
107     }
108
109     private NormalizedNodeContext parse(final InstanceIdentifierContext<?> pathContext,final Document doc)
110             throws XMLStreamException, IOException, ParserConfigurationException, SAXException, URISyntaxException {
111         final SchemaNode schemaNodeContext = pathContext.getSchemaNode();
112         DataSchemaNode schemaNode;
113         boolean isRpc = false;
114         if (schemaNodeContext instanceof RpcDefinition) {
115             schemaNode = ((RpcDefinition) schemaNodeContext).getInput();
116             isRpc = true;
117         } else if (schemaNodeContext instanceof DataSchemaNode) {
118             schemaNode = (DataSchemaNode) schemaNodeContext;
119         } else {
120             throw new IllegalStateException("Unknown SchemaNode");
121         }
122
123         final String docRootElm = doc.getDocumentElement().getLocalName();
124         final String docRootNamespace = doc.getDocumentElement().getNamespaceURI();
125         final List<YangInstanceIdentifier.PathArgument> iiToDataList = new ArrayList<>();
126
127         if (isPost() && !isRpc) {
128             final Deque<Object> foundSchemaNodes = findPathToSchemaNodeByName(schemaNode, docRootElm, docRootNamespace);
129             if (foundSchemaNodes.isEmpty()) {
130                 throw new IllegalStateException(String.format("Child \"%s\" was not found in parent schema node \"%s\"",
131                         docRootElm, schemaNode.getQName()));
132             }
133             while (!foundSchemaNodes.isEmpty()) {
134                 final Object child = foundSchemaNodes.pop();
135                 if (child instanceof AugmentationSchema) {
136                     final AugmentationSchema augmentSchemaNode = (AugmentationSchema) child;
137                     iiToDataList.add(SchemaUtils.getNodeIdentifierForAugmentation(augmentSchemaNode));
138                 } else if (child instanceof DataSchemaNode) {
139                     schemaNode = (DataSchemaNode) child;
140                     iiToDataList.add(new YangInstanceIdentifier.NodeIdentifier(schemaNode.getQName()));
141                 }
142             }
143         // PUT
144         } else if (!isRpc) {
145             final QName scQName = schemaNode.getQName();
146             Preconditions.checkState(
147                     docRootElm.equals(scQName.getLocalName())
148                             && docRootNamespace.equals(scQName.getNamespace().toASCIIString()),
149                     String.format("Not correct message root element \"%s\", should be \"%s\"",
150                             docRootElm, scQName));
151         }
152
153         NormalizedNode<?, ?> parsed;
154         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
155         final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
156
157         if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
158                 || schemaNode instanceof LeafSchemaNode) {
159             final XmlParserStream xmlParser = XmlParserStream.create(writer, pathContext.getSchemaContext(),
160                     schemaNode);
161             xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
162             parsed = resultHolder.getResult();
163
164             // When parsing an XML source with a list root node
165             // the new XML parser always returns a MapNode with one MapEntryNode inside.
166             // However, the old XML parser returned a MapEntryNode directly in this place.
167             // Therefore we now have to extract the MapEntryNode from the parsed MapNode.
168             if (parsed instanceof MapNode) {
169                 final MapNode mapNode = (MapNode) parsed;
170                 // extracting the MapEntryNode
171                 parsed = mapNode.getValue().iterator().next();
172             }
173
174             if (schemaNode instanceof  ListSchemaNode && isPost()) {
175                 iiToDataList.add(parsed.getIdentifier());
176             }
177         } else {
178             LOG.warn("Unknown schema node extension {} was not parsed", schemaNode.getClass());
179             parsed = null;
180         }
181
182         final YangInstanceIdentifier fullIIToData = YangInstanceIdentifier.create(Iterables.concat(
183                 pathContext.getInstanceIdentifier().getPathArguments(), iiToDataList));
184
185         final InstanceIdentifierContext<? extends SchemaNode> outIIContext = new InstanceIdentifierContext<>(
186                 fullIIToData, pathContext.getSchemaNode(), pathContext.getMountPoint(), pathContext.getSchemaContext());
187
188         return new NormalizedNodeContext(outIIContext, parsed);
189     }
190
191     private static Deque<Object> findPathToSchemaNodeByName(final DataSchemaNode schemaNode, final String elementName,
192                                                             final String namespace) {
193         final Deque<Object> result = new ArrayDeque<>();
194         final ArrayList<ChoiceSchemaNode> choiceSchemaNodes = new ArrayList<>();
195         final Collection<DataSchemaNode> children = ((DataNodeContainer) schemaNode).getChildNodes();
196         for (final DataSchemaNode child : children) {
197             if (child instanceof ChoiceSchemaNode) {
198                 choiceSchemaNodes.add((ChoiceSchemaNode) child);
199             } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)
200                     && child.getQName().getNamespace().toString().equalsIgnoreCase(namespace)) {
201                 // add child to result
202                 result.push(child);
203
204                 // find augmentation
205                 if (child.isAugmenting()) {
206                     final AugmentationSchema augment = findCorrespondingAugment(schemaNode, child);
207                     if (augment != null) {
208                         result.push(augment);
209                     }
210                 }
211
212                 // return result
213                 return result;
214             }
215         }
216
217         for (final ChoiceSchemaNode choiceNode : choiceSchemaNodes) {
218             for (final ChoiceCaseNode caseNode : choiceNode.getCases()) {
219                 final Deque<Object> resultFromRecursion = findPathToSchemaNodeByName(caseNode, elementName, namespace);
220                 if (!resultFromRecursion.isEmpty()) {
221                     resultFromRecursion.push(choiceNode);
222                     if (choiceNode.isAugmenting()) {
223                         final AugmentationSchema augment = findCorrespondingAugment(schemaNode, choiceNode);
224                         if (augment != null) {
225                             resultFromRecursion.push(augment);
226                         }
227                     }
228                     return resultFromRecursion;
229                 }
230             }
231         }
232         return result;
233     }
234
235     private static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent,
236                                                                final DataSchemaNode child) {
237         if ((parent instanceof AugmentationTarget) && !(parent instanceof ChoiceSchemaNode)) {
238             for (final AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
239                 final DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
240                 if (childInAugmentation != null) {
241                     return augmentation;
242                 }
243             }
244         }
245         return null;
246     }
247 }
248