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