BUG-1770: emit proper namespaces
[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 static javax.xml.XMLConstants.DEFAULT_NS_PREFIX;
11
12 import com.google.common.base.Preconditions;
13
14 import java.io.IOException;
15
16 import javax.xml.stream.XMLStreamException;
17 import javax.xml.stream.XMLStreamWriter;
18
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.data.api.Node;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
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 NormalizedNodeStreamWriter {
42     private static final XmlStreamUtils UTILS = XmlStreamUtils.create(XmlUtils.DEFAULT_XML_CODEC_PROVIDER);
43
44     private final XMLStreamWriter writer;
45     private final SchemaTracker tracker;
46
47     private XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer, final SchemaContext context, final SchemaPath path) {
48         this.writer = Preconditions.checkNotNull(writer);
49         this.tracker = SchemaTracker.create(context, path);
50     }
51
52     /**
53      * Create a new writer with the specified context as its root.
54      *
55      * @param writer Output {@link XMLStreamWriter}
56      * @param context Associated {@link SchemaContext}.
57      * @return A new {@link NormalizedNodeStreamWriter}
58      */
59     public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context) {
60         return create( writer, context, SchemaPath.ROOT);
61     }
62
63     /**
64      * Create a new writer with the specified context and rooted in the specified schema path
65      *
66      * @param writer Output {@link XMLStreamWriter}
67      * @param context Associated {@link SchemaContext}.
68      *
69      * @return A new {@link NormalizedNodeStreamWriter}
70      */
71     public static NormalizedNodeStreamWriter create(final XMLStreamWriter writer, final SchemaContext context, final SchemaPath path) {
72         return new XMLStreamNormalizedNodeStreamWriter(writer, context, path);
73     }
74
75     private void writeElement(final QName qname, final TypeDefinition<?> type, final Object value) throws IOException {
76         final String ns = qname.getNamespace().toString();
77
78         try {
79             if (value != null) {
80                 writer.writeStartElement(DEFAULT_NS_PREFIX, qname.getLocalName(), ns);
81                 UTILS.writeValue(writer, type, value);
82                 writer.writeEndElement();
83             } else {
84                 writer.writeEmptyElement(DEFAULT_NS_PREFIX, qname.getLocalName(), ns);
85             }
86         } catch (XMLStreamException e) {
87             throw new IOException("Failed to emit element", e);
88         }
89     }
90
91     private void startElement(final QName qname) throws IOException {
92         try {
93             writer.writeStartElement(DEFAULT_NS_PREFIX, qname.getLocalName(), qname.getNamespace().toString());
94         } catch (XMLStreamException e) {
95             throw new IOException("Failed to start element", e);
96         }
97     }
98
99     private void startList(final NodeIdentifier name) {
100         tracker.startList(name);
101     }
102
103     private void startListItem(final PathArgument name) throws IOException {
104         tracker.startListItem(name);
105         startElement(name.getNodeType());
106     }
107
108     @Override
109     public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
110         final LeafSchemaNode schema = tracker.leafNode(name);
111
112         writeElement(schema.getQName(), schema.getType(), value);
113     }
114
115     @Override
116     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) {
117         tracker.startLeafSet(name);
118     }
119
120     @Override
121     public void leafSetEntryNode(final Object value) throws IOException {
122         final LeafListSchemaNode schema = tracker.leafSetEntryNode();
123         writeElement(schema.getQName(), schema.getType(), value);
124     }
125
126     @Override
127     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
128         final SchemaNode schema = tracker.startContainerNode(name);
129         startElement(schema.getQName());
130     }
131
132     @Override
133     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) {
134         startList(name);
135     }
136
137     @Override
138     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
139         startListItem(name);
140     }
141
142     @Override
143     public void startMapNode(final NodeIdentifier name, final int childSizeHint) {
144         startList(name);
145     }
146
147     @Override
148     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint) throws IOException {
149         startListItem(identifier);
150     }
151
152     @Override
153     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) {
154         startList(name);
155     }
156
157     @Override
158     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
159         tracker.startChoiceNode(name);
160     }
161
162     @Override
163     public void startAugmentationNode(final AugmentationIdentifier identifier) {
164         tracker.startAugmentationNode(identifier);
165     }
166
167     @Override
168     public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException {
169         final AnyXmlSchemaNode schema = tracker.anyxmlNode(name);
170         final QName qname = schema.getQName();
171         final String ns = qname.getNamespace().toString();
172
173         try {
174             if (value != null) {
175                 writer.writeStartElement(ns, qname.getLocalName());
176                 UTILS.writeValue(writer, (Node<?>)value, schema);
177                 writer.writeEndElement();
178             } else {
179                 writer.writeEmptyElement(ns, qname.getLocalName());
180             }
181         } catch (XMLStreamException e) {
182             throw new IOException("Failed to emit element", e);
183         }
184     }
185
186     @Override
187     public void endNode() throws IOException {
188         final Object schema = tracker.endNode();
189
190         try {
191             if (schema instanceof ListSchemaNode) {
192                 // For lists, we only emit end element on the inner frame
193                 final Object parent = tracker.getParent();
194                 if (parent == schema) {
195                     writer.writeEndElement();
196                 }
197             } else if (schema instanceof ContainerSchemaNode) {
198                 // Emit container end element
199                 writer.writeEndElement();
200             }
201         } catch (XMLStreamException e) {
202             throw new IOException("Failed to end element", e);
203         }
204     }
205
206     @Override
207     public void close() throws IOException {
208         try {
209             writer.close();
210         } catch (XMLStreamException e) {
211             throw new IOException("Failed to close writer", e);
212         }
213     }
214
215     @Override
216     public void flush() throws IOException {
217         try {
218             writer.flush();
219         } catch (XMLStreamException e) {
220             throw new IOException("Failed to flush writer", e);
221         }
222     }
223 }