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