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