Bump versions to 4.0.0-SNAPSHOT
[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 static com.google.common.base.Preconditions.checkState;
11
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.ArrayList;
18 import java.util.List;
19 import java.util.Optional;
20 import javax.ws.rs.Consumes;
21 import javax.ws.rs.WebApplicationException;
22 import javax.ws.rs.core.MediaType;
23 import javax.ws.rs.core.MultivaluedMap;
24 import javax.ws.rs.ext.MessageBodyReader;
25 import javax.ws.rs.ext.Provider;
26 import javax.xml.parsers.ParserConfigurationException;
27 import javax.xml.stream.XMLStreamException;
28 import javax.xml.transform.dom.DOMSource;
29 import org.opendaylight.netconf.sal.rest.api.Draft02;
30 import org.opendaylight.netconf.sal.rest.api.RestconfService;
31 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
32 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
33 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
34 import org.opendaylight.restconf.common.util.RestUtil;
35 import org.opendaylight.yangtools.util.xml.UntrustedXML;
36 import org.opendaylight.yangtools.yang.common.ErrorTag;
37 import org.opendaylight.yangtools.yang.common.ErrorType;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.common.XMLNamespace;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
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.util.DataSchemaContextTree;
48 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
49 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
53 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 import org.w3c.dom.Document;
59 import org.xml.sax.SAXException;
60
61 @Provider
62 @Consumes({
63     Draft02.MediaTypes.DATA + RestconfService.XML,
64     Draft02.MediaTypes.OPERATION + RestconfService.XML,
65     MediaType.APPLICATION_XML,
66     MediaType.TEXT_XML
67 })
68 public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsProvider
69         implements MessageBodyReader<NormalizedNodeContext> {
70
71     private static final Logger LOG = LoggerFactory.getLogger(XmlNormalizedNodeBodyReader.class);
72
73     public XmlNormalizedNodeBodyReader(final ControllerContext controllerContext) {
74         super(controllerContext);
75     }
76
77     @Override
78     public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
79             final MediaType mediaType) {
80         return true;
81     }
82
83     @SuppressWarnings("checkstyle:IllegalCatch")
84     @Override
85     public NormalizedNodeContext readFrom(final Class<NormalizedNodeContext> type, final Type genericType,
86             final Annotation[] annotations, final MediaType mediaType,
87             final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws
88         WebApplicationException {
89         try {
90             return readFrom(entityStream);
91         } catch (final RestconfDocumentedException e) {
92             throw e;
93         } catch (final Exception e) {
94             LOG.debug("Error parsing xml input", e);
95             RestconfDocumentedException.throwIfYangError(e);
96             throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
97                     ErrorTag.MALFORMED_MESSAGE, e);
98         }
99     }
100
101     private NormalizedNodeContext readFrom(final InputStream entityStream) throws IOException, SAXException,
102             XMLStreamException, ParserConfigurationException, URISyntaxException {
103         final InstanceIdentifierContext path = getInstanceIdentifierContext();
104         final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
105         if (nonEmptyInputStreamOptional.isEmpty()) {
106             // represent empty nopayload input
107             return new NormalizedNodeContext(path, null);
108         }
109
110         final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
111         return parse(path, doc);
112     }
113
114     private NormalizedNodeContext parse(final InstanceIdentifierContext pathContext,final Document doc)
115             throws XMLStreamException, IOException, SAXException, URISyntaxException {
116         final SchemaNode schemaNodeContext = pathContext.getSchemaNode();
117         DataSchemaNode schemaNode;
118         final List<PathArgument> iiToDataList = new ArrayList<>();
119         Inference inference;
120         if (schemaNodeContext instanceof RpcDefinition) {
121             schemaNode = ((RpcDefinition) schemaNodeContext).getInput();
122             inference = pathContext.inference();
123         } else if (schemaNodeContext instanceof DataSchemaNode) {
124             schemaNode = (DataSchemaNode) schemaNodeContext;
125
126             final String docRootElm = doc.getDocumentElement().getLocalName();
127             final XMLNamespace docRootNamespace = XMLNamespace.of(doc.getDocumentElement().getNamespaceURI());
128
129             if (isPost()) {
130                 final var context = pathContext.getSchemaContext();
131                 final var it = context.findModuleStatements(docRootNamespace).iterator();
132                 checkState(it.hasNext(), "Failed to find module for %s", docRootNamespace);
133                 final var qname = QName.create(it.next().localQNameModule(), docRootElm);
134
135                 final var nodeAndStack = DataSchemaContextTree.from(context)
136                     .enterPath(pathContext.getInstanceIdentifier()).orElseThrow();
137
138                 final var stack = nodeAndStack.stack();
139                 var current = nodeAndStack.node();
140                 do {
141                     final var next = current.enterChild(stack, qname);
142                     checkState(next != null, "Child \"%s\" was not found in parent schema node \"%s\"", qname,
143                         schemaNode);
144                     iiToDataList.add(next.getIdentifier());
145                     schemaNode = next.getDataSchemaNode();
146                     current = next;
147                 } while (current.isMixin());
148
149                 // We need to unwind the last identifier if it a NodeIdentifierWithPredicates, as it does not have
150                 // any predicates at all. The real identifier is then added below
151                 if (stack.currentStatement() instanceof ListEffectiveStatement) {
152                     iiToDataList.remove(iiToDataList.size() - 1);
153                 }
154
155                 inference = stack.toInference();
156
157             } else {
158                 // PUT
159                 final QName scQName = schemaNode.getQName();
160                 checkState(docRootElm.equals(scQName.getLocalName()) && docRootNamespace.equals(scQName.getNamespace()),
161                     "Not correct message root element \"%s\", should be \"%s\"", docRootElm, scQName);
162                 inference = pathContext.inference();
163             }
164         } else {
165             throw new IllegalStateException("Unknown SchemaNode");
166         }
167
168
169         NormalizedNode parsed;
170         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
171         final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
172
173         if (schemaNode instanceof ContainerLike || schemaNode instanceof ListSchemaNode
174                 || schemaNode instanceof LeafSchemaNode) {
175             final XmlParserStream xmlParser = XmlParserStream.create(writer, inference);
176             xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
177             parsed = resultHolder.getResult();
178
179             // When parsing an XML source with a list root node
180             // the new XML parser always returns a MapNode with one MapEntryNode inside.
181             // However, the old XML parser returned a MapEntryNode directly in this place.
182             // Therefore we now have to extract the MapEntryNode from the parsed MapNode.
183             if (parsed instanceof MapNode) {
184                 final MapNode mapNode = (MapNode) parsed;
185                 // extracting the MapEntryNode
186                 parsed = mapNode.body().iterator().next();
187             }
188
189             if (schemaNode instanceof ListSchemaNode && isPost()) {
190                 iiToDataList.add(parsed.getIdentifier());
191             }
192         } else {
193             LOG.warn("Unknown schema node extension {} was not parsed", schemaNode.getClass());
194             parsed = null;
195         }
196
197         return new NormalizedNodeContext(pathContext.withConcatenatedArgs(iiToDataList), parsed);
198     }
199 }
200