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