Share common parts of AbstractNormalizedNodeBodyWriter
[netconf.git] / restconf / restconf-nb / 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.nio.charset.StandardCharsets;
13 import java.util.List;
14 import java.util.Set;
15 import javanet.staxutils.IndentingXMLStreamWriter;
16 import javax.ws.rs.Produces;
17 import javax.ws.rs.core.MediaType;
18 import javax.ws.rs.ext.Provider;
19 import javax.xml.XMLConstants;
20 import javax.xml.stream.FactoryConfigurationError;
21 import javax.xml.stream.XMLOutputFactory;
22 import javax.xml.stream.XMLStreamException;
23 import javax.xml.stream.XMLStreamWriter;
24 import org.opendaylight.restconf.api.query.DepthParam;
25 import org.opendaylight.restconf.nb.rfc8040.MediaTypes;
26 import org.opendaylight.restconf.nb.rfc8040.jersey.providers.api.RestconfNormalizedNodeWriter;
27 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
28 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
34 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
35 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
36 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
37 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
38 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
39
40 @Provider
41 @Produces({ MediaTypes.APPLICATION_YANG_DATA_XML, MediaType.APPLICATION_XML, MediaType.TEXT_XML })
42 public final class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWriter {
43     private static final XMLOutputFactory XML_FACTORY;
44
45     static {
46         XML_FACTORY = XMLOutputFactory.newFactory();
47         XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
48     }
49
50     @Override
51     void writeTo(final InstanceIdentifierContext context, final QueryParameters writerParameters,
52             final NormalizedNode data, final OutputStream entityStream) throws IOException {
53         XMLStreamWriter xmlWriter;
54         try {
55             xmlWriter = XML_FACTORY.createXMLStreamWriter(entityStream, StandardCharsets.UTF_8.name());
56
57             final var prettyPrint = writerParameters.prettyPrint();
58             if (prettyPrint != null && prettyPrint.value()) {
59                 xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
60             }
61         } catch (XMLStreamException | FactoryConfigurationError e) {
62             throw new IllegalStateException(e);
63         }
64
65         writeNormalizedNode(xmlWriter, context, data, writerParameters.depth(), writerParameters.fields());
66     }
67
68     private static void writeNormalizedNode(final XMLStreamWriter xmlWriter,
69             final InstanceIdentifierContext pathContext, final NormalizedNode data, final DepthParam depth,
70             final List<Set<QName>> fields) throws IOException {
71         final var schemaNode = pathContext.getSchemaNode();
72         if (schemaNode instanceof RpcDefinition rpc) {
73             // RpcDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit initial output
74             // declaration.
75             final var stack = SchemaInferenceStack.of(pathContext.getSchemaContext());
76             stack.enterSchemaTree(rpc.getQName());
77             stack.enterSchemaTree(rpc.getOutput().getQName());
78
79             final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth, fields);
80             writeElements(xmlWriter, nnWriter, (ContainerNode) data);
81             nnWriter.flush();
82         } else if (schemaNode instanceof ActionDefinition action) {
83             // ActionDefinition is not supported as initial codec in XMLStreamWriter, so we need to emit initial output
84             // declaration.
85             final var stack = pathContext.inference().toSchemaInferenceStack();
86             stack.enterSchemaTree(action.getOutput().getQName());
87
88             final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth, fields);
89             writeElements(xmlWriter, nnWriter, (ContainerNode) data);
90             nnWriter.flush();
91         } else {
92             final var stack = pathContext.inference().toSchemaInferenceStack();
93             final boolean isRoot;
94             if (!stack.isEmpty()) {
95                 stack.exit();
96                 isRoot = false;
97             } else {
98                 isRoot = true;
99             }
100
101             final var nnWriter = createNormalizedNodeWriter(xmlWriter, stack.toInference(), depth, fields);
102             if (data instanceof MapEntryNode mapEntry) {
103                 // Restconf allows returning one list item. We need to wrap it
104                 // in map node in order to serialize it properly
105                 nnWriter.write(ImmutableNodes.mapNodeBuilder(data.name().getNodeType()).addChild(mapEntry).build());
106             } else if (isRoot) {
107                 if (data instanceof ContainerNode container && container.isEmpty()) {
108                     writeEmptyDataNode(xmlWriter, container);
109                 } else {
110                     writeAndWrapInDataNode(xmlWriter, nnWriter, data);
111                 }
112             } else {
113                 nnWriter.write(data);
114             }
115             nnWriter.flush();
116         }
117     }
118
119     private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
120             final Inference inference, final DepthParam depth,
121             final List<Set<QName>> fields) {
122         return ParameterAwareNormalizedNodeWriter.forStreamWriter(
123             XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, inference), depth, fields);
124     }
125
126     private static void writeAndWrapInDataNode(final XMLStreamWriter xmlWriter,
127             final RestconfNormalizedNodeWriter nnWriter, final NormalizedNode data) throws IOException {
128         final QName nodeType = data.name().getNodeType();
129         final String namespace = nodeType.getNamespace().toString();
130         try {
131             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
132             xmlWriter.writeDefaultNamespace(namespace);
133             nnWriter.write(data);
134             xmlWriter.writeEndElement();
135             xmlWriter.flush();
136         } catch (XMLStreamException e) {
137             throw new IOException("Failed to write elements", e);
138         }
139     }
140
141     private static void writeEmptyDataNode(final XMLStreamWriter xmlWriter, final ContainerNode data)
142             throws IOException {
143         final QName nodeType = data.name().getNodeType();
144         final String namespace = nodeType.getNamespace().toString();
145         try {
146             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
147             xmlWriter.writeDefaultNamespace(namespace);
148             xmlWriter.writeEndElement();
149             xmlWriter.flush();
150         } catch (XMLStreamException e) {
151             throw new IOException("Failed to write elements", e);
152         }
153     }
154
155     private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
156             final ContainerNode data) throws IOException {
157         final QName nodeType = data.name().getNodeType();
158         final String namespace = nodeType.getNamespace().toString();
159         try {
160             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
161             xmlWriter.writeDefaultNamespace(namespace);
162             for (var child : data.body()) {
163                 nnWriter.write(child);
164             }
165             nnWriter.flush();
166             xmlWriter.writeEndElement();
167             xmlWriter.flush();
168         } catch (final XMLStreamException e) {
169             throw new IOException("Failed to write elements", e);
170         }
171     }
172 }