Add support for attributes int NN stream writer
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / codec / xml / XMLStreamNormalizedNodeStreamWriter.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.yangtools.yang.data.impl.codec.xml;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Strings;
12 import java.io.IOException;
13 import java.io.StringWriter;
14 import java.util.Map;
15 import javax.xml.XMLConstants;
16 import javax.xml.stream.XMLStreamException;
17 import javax.xml.stream.XMLStreamWriter;
18 import org.opendaylight.yangtools.yang.common.QName;
19 import org.opendaylight.yangtools.yang.data.api.Node;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
24 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
25 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
26 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
27 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
33 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
35 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
36
37 /**
38  * A {@link NormalizedNodeStreamWriter} which translates the events into an
39  * {@link XMLStreamWriter}, resulting in a RFC 6020 XML encoding.
40  */
41 public final class XMLStreamNormalizedNodeStreamWriter implements NormalizedNodeStreamAttributeWriter {
42
43     private final XMLStreamWriter writer;
44     private final SchemaTracker tracker;
45     private final XmlStreamUtils streamUtils;
46     private RandomPrefix randomPrefix;
47
48     private XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer, final SchemaContext context, final SchemaPath path) {
49         this.writer = Preconditions.checkNotNull(writer);
50         this.tracker = SchemaTracker.create(context, path);
51         this.streamUtils = XmlStreamUtils.create(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, context);
52         randomPrefix = new RandomPrefix();
53     }
54
55     /**
56      * Create a new writer with the specified context as its root.
57      *
58      * @param writer Output {@link XMLStreamWriter}
59      * @param context Associated {@link SchemaContext}.
60      * @return A new {@link NormalizedNodeStreamWriter}
61      */
62     public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context) {
63         return create( writer, context, SchemaPath.ROOT);
64     }
65
66     /**
67      * Create a new writer with the specified context and rooted in the specified schema path
68      *
69      * @param writer Output {@link XMLStreamWriter}
70      * @param context Associated {@link SchemaContext}.
71      *
72      * @return A new {@link NormalizedNodeStreamWriter}
73      */
74     public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context, final SchemaPath path) {
75         return new XMLStreamNormalizedNodeStreamWriter(writer, context, path);
76     }
77
78     private void writeStartElement( QName qname) throws XMLStreamException {
79         String ns = qname.getNamespace().toString();
80         writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), ns);
81         if(writer.getNamespaceContext() != null) {
82             String parentNs = writer.getNamespaceContext().getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
83             if (!ns.equals(parentNs)) {
84                 writer.writeDefaultNamespace(ns);
85             }
86         }
87     }
88
89     private void writeElement(final QName qname, final TypeDefinition<?> type, final Object value) throws IOException {
90         try {
91             writeStartElement(qname);
92             if (value != null) {
93                 streamUtils.writeValue(writer, type, value);
94             }
95             writer.writeEndElement();
96         } catch (XMLStreamException e) {
97             throw new IOException("Failed to emit element", e);
98         }
99     }
100
101     private void writeElement(final QName qname, final SchemaNode schemaNode, final Object value) throws IOException {
102         try {
103             writeStartElement(qname);
104             if (value != null) {
105                 streamUtils.writeValue(writer, schemaNode, value);
106             }
107             writer.writeEndElement();
108         } catch (XMLStreamException e) {
109             throw new IOException("Failed to emit element", e);
110         }
111     }
112
113     private void writeElement(final QName qname, final SchemaNode schemaNode, final Object value, final Map<QName, String> attributes) throws IOException {
114         try {
115             writeStartElement(qname);
116             writeAttributes(attributes);
117             if (value != null) {
118                 streamUtils.writeValue(writer, schemaNode, value);
119             }
120             writer.writeEndElement();
121         } catch (XMLStreamException e) {
122             throw new IOException("Failed to emit element", e);
123         }
124     }
125
126     private void startElement(final QName qname) throws IOException {
127         try {
128             writeStartElement(qname);
129         } catch (XMLStreamException e) {
130             throw new IOException("Failed to start element", e);
131         }
132     }
133
134     private void startList(final NodeIdentifier name) {
135         tracker.startList(name);
136     }
137
138     private void startListItem(final PathArgument name) throws IOException {
139         tracker.startListItem(name);
140         startElement(name.getNodeType());
141     }
142
143     @Override
144     public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
145         final LeafSchemaNode schema = tracker.leafNode(name);
146         writeElement(schema.getQName(), schema, value);
147     }
148
149     @Override
150     public void leafNode(final NodeIdentifier name, final Object value, final Map<QName, String> attributes) throws IOException {
151         final LeafSchemaNode schema = tracker.leafNode(name);
152         writeElement(schema.getQName(), schema, value, attributes);
153     }
154
155     @Override
156     public void leafSetEntryNode(final Object value, final Map<QName, String> attributes) throws IOException, IllegalArgumentException {
157         final LeafListSchemaNode schema = tracker.leafSetEntryNode();
158         writeElement(schema.getQName(), schema, value, attributes);
159     }
160
161     @Override
162     public void startContainerNode(final NodeIdentifier name, final int childSizeHint, final Map<QName, String> attributes) throws IOException, IllegalArgumentException {
163         startContainerNode(name, childSizeHint);
164         writeAttributes(attributes);
165     }
166
167     @Override
168     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint, final Map<QName, String> attributes) throws IOException, IllegalStateException {
169         startUnkeyedListItem(name, childSizeHint);
170         writeAttributes(attributes);
171     }
172
173     @Override
174     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint, final Map<QName, String> attributes) throws IOException, IllegalArgumentException {
175         startMapEntryNode(identifier, childSizeHint);
176         writeAttributes(attributes);
177     }
178
179     private void writeAttributes(final Map<QName, String> attributes) throws IOException {
180         for (final Map.Entry<QName, String> qNameStringEntry : attributes.entrySet()) {
181             try {
182                 final String namespace = qNameStringEntry.getKey().getNamespace().toString();
183                 if(Strings.isNullOrEmpty(namespace)) {
184                     writer.writeAttribute(qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue());
185                 } else {
186                     final String prefix = randomPrefix.encodePrefix(qNameStringEntry.getKey().getNamespace());
187                     writer.writeAttribute(prefix, namespace, qNameStringEntry.getKey().getLocalName(), qNameStringEntry.getValue());
188                 }
189             } catch (final XMLStreamException e) {
190                 throw new IOException("Unable to emit attribute " + qNameStringEntry, e);
191             }
192         }
193     }
194
195     @Override
196     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) {
197         tracker.startLeafSet(name);
198     }
199
200     @Override
201     public void leafSetEntryNode(final Object value) throws IOException {
202         final LeafListSchemaNode schema = tracker.leafSetEntryNode();
203         writeElement(schema.getQName(), schema, value);
204     }
205
206     @Override
207     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
208         final SchemaNode schema = tracker.startContainerNode(name);
209         startElement(schema.getQName());
210     }
211
212     @Override
213     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) {
214         startList(name);
215     }
216
217     @Override
218     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
219         startListItem(name);
220     }
221
222     @Override
223     public void startMapNode(final NodeIdentifier name, final int childSizeHint) {
224         startList(name);
225     }
226
227     @Override
228     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint) throws IOException {
229         startListItem(identifier);
230     }
231
232     @Override
233     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) {
234         startList(name);
235     }
236
237     @Override
238     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
239         tracker.startChoiceNode(name);
240     }
241
242     @Override
243     public void startAugmentationNode(final AugmentationIdentifier identifier) {
244         tracker.startAugmentationNode(identifier);
245     }
246
247     @Override
248     public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException {
249         final AnyXmlSchemaNode schema = tracker.anyxmlNode(name);
250         final QName qname = schema.getQName();
251         try {
252             writeStartElement(qname);
253             if (value != null) {
254                 streamUtils.writeValue(writer, (Node<?>)value, schema);
255             }
256             writer.writeEndElement();
257         } catch (XMLStreamException e) {
258             throw new IOException("Failed to emit element", e);
259         }
260     }
261
262     @Override
263     public void endNode() throws IOException {
264         final Object schema = tracker.endNode();
265
266         try {
267             if (schema instanceof ListSchemaNode) {
268                 // For lists, we only emit end element on the inner frame
269                 final Object parent = tracker.getParent();
270                 if (parent == schema) {
271                     writer.writeEndElement();
272                 }
273             } else if (schema instanceof ContainerSchemaNode) {
274                 // Emit container end element
275                 writer.writeEndElement();
276             }
277         } catch (XMLStreamException e) {
278             throw new IOException("Failed to end element", e);
279         }
280     }
281
282     @Override
283     public void close() throws IOException {
284         try {
285             writer.close();
286         } catch (XMLStreamException e) {
287             throw new IOException("Failed to close writer", e);
288         }
289     }
290
291     @Override
292     public void flush() throws IOException {
293         try {
294             writer.flush();
295         } catch (XMLStreamException e) {
296             throw new IOException("Failed to flush writer", e);
297         }
298     }
299 }