Add XmlParserStream documentation
[yangtools.git] / yang / yang-data-codec-xml / src / main / java / org / opendaylight / yangtools / yang / data / codec / xml / XmlStreamUtils.java
1 /*
2  * Copyright (c) 2015 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
9 package org.opendaylight.yangtools.yang.data.codec.xml;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Verify;
15 import java.net.URI;
16 import java.util.Map.Entry;
17 import javax.annotation.Nonnull;
18 import javax.xml.stream.XMLStreamException;
19 import javax.xml.stream.XMLStreamWriter;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.common.QNameModule;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
23 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
24 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
27 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
29 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
30 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
31 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
32 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * Utility class for bridging JAXP Stream and YANG Data APIs. Note that the definition of this class
38  * by no means final and subject to change as more functionality is centralized here.
39  */
40 class XmlStreamUtils {
41     private static final Logger LOG = LoggerFactory.getLogger(XmlStreamUtils.class);
42     private final Optional<SchemaContext> schemaContext;
43
44     private XmlStreamUtils(final SchemaContext schemaContext) {
45         this.schemaContext = Optional.fromNullable(schemaContext);
46     }
47
48     static XmlStreamUtils create(final SchemaContext schemaContext) {
49         return new XmlStreamUtils(schemaContext);
50     }
51
52     @VisibleForTesting
53     static void writeAttribute(final XMLStreamWriter writer, final Entry<QName, String> attribute,
54                                final RandomPrefix randomPrefix) throws XMLStreamException {
55         final QName key = attribute.getKey();
56         final String prefix = randomPrefix.encodePrefix(key.getNamespace());
57         writer.writeAttribute("xmlns:" + prefix, key.getNamespace().toString());
58         writer.writeAttribute(prefix, key.getNamespace().toString(), key.getLocalName(), attribute.getValue());
59     }
60
61     /**
62      * Write a value into a XML stream writer. This method assumes the start and end of element is
63      * emitted by the caller.
64      *
65      * @param writer XML Stream writer
66      * @param schemaNode Schema node that describes the value
67      * @param value data value
68      * @param parent optional parameter of a module QName owning the leaf definition
69      * @throws XMLStreamException if an encoding problem occurs
70      */
71     void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final SchemaNode schemaNode,
72                            final Object value, final Optional<QNameModule> parent) throws XMLStreamException {
73         if (value == null) {
74             LOG.debug("Value of {}:{} is null, not encoding it", schemaNode.getQName().getNamespace(),
75                     schemaNode.getQName().getLocalName());
76             return;
77         }
78
79         Preconditions.checkArgument(schemaNode instanceof LeafSchemaNode || schemaNode instanceof LeafListSchemaNode,
80                 "Unable to write value for node %s, only nodes of type: leaf and leaf-list can be written at this point",
81                 schemaNode.getQName());
82
83         TypeDefinition<?> type = schemaNode instanceof LeafSchemaNode ?
84                 ((LeafSchemaNode) schemaNode).getType():
85                 ((LeafListSchemaNode) schemaNode).getType();
86
87         if (schemaContext.isPresent() && type instanceof LeafrefTypeDefinition) {
88             LeafrefTypeDefinition leafrefTypeDefinition = (LeafrefTypeDefinition) type;
89             type = SchemaContextUtil.getBaseTypeForLeafRef(leafrefTypeDefinition, schemaContext.get(), schemaNode);
90             Verify.verifyNotNull(type, "Unable to find base type for leafref node '%s'.", schemaNode.getPath());
91         }
92
93         writeValue(writer, type, value, parent);
94     }
95
96     void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final SchemaNode schemaNode,
97                            final Object value) throws XMLStreamException {
98         writeValue(writer, schemaNode, value, Optional.absent());
99     }
100
101     void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final SchemaNode schemaNode,
102                            final Object value, final QNameModule parent) throws XMLStreamException {
103         writeValue(writer, schemaNode, value, Optional.of(parent));
104     }
105
106     /**
107      * Write a value into a XML stream writer. This method assumes the start and end of element is
108      * emitted by the caller.
109      *
110      * @param writer XML Stream writer
111      * @param type data type. In case of leaf ref this should be the type of leaf being referenced
112      * @param value data value
113      * @param parent optional parameter of a module QName owning the leaf definition
114      * @throws XMLStreamException if an encoding problem occurs
115      */
116     void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final TypeDefinition<?> type,
117                            final Object value, final Optional<QNameModule> parent) throws XMLStreamException {
118         if (value == null) {
119             LOG.debug("Value of {}:{} is null, not encoding it", type.getQName().getNamespace(),
120                     type.getQName().getLocalName());
121             return;
122         }
123
124         if (type instanceof IdentityrefTypeDefinition) {
125             if (parent.isPresent()) {
126                 write(writer, (IdentityrefTypeDefinition) type, value, parent);
127             } else {
128                 write(writer, (IdentityrefTypeDefinition) type, value, Optional.absent());
129             }
130         } else if (type instanceof InstanceIdentifierTypeDefinition) {
131             write(writer, (InstanceIdentifierTypeDefinition) type, value);
132         } else {
133             final TypeDefinitionAwareCodec<Object, ?> codec = TypeDefinitionAwareCodec.from(type);
134             String text;
135             if (codec != null) {
136                 try {
137                     text = codec.serialize(value);
138                 } catch (ClassCastException e) {
139                     LOG.warn("Provided node value {} did not have type {} required by mapping. Using stream instead.",
140                             value, type, e);
141                     text = String.valueOf(value);
142                 }
143             } else {
144                 LOG.warn("Failed to find codec for {}, falling back to using stream", type);
145                 text = String.valueOf(value);
146             }
147             writer.writeCharacters(text);
148         }
149     }
150
151     void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final TypeDefinition<?> type,
152                            final Object value, final QNameModule parent) throws XMLStreamException {
153         writeValue(writer, type, value, Optional.of(parent));
154     }
155
156     void writeValue(@Nonnull final XMLStreamWriter writer, @Nonnull final TypeDefinition<?> type,
157                            final Object value) throws XMLStreamException {
158         writeValue(writer, type, value, Optional.absent());
159     }
160
161     @VisibleForTesting
162     static void write(@Nonnull final XMLStreamWriter writer, @Nonnull final IdentityrefTypeDefinition type,
163                       @Nonnull final Object value, final Optional<QNameModule>  parent) throws XMLStreamException {
164         if (value instanceof QName) {
165             final QName qname = (QName) value;
166             final String prefix = "x";
167
168             //in case parent is present and same as element namespace write value without namespace
169             if (parent.isPresent() && qname.getNamespace().equals(parent.get().getNamespace())){
170                 writer.writeCharacters(qname.getLocalName());
171             } else {
172                 final String ns = qname.getNamespace().toString();
173                 writer.writeNamespace(prefix, ns);
174                 writer.writeCharacters(prefix + ':' + qname.getLocalName());
175             }
176
177         } else {
178             LOG.debug("Value of {}:{} is not a QName but {}", type.getQName().getNamespace(),
179                     type.getQName().getLocalName(), value.getClass());
180             writer.writeCharacters(String.valueOf(value));
181         }
182     }
183
184     private void write(@Nonnull final XMLStreamWriter writer, @Nonnull final InstanceIdentifierTypeDefinition type,
185                        @Nonnull final Object value) throws XMLStreamException {
186         if (value instanceof YangInstanceIdentifier) {
187             writeInstanceIdentifier(writer, (YangInstanceIdentifier)value);
188         } else {
189             LOG.warn("Value of {}:{} is not an InstanceIdentifier but {}", type.getQName().getNamespace(),
190                     type.getQName().getLocalName(), value.getClass());
191             writer.writeCharacters(String.valueOf(value));
192         }
193     }
194
195     void writeInstanceIdentifier(final XMLStreamWriter writer, final YangInstanceIdentifier value)
196             throws XMLStreamException {
197         if (schemaContext.isPresent()) {
198             RandomPrefixInstanceIdentifierSerializer iiCodec =
199                     new RandomPrefixInstanceIdentifierSerializer(schemaContext.get());
200             String serializedValue = iiCodec.serialize(value);
201             writeNamespaceDeclarations(writer, iiCodec.getPrefixes());
202             writer.writeCharacters(serializedValue);
203         } else {
204             LOG.warn("Schema context not present in {}, serializing {} without schema.",this,value);
205             writeInstanceIdentifier(writer, value);
206         }
207     }
208
209     private static void writeNamespaceDeclarations(final XMLStreamWriter writer,
210                                                final Iterable<Entry<URI, String>> prefixes) throws XMLStreamException {
211         for (Entry<URI, String> e: prefixes) {
212             writer.writeNamespace(e.getValue(), e.getKey().toString());
213         }
214     }
215 }