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