Handle empty type in NormalizedNode streaming
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / cluster / datastore / node / utils / stream / AbstractNormalizedNodeDataOutput.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 package org.opendaylight.controller.cluster.datastore.node.utils.stream;
9
10 import com.google.common.base.Preconditions;
11 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
12 import java.io.DataOutput;
13 import java.io.IOException;
14 import java.io.OutputStream;
15 import java.io.StringWriter;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Collection;
18 import java.util.Map;
19 import java.util.Set;
20 import javax.xml.transform.TransformerException;
21 import javax.xml.transform.TransformerFactory;
22 import javax.xml.transform.TransformerFactoryConfigurationError;
23 import javax.xml.transform.dom.DOMSource;
24 import javax.xml.transform.stream.StreamResult;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
34 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
35 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 abstract class AbstractNormalizedNodeDataOutput implements NormalizedNodeDataOutput, NormalizedNodeStreamWriter {
40     private static final Logger LOG = LoggerFactory.getLogger(AbstractNormalizedNodeDataOutput.class);
41
42     private final DataOutput output;
43
44     private NormalizedNodeWriter normalizedNodeWriter;
45     private boolean headerWritten;
46     private QName lastLeafSetQName;
47
48     AbstractNormalizedNodeDataOutput(final DataOutput output) {
49         this.output = Preconditions.checkNotNull(output);
50     }
51
52     private void ensureHeaderWritten() throws IOException {
53         if (!headerWritten) {
54             output.writeByte(TokenTypes.SIGNATURE_MARKER);
55             output.writeShort(streamVersion());
56             headerWritten = true;
57         }
58     }
59
60     protected abstract short streamVersion();
61
62     protected abstract void writeQName(QName qname) throws IOException;
63
64     protected abstract void writeString(String string) throws IOException;
65
66     @Override
67     public final void write(final int value) throws IOException {
68         ensureHeaderWritten();
69         output.write(value);
70     }
71
72     @Override
73     public final void write(final byte[] bytes) throws IOException {
74         ensureHeaderWritten();
75         output.write(bytes);
76     }
77
78     @Override
79     public final void write(final byte[] bytes, final int off, final int len) throws IOException {
80         ensureHeaderWritten();
81         output.write(bytes, off, len);
82     }
83
84     @Override
85     public final void writeBoolean(final boolean value) throws IOException {
86         ensureHeaderWritten();
87         output.writeBoolean(value);
88     }
89
90     @Override
91     public final void writeByte(final int value) throws IOException {
92         ensureHeaderWritten();
93         output.writeByte(value);
94     }
95
96     @Override
97     public final void writeShort(final int value) throws IOException {
98         ensureHeaderWritten();
99         output.writeShort(value);
100     }
101
102     @Override
103     public final void writeChar(final int value) throws IOException {
104         ensureHeaderWritten();
105         output.writeChar(value);
106     }
107
108     @Override
109     public final void writeInt(final int value) throws IOException {
110         ensureHeaderWritten();
111         output.writeInt(value);
112     }
113
114     @Override
115     public final void writeLong(final long value) throws IOException {
116         ensureHeaderWritten();
117         output.writeLong(value);
118     }
119
120     @Override
121     public final void writeFloat(final float value) throws IOException {
122         ensureHeaderWritten();
123         output.writeFloat(value);
124     }
125
126     @Override
127     public final void writeDouble(final double value) throws IOException {
128         ensureHeaderWritten();
129         output.writeDouble(value);
130     }
131
132     @Override
133     public final void writeBytes(final String str) throws IOException {
134         ensureHeaderWritten();
135         output.writeBytes(str);
136     }
137
138     @Override
139     public final void writeChars(final String str) throws IOException {
140         ensureHeaderWritten();
141         output.writeChars(str);
142     }
143
144     @Override
145     public final void writeUTF(final String str) throws IOException {
146         ensureHeaderWritten();
147         output.writeUTF(str);
148     }
149
150     private NormalizedNodeWriter normalizedNodeWriter() {
151         if (normalizedNodeWriter == null) {
152             normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(this);
153         }
154
155         return normalizedNodeWriter;
156     }
157
158     @Override
159     public void writeNormalizedNode(final NormalizedNode<?, ?> node) throws IOException {
160         ensureHeaderWritten();
161         normalizedNodeWriter().write(node);
162     }
163
164     @Override
165     public void leafNode(final NodeIdentifier name, final Object value) throws IOException, IllegalArgumentException {
166         Preconditions.checkNotNull(name, "Node identifier should not be null");
167         LOG.trace("Writing a new leaf node");
168         startNode(name.getNodeType(), NodeTypes.LEAF_NODE);
169
170         writeObject(value);
171     }
172
173     @Override
174     public void startLeafSet(final NodeIdentifier name, final int childSizeHint)
175
176             throws IOException, IllegalArgumentException {
177         Preconditions.checkNotNull(name, "Node identifier should not be null");
178         LOG.trace("Starting a new leaf set");
179
180         lastLeafSetQName = name.getNodeType();
181         startNode(name.getNodeType(), NodeTypes.LEAF_SET);
182     }
183
184     @Override
185     public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint)
186             throws IOException, IllegalArgumentException {
187         Preconditions.checkNotNull(name, "Node identifier should not be null");
188         LOG.trace("Starting a new ordered leaf set");
189
190         lastLeafSetQName = name.getNodeType();
191         startNode(name.getNodeType(), NodeTypes.ORDERED_LEAF_SET);
192     }
193
194     @Override
195     public void leafSetEntryNode(final QName name, final Object value) throws IOException, IllegalArgumentException {
196         LOG.trace("Writing a new leaf set entry node");
197
198         output.writeByte(NodeTypes.LEAF_SET_ENTRY_NODE);
199
200         // lastLeafSetQName is set if the parent LeafSetNode was previously written. Otherwise this is a
201         // stand alone LeafSetEntryNode so write out it's name here.
202         if (lastLeafSetQName == null) {
203             writeQName(name);
204         }
205
206         writeObject(value);
207     }
208
209     @Override
210     public void startContainerNode(final NodeIdentifier name, final int childSizeHint)
211             throws IOException, IllegalArgumentException {
212         Preconditions.checkNotNull(name, "Node identifier should not be null");
213
214         LOG.trace("Starting a new container node");
215
216         startNode(name.getNodeType(), NodeTypes.CONTAINER_NODE);
217     }
218
219     @Override
220     public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint)
221             throws IOException, IllegalArgumentException {
222         Preconditions.checkNotNull(name, "Node identifier should not be null");
223
224         LOG.trace("Starting a new yang modeled anyXml node");
225
226         startNode(name.getNodeType(), NodeTypes.YANG_MODELED_ANY_XML_NODE);
227     }
228
229     @Override
230     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint)
231             throws IOException, IllegalArgumentException {
232         Preconditions.checkNotNull(name, "Node identifier should not be null");
233         LOG.trace("Starting a new unkeyed list");
234
235         startNode(name.getNodeType(), NodeTypes.UNKEYED_LIST);
236     }
237
238     @Override
239     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint)
240             throws IOException, IllegalStateException {
241         Preconditions.checkNotNull(name, "Node identifier should not be null");
242         LOG.trace("Starting a new unkeyed list item");
243
244         startNode(name.getNodeType(), NodeTypes.UNKEYED_LIST_ITEM);
245     }
246
247     @Override
248     public void startMapNode(final NodeIdentifier name, final int childSizeHint)
249             throws IOException, IllegalArgumentException {
250         Preconditions.checkNotNull(name, "Node identifier should not be null");
251         LOG.trace("Starting a new map node");
252
253         startNode(name.getNodeType(), NodeTypes.MAP_NODE);
254     }
255
256     @Override
257     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
258             throws IOException, IllegalArgumentException {
259         Preconditions.checkNotNull(identifier, "Node identifier should not be null");
260         LOG.trace("Starting a new map entry node");
261         startNode(identifier.getNodeType(), NodeTypes.MAP_ENTRY_NODE);
262
263         writeKeyValueMap(identifier.getKeyValues());
264
265     }
266
267     @Override
268     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint)
269             throws IOException, IllegalArgumentException {
270         Preconditions.checkNotNull(name, "Node identifier should not be null");
271         LOG.trace("Starting a new ordered map node");
272
273         startNode(name.getNodeType(), NodeTypes.ORDERED_MAP_NODE);
274     }
275
276     @Override
277     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint)
278             throws IOException, IllegalArgumentException {
279         Preconditions.checkNotNull(name, "Node identifier should not be null");
280         LOG.trace("Starting a new choice node");
281
282         startNode(name.getNodeType(), NodeTypes.CHOICE_NODE);
283     }
284
285     @Override
286     public void startAugmentationNode(final AugmentationIdentifier identifier)
287             throws IOException, IllegalArgumentException {
288         Preconditions.checkNotNull(identifier, "Node identifier should not be null");
289         LOG.trace("Starting a new augmentation node");
290
291         output.writeByte(NodeTypes.AUGMENTATION_NODE);
292         writeQNameSet(identifier.getPossibleChildNames());
293     }
294
295     @Override
296     public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException, IllegalArgumentException {
297         Preconditions.checkNotNull(name, "Node identifier should not be null");
298         LOG.trace("Writing any xml node");
299
300         startNode(name.getNodeType(), NodeTypes.ANY_XML_NODE);
301
302         try {
303             StreamResult xmlOutput = new StreamResult(new StringWriter());
304             TransformerFactory.newInstance().newTransformer().transform((DOMSource)value, xmlOutput);
305             writeObject(xmlOutput.getWriter().toString());
306         } catch (TransformerException | TransformerFactoryConfigurationError e) {
307             throw new IOException("Error writing anyXml", e);
308         }
309     }
310
311     @Override
312     public void endNode() throws IOException, IllegalStateException {
313         LOG.trace("Ending the node");
314         lastLeafSetQName = null;
315         output.writeByte(NodeTypes.END_NODE);
316     }
317
318     @Override
319     public void close() throws IOException {
320         flush();
321     }
322
323     @Override
324     public void flush() throws IOException {
325         if (output instanceof OutputStream) {
326             ((OutputStream)output).flush();
327         }
328     }
329
330     private void startNode(final QName qname, final byte nodeType) throws IOException {
331         Preconditions.checkNotNull(qname, "QName of node identifier should not be null.");
332
333         ensureHeaderWritten();
334
335         // First write the type of node
336         output.writeByte(nodeType);
337         // Write Start Tag
338         writeQName(qname);
339     }
340
341     private void writeObjSet(final Set<?> set) throws IOException {
342         output.writeInt(set.size());
343         for (Object o : set) {
344             Preconditions.checkArgument(o instanceof String, "Expected value type to be String but was %s (%s)",
345                 o.getClass(), o);
346
347             writeString((String) o);
348         }
349     }
350
351     @Override
352     public void writeSchemaPath(final SchemaPath path) throws IOException {
353         ensureHeaderWritten();
354         output.writeBoolean(path.isAbsolute());
355
356         final Collection<QName> qnames = path.getPath();
357         output.writeInt(qnames.size());
358         for (QName qname : qnames) {
359             writeQName(qname);
360         }
361     }
362
363     @Override
364     public void writeYangInstanceIdentifier(final YangInstanceIdentifier identifier) throws IOException {
365         ensureHeaderWritten();
366         writeYangInstanceIdentifierInternal(identifier);
367     }
368
369     private void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
370         Collection<PathArgument> pathArguments = identifier.getPathArguments();
371         output.writeInt(pathArguments.size());
372
373         for (PathArgument pathArgument : pathArguments) {
374             writePathArgument(pathArgument);
375         }
376     }
377
378     @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
379             justification = "The casts in the switch clauses are indirectly confirmed via the determination of 'type'.")
380     @Override
381     public void writePathArgument(final PathArgument pathArgument) throws IOException {
382
383         byte type = PathArgumentTypes.getSerializablePathArgumentType(pathArgument);
384
385         output.writeByte(type);
386
387         switch (type) {
388             case PathArgumentTypes.NODE_IDENTIFIER:
389
390                 NodeIdentifier nodeIdentifier = (NodeIdentifier) pathArgument;
391
392                 writeQName(nodeIdentifier.getNodeType());
393                 break;
394
395             case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
396
397                 NodeIdentifierWithPredicates nodeIdentifierWithPredicates =
398                     (NodeIdentifierWithPredicates) pathArgument;
399                 writeQName(nodeIdentifierWithPredicates.getNodeType());
400
401                 writeKeyValueMap(nodeIdentifierWithPredicates.getKeyValues());
402                 break;
403
404             case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE :
405
406                 NodeWithValue<?> nodeWithValue = (NodeWithValue<?>) pathArgument;
407
408                 writeQName(nodeWithValue.getNodeType());
409                 writeObject(nodeWithValue.getValue());
410                 break;
411
412             case PathArgumentTypes.AUGMENTATION_IDENTIFIER :
413
414                 AugmentationIdentifier augmentationIdentifier = (AugmentationIdentifier) pathArgument;
415
416                 // No Qname in augmentation identifier
417                 writeQNameSet(augmentationIdentifier.getPossibleChildNames());
418                 break;
419             default :
420                 throw new IllegalStateException("Unknown node identifier type is found : "
421                         + pathArgument.getClass().toString());
422         }
423     }
424
425     private void writeKeyValueMap(final Map<QName, Object> keyValueMap) throws IOException {
426         if (keyValueMap != null && !keyValueMap.isEmpty()) {
427             output.writeInt(keyValueMap.size());
428
429             for (Map.Entry<QName, Object> entry : keyValueMap.entrySet()) {
430                 writeQName(entry.getKey());
431                 writeObject(entry.getValue());
432             }
433         } else {
434             output.writeInt(0);
435         }
436     }
437
438     private void writeQNameSet(final Set<QName> children) throws IOException {
439         // Write each child's qname separately, if list is empty send count as 0
440         if (children != null && !children.isEmpty()) {
441             output.writeInt(children.size());
442             for (QName qname : children) {
443                 writeQName(qname);
444             }
445         } else {
446             LOG.debug("augmentation node does not have any child");
447             output.writeInt(0);
448         }
449     }
450
451     private void writeObject(final Object value) throws IOException {
452
453         byte type = ValueTypes.getSerializableType(value);
454         // Write object type first
455         output.writeByte(type);
456
457         switch (type) {
458             case ValueTypes.BOOL_TYPE:
459                 output.writeBoolean((Boolean) value);
460                 break;
461             case ValueTypes.QNAME_TYPE:
462                 writeQName((QName) value);
463                 break;
464             case ValueTypes.INT_TYPE:
465                 output.writeInt((Integer) value);
466                 break;
467             case ValueTypes.BYTE_TYPE:
468                 output.writeByte((Byte) value);
469                 break;
470             case ValueTypes.LONG_TYPE:
471                 output.writeLong((Long) value);
472                 break;
473             case ValueTypes.SHORT_TYPE:
474                 output.writeShort((Short) value);
475                 break;
476             case ValueTypes.BITS_TYPE:
477                 writeObjSet((Set<?>) value);
478                 break;
479             case ValueTypes.BINARY_TYPE:
480                 byte[] bytes = (byte[]) value;
481                 output.writeInt(bytes.length);
482                 output.write(bytes);
483                 break;
484             case ValueTypes.YANG_IDENTIFIER_TYPE:
485                 writeYangInstanceIdentifierInternal((YangInstanceIdentifier) value);
486                 break;
487             case ValueTypes.EMPTY_TYPE:
488                 break;
489             case ValueTypes.STRING_BYTES_TYPE:
490                 final byte[] valueBytes = value.toString().getBytes(StandardCharsets.UTF_8);
491                 output.writeInt(valueBytes.length);
492                 output.write(valueBytes);
493                 break;
494             default:
495                 output.writeUTF(value.toString());
496                 break;
497         }
498     }
499 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.