BUG 4589 : Handle writing and reading large strings
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / cluster / datastore / node / utils / stream / NormalizedNodeOutputStreamWriter.java
1 /*
2  * Copyright (c) 2014, 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
9 package org.opendaylight.controller.cluster.datastore.node.utils.stream;
10
11 import com.google.common.base.Preconditions;
12 import java.io.DataOutput;
13 import java.io.DataOutputStream;
14 import java.io.IOException;
15 import java.io.OutputStream;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Collection;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Set;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
25 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * NormalizedNodeOutputStreamWriter will be used by distributed datastore to send normalized node in
31  * a stream.
32  * A stream writer wrapper around this class will write node objects to stream in recursive manner.
33  * for example - If you have a ContainerNode which has a two LeafNode as children, then
34  * you will first call {@link #startContainerNode(YangInstanceIdentifier.NodeIdentifier, int)}, then will call
35  * {@link #leafNode(YangInstanceIdentifier.NodeIdentifier, Object)} twice and then, {@link #endNode()} to end
36  * container node.
37  *
38  * Based on the each node, the node type is also written to the stream, that helps in reconstructing the object,
39  * while reading.
40  *
41  *
42  */
43
44 public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWriter {
45
46     private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeOutputStreamWriter.class);
47
48     static final byte SIGNATURE_MARKER = (byte) 0xab;
49     static final short CURRENT_VERSION = (short) 1;
50
51     static final byte IS_CODE_VALUE = 1;
52     static final byte IS_STRING_VALUE = 2;
53     static final byte IS_NULL_VALUE = 3;
54
55     private final DataOutput output;
56
57     private final Map<String, Integer> stringCodeMap = new HashMap<>();
58
59     private NormalizedNodeWriter normalizedNodeWriter;
60
61     private boolean wroteSignatureMarker;
62
63     public NormalizedNodeOutputStreamWriter(OutputStream stream) throws IOException {
64         Preconditions.checkNotNull(stream);
65         output = new DataOutputStream(stream);
66     }
67
68     public NormalizedNodeOutputStreamWriter(DataOutput output) {
69         this.output = Preconditions.checkNotNull(output);
70     }
71
72     private NormalizedNodeWriter normalizedNodeWriter() {
73         if(normalizedNodeWriter == null) {
74             normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(this);
75         }
76
77         return normalizedNodeWriter;
78     }
79
80     public void writeNormalizedNode(NormalizedNode<?, ?> node) throws IOException {
81         writeSignatureMarkerAndVersionIfNeeded();
82         normalizedNodeWriter().write(node);
83     }
84
85     private void writeSignatureMarkerAndVersionIfNeeded() throws IOException {
86         if(!wroteSignatureMarker) {
87             output.writeByte(SIGNATURE_MARKER);
88             output.writeShort(CURRENT_VERSION);
89             wroteSignatureMarker = true;
90         }
91     }
92
93     @Override
94     public void leafNode(YangInstanceIdentifier.NodeIdentifier name, Object value) throws IOException, IllegalArgumentException {
95         Preconditions.checkNotNull(name, "Node identifier should not be null");
96         LOG.debug("Writing a new leaf node");
97         startNode(name.getNodeType(), NodeTypes.LEAF_NODE);
98
99         writeObject(value);
100     }
101
102     @Override
103     public void startLeafSet(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
104         Preconditions.checkNotNull(name, "Node identifier should not be null");
105         LOG.debug("Starting a new leaf set");
106
107         startNode(name.getNodeType(), NodeTypes.LEAF_SET);
108     }
109
110     @Override
111     public void leafSetEntryNode(Object value) throws IOException, IllegalArgumentException {
112         LOG.debug("Writing a new leaf set entry node");
113
114         output.writeByte(NodeTypes.LEAF_SET_ENTRY_NODE);
115         writeObject(value);
116     }
117
118     @Override
119     public void startContainerNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
120         Preconditions.checkNotNull(name, "Node identifier should not be null");
121
122         LOG.debug("Starting a new container node");
123
124         startNode(name.getNodeType(), NodeTypes.CONTAINER_NODE);
125     }
126
127     @Override
128     public void startUnkeyedList(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
129         Preconditions.checkNotNull(name, "Node identifier should not be null");
130         LOG.debug("Starting a new unkeyed list");
131
132         startNode(name.getNodeType(), NodeTypes.UNKEYED_LIST);
133     }
134
135     @Override
136     public void startUnkeyedListItem(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalStateException {
137         Preconditions.checkNotNull(name, "Node identifier should not be null");
138         LOG.debug("Starting a new unkeyed list item");
139
140         startNode(name.getNodeType(), NodeTypes.UNKEYED_LIST_ITEM);
141     }
142
143     @Override
144     public void startMapNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
145         Preconditions.checkNotNull(name, "Node identifier should not be null");
146         LOG.debug("Starting a new map node");
147
148         startNode(name.getNodeType(), NodeTypes.MAP_NODE);
149     }
150
151     @Override
152     public void startMapEntryNode(YangInstanceIdentifier.NodeIdentifierWithPredicates identifier, int childSizeHint) throws IOException, IllegalArgumentException {
153         Preconditions.checkNotNull(identifier, "Node identifier should not be null");
154         LOG.debug("Starting a new map entry node");
155         startNode(identifier.getNodeType(), NodeTypes.MAP_ENTRY_NODE);
156
157         writeKeyValueMap(identifier.getKeyValues());
158
159     }
160
161     @Override
162     public void startOrderedMapNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
163         Preconditions.checkNotNull(name, "Node identifier should not be null");
164         LOG.debug("Starting a new ordered map node");
165
166         startNode(name.getNodeType(), NodeTypes.ORDERED_MAP_NODE);
167     }
168
169     @Override
170     public void startChoiceNode(YangInstanceIdentifier.NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
171         Preconditions.checkNotNull(name, "Node identifier should not be null");
172         LOG.debug("Starting a new choice node");
173
174         startNode(name.getNodeType(), NodeTypes.CHOICE_NODE);
175     }
176
177     @Override
178     public void startAugmentationNode(YangInstanceIdentifier.AugmentationIdentifier identifier) throws IOException, IllegalArgumentException {
179         Preconditions.checkNotNull(identifier, "Node identifier should not be null");
180         LOG.debug("Starting a new augmentation node");
181
182         output.writeByte(NodeTypes.AUGMENTATION_NODE);
183         writeQNameSet(identifier.getPossibleChildNames());
184     }
185
186     @Override
187     public void anyxmlNode(YangInstanceIdentifier.NodeIdentifier name, Object value) throws IOException, IllegalArgumentException {
188         Preconditions.checkNotNull(name, "Node identifier should not be null");
189         LOG.debug("Writing a new xml node");
190
191         startNode(name.getNodeType(), NodeTypes.ANY_XML_NODE);
192
193         writeObject(value);
194     }
195
196     @Override
197     public void endNode() throws IOException, IllegalStateException {
198         LOG.debug("Ending the node");
199
200         output.writeByte(NodeTypes.END_NODE);
201     }
202
203     @Override
204     public void close() throws IOException {
205         flush();
206     }
207
208     @Override
209     public void flush() throws IOException {
210         if (output instanceof OutputStream) {
211             ((OutputStream)output).flush();
212         }
213     }
214
215     private void startNode(final QName qName, byte nodeType) throws IOException {
216
217         Preconditions.checkNotNull(qName, "QName of node identifier should not be null.");
218
219         writeSignatureMarkerAndVersionIfNeeded();
220
221         // First write the type of node
222         output.writeByte(nodeType);
223         // Write Start Tag
224         writeQName(qName);
225     }
226
227     private void writeQName(QName qName) throws IOException {
228
229         writeCodedString(qName.getLocalName());
230         writeCodedString(qName.getNamespace().toString());
231         writeCodedString(qName.getFormattedRevision());
232     }
233
234     private void writeCodedString(String key) throws IOException {
235         Integer value = stringCodeMap.get(key);
236         if(value != null) {
237             output.writeByte(IS_CODE_VALUE);
238             output.writeInt(value);
239         } else {
240             if(key != null) {
241                 output.writeByte(IS_STRING_VALUE);
242                 stringCodeMap.put(key, Integer.valueOf(stringCodeMap.size()));
243                 output.writeUTF(key);
244             } else {
245                 output.writeByte(IS_NULL_VALUE);
246             }
247         }
248     }
249
250     private void writeObjSet(Set<?> set) throws IOException {
251         if(!set.isEmpty()){
252             output.writeInt(set.size());
253             for(Object o : set){
254                 if(o instanceof String){
255                     writeCodedString(o.toString());
256                 } else {
257                     throw new IllegalArgumentException("Expected value type to be String but was : " +
258                         o.toString());
259                 }
260             }
261         } else {
262             output.writeInt(0);
263         }
264     }
265
266     public void writeYangInstanceIdentifier(YangInstanceIdentifier identifier) throws IOException {
267         writeSignatureMarkerAndVersionIfNeeded();
268         writeYangInstanceIdentifierInternal(identifier);
269     }
270
271     private void writeYangInstanceIdentifierInternal(YangInstanceIdentifier identifier) throws IOException {
272         Collection<YangInstanceIdentifier.PathArgument> pathArguments = identifier.getPathArguments();
273         output.writeInt(pathArguments.size());
274
275         for(YangInstanceIdentifier.PathArgument pathArgument : pathArguments) {
276             writePathArgument(pathArgument);
277         }
278     }
279
280     public void writePathArgument(YangInstanceIdentifier.PathArgument pathArgument) throws IOException {
281
282         byte type = PathArgumentTypes.getSerializablePathArgumentType(pathArgument);
283
284         output.writeByte(type);
285
286         switch(type) {
287             case PathArgumentTypes.NODE_IDENTIFIER :
288
289                 YangInstanceIdentifier.NodeIdentifier nodeIdentifier =
290                     (YangInstanceIdentifier.NodeIdentifier) pathArgument;
291
292                 writeQName(nodeIdentifier.getNodeType());
293                 break;
294
295             case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
296
297                 YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifierWithPredicates =
298                     (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument;
299                 writeQName(nodeIdentifierWithPredicates.getNodeType());
300
301                 writeKeyValueMap(nodeIdentifierWithPredicates.getKeyValues());
302                 break;
303
304             case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE :
305
306                 YangInstanceIdentifier.NodeWithValue nodeWithValue =
307                     (YangInstanceIdentifier.NodeWithValue) pathArgument;
308
309                 writeQName(nodeWithValue.getNodeType());
310                 writeObject(nodeWithValue.getValue());
311                 break;
312
313             case PathArgumentTypes.AUGMENTATION_IDENTIFIER :
314
315                 YangInstanceIdentifier.AugmentationIdentifier augmentationIdentifier =
316                     (YangInstanceIdentifier.AugmentationIdentifier) pathArgument;
317
318                 // No Qname in augmentation identifier
319                 writeQNameSet(augmentationIdentifier.getPossibleChildNames());
320                 break;
321             default :
322                 throw new IllegalStateException("Unknown node identifier type is found : " + pathArgument.getClass().toString() );
323         }
324     }
325
326     private void writeKeyValueMap(Map<QName, Object> keyValueMap) throws IOException {
327         if(keyValueMap != null && !keyValueMap.isEmpty()) {
328             output.writeInt(keyValueMap.size());
329             Set<QName> qNameSet = keyValueMap.keySet();
330
331             for(QName qName : qNameSet) {
332                 writeQName(qName);
333                 writeObject(keyValueMap.get(qName));
334             }
335         } else {
336             output.writeInt(0);
337         }
338     }
339
340     private void writeQNameSet(Set<QName> children) throws IOException {
341         // Write each child's qname separately, if list is empty send count as 0
342         if(children != null && !children.isEmpty()) {
343             output.writeInt(children.size());
344             for(QName qName : children) {
345                 writeQName(qName);
346             }
347         } else {
348             LOG.debug("augmentation node does not have any child");
349             output.writeInt(0);
350         }
351     }
352
353     private void writeObject(Object value) throws IOException {
354
355         byte type = ValueTypes.getSerializableType(value);
356         // Write object type first
357         output.writeByte(type);
358
359         switch(type) {
360             case ValueTypes.BOOL_TYPE:
361                 output.writeBoolean((Boolean) value);
362                 break;
363             case ValueTypes.QNAME_TYPE:
364                 writeQName((QName) value);
365                 break;
366             case ValueTypes.INT_TYPE:
367                 output.writeInt((Integer) value);
368                 break;
369             case ValueTypes.BYTE_TYPE:
370                 output.writeByte((Byte) value);
371                 break;
372             case ValueTypes.LONG_TYPE:
373                 output.writeLong((Long) value);
374                 break;
375             case ValueTypes.SHORT_TYPE:
376                 output.writeShort((Short) value);
377                 break;
378             case ValueTypes.BITS_TYPE:
379                 writeObjSet((Set<?>) value);
380                 break;
381             case ValueTypes.BINARY_TYPE:
382                 byte[] bytes = (byte[]) value;
383                 output.writeInt(bytes.length);
384                 output.write(bytes);
385                 break;
386             case ValueTypes.YANG_IDENTIFIER_TYPE:
387                 writeYangInstanceIdentifierInternal((YangInstanceIdentifier) value);
388                 break;
389             case ValueTypes.NULL_TYPE :
390                 break;
391             case ValueTypes.STRING_BYTES_TYPE:
392                 final byte[] valueBytes = value.toString().getBytes(StandardCharsets.UTF_8);
393                 output.writeInt(valueBytes.length);
394                 output.write(valueBytes);
395                 break;
396             default:
397                 output.writeUTF(value.toString());
398                 break;
399         }
400     }
401 }