Remove SchemaNode#getPath from XML writer in rfc8040
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / jersey / providers / XmlNormalizedNodeBodyWriter.java
1 /*
2  * Copyright (c) 2016 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.restconf.nb.rfc8040.jersey.providers;
9
10 import java.io.IOException;
11 import java.io.OutputStream;
12 import java.lang.annotation.Annotation;
13 import java.lang.reflect.Type;
14 import java.nio.charset.StandardCharsets;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.stream.Collectors;
19 import javanet.staxutils.IndentingXMLStreamWriter;
20 import javax.ws.rs.Produces;
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.Provider;
25 import javax.xml.XMLConstants;
26 import javax.xml.stream.FactoryConfigurationError;
27 import javax.xml.stream.XMLOutputFactory;
28 import javax.xml.stream.XMLStreamException;
29 import javax.xml.stream.XMLStreamWriter;
30 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
31 import org.opendaylight.restconf.nb.rfc8040.DepthParam;
32 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
33 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
34 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
38 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
42 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
43 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
44 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
45 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
46 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
47 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
48
49 @Provider
50 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
51 public class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
52     private static final XMLOutputFactory XML_FACTORY;
53
54     static {
55         XML_FACTORY = XMLOutputFactory.newFactory();
56         XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
57     }
58
59     @Override
60     public void writeTo(final NormalizedNodePayload context,
61                         final Class<?> type,
62                         final Type genericType,
63                         final Annotation[] annotations,
64                         final MediaType mediaType,
65                         final MultivaluedMap<String, Object> httpHeaders,
66                         final OutputStream entityStream) throws IOException, WebApplicationException {
67         final InstanceIdentifierContext<?> pathContext = context.getInstanceIdentifierContext();
68         if (context.getData() == null) {
69             return;
70         }
71         if (httpHeaders != null) {
72             for (final Map.Entry<String, Object> entry : context.getNewHeaders().entrySet()) {
73                 httpHeaders.add(entry.getKey(), entry.getValue());
74             }
75         }
76
77         XMLStreamWriter xmlWriter;
78         try {
79             xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
80
81             final var prettyPrint = context.getWriterParameters().prettyPrint();
82             if (prettyPrint != null && prettyPrint.value()) {
83                 xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
84             }
85         } catch (final XMLStreamException | FactoryConfigurationError e) {
86             throw new IllegalStateException(e);
87         }
88         final NormalizedNode data = context.getData();
89
90         writeNormalizedNode(xmlWriter, pathContext, data, context.getWriterParameters().depth(),
91                 context.getWriterParameters().fields());
92     }
93
94     private static void writeNormalizedNode(final XMLStreamWriter xmlWriter,
95             final InstanceIdentifierContext<?> pathContext, final NormalizedNode data, final DepthParam depth,
96             final List<Set<QName>> fields) throws IOException {
97         final RestconfNormalizedNodeWriter nnWriter;
98         final EffectiveModelContext schemaCtx = pathContext.getSchemaContext();
99
100         if (pathContext.getSchemaNode() instanceof RpcDefinition) {
101             /*
102              *  RpcDefinition is not supported as initial codec in XMLStreamWriter,
103              *  so we need to emit initial output declaration..
104              */
105             final RpcDefinition rpc = (RpcDefinition) pathContext.getSchemaNode();
106             final SchemaPath rpcPath = SchemaPath.of(Absolute.of(rpc.getQName(), rpc.getOutput().getQName()));
107             nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, rpcPath, depth, fields);
108             writeElements(xmlWriter, nnWriter, (ContainerNode) data);
109         } else if (pathContext.getSchemaNode() instanceof ActionDefinition) {
110             /*
111              *  ActionDefinition is not supported as initial codec in XMLStreamWriter,
112              *  so we need to emit initial output declaration..
113              */
114             final ActionDefinition actDef = (ActionDefinition) pathContext.getSchemaNode();
115             final List<QName> qNames = pathContext.getInstanceIdentifier().getPathArguments().stream()
116                     .filter(arg -> !(arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates))
117                     .filter(arg -> !(arg instanceof YangInstanceIdentifier.AugmentationIdentifier))
118                     .map(PathArgument::getNodeType)
119                     .collect(Collectors.toList());
120             qNames.add(actDef.getQName());
121             qNames.add(actDef.getOutput().getQName());
122             final SchemaPath actPath = SchemaPath.of(Absolute.of(qNames));
123             nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, actPath, depth, fields);
124             writeElements(xmlWriter, nnWriter, (ContainerNode) data);
125         } else {
126             final boolean isRoot = pathContext.getInstanceIdentifier().isEmpty();
127             if (isRoot) {
128                 nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, SchemaPath.ROOT, depth, fields);
129             } else {
130                 final List<QName> qNames = pathContext.getInstanceIdentifier().getPathArguments().stream()
131                         .filter(arg -> !(arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates))
132                         .filter(arg -> !(arg instanceof YangInstanceIdentifier.AugmentationIdentifier))
133                         .map(PathArgument::getNodeType)
134                         .collect(Collectors.toList());
135                 final SchemaPath path = SchemaPath.of(Absolute.of(qNames));
136                 nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, path.getParent(), depth, fields);
137             }
138
139             if (data instanceof MapEntryNode) {
140                 // Restconf allows returning one list item. We need to wrap it
141                 // in map node in order to serialize it properly
142                 nnWriter.write(ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
143                     .addChild((MapEntryNode) data)
144                     .build());
145             } else if (isRoot) {
146                 if (data instanceof ContainerNode && ((ContainerNode) data).isEmpty()) {
147                     writeEmptyDataNode(xmlWriter, data);
148                 } else {
149                     writeAndWrapInDataNode(xmlWriter, nnWriter, data);
150                 }
151             } else {
152                 nnWriter.write(data);
153             }
154         }
155
156         nnWriter.flush();
157     }
158
159     private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
160             final EffectiveModelContext schemaContext, final SchemaPath schemaPath, final DepthParam depth,
161             final List<Set<QName>> fields) {
162         return ParameterAwareNormalizedNodeWriter.forStreamWriter(
163             XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, schemaContext, schemaPath), depth, fields);
164     }
165
166     private static void writeAndWrapInDataNode(final XMLStreamWriter xmlWriter,
167             final RestconfNormalizedNodeWriter nnWriter, final NormalizedNode data) throws IOException {
168         final QName nodeType = data.getIdentifier().getNodeType();
169         final String namespace = nodeType.getNamespace().toString();
170         try {
171             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
172             xmlWriter.writeDefaultNamespace(namespace);
173             nnWriter.write(data);
174             xmlWriter.writeEndElement();
175             xmlWriter.flush();
176         } catch (XMLStreamException e) {
177             throw new IOException("Failed to write elements", e);
178         }
179     }
180
181     private static void writeEmptyDataNode(final XMLStreamWriter xmlWriter, final NormalizedNode data)
182             throws IOException {
183         final QName nodeType = data.getIdentifier().getNodeType();
184         final String namespace = nodeType.getNamespace().toString();
185         try {
186             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
187             xmlWriter.writeDefaultNamespace(namespace);
188             xmlWriter.writeEndElement();
189             xmlWriter.flush();
190         } catch (XMLStreamException e) {
191             throw new IOException("Failed to write elements", e);
192         }
193     }
194
195     private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
196             final ContainerNode data) throws IOException {
197         final QName nodeType = data.getIdentifier().getNodeType();
198         final String namespace = nodeType.getNamespace().toString();
199         try {
200             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
201             xmlWriter.writeDefaultNamespace(namespace);
202             for (final NormalizedNode child : data.body()) {
203                 nnWriter.write(child);
204             }
205             nnWriter.flush();
206             xmlWriter.writeEndElement();
207             xmlWriter.flush();
208         } catch (final XMLStreamException e) {
209             throw new IOException("Failed to write elements", e);
210         }
211     }
212 }