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