Merge "Event Source: switch from wildcards to regexs"
[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) {
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         flush();
207     }
208
209     @Override
210     public void flush() throws IOException {
211         if (output instanceof OutputStream) {
212             ((OutputStream)output).flush();
213         }
214     }
215
216     private void startNode(final QName qName, byte nodeType) throws IOException {
217
218         Preconditions.checkNotNull(qName, "QName of node identifier should not be null.");
219
220         writeSignatureMarkerAndVersionIfNeeded();
221
222         // First write the type of node
223         output.writeByte(nodeType);
224         // Write Start Tag
225         writeQName(qName);
226     }
227
228     private void writeQName(QName qName) throws IOException {
229
230         writeCodedString(qName.getLocalName());
231         writeCodedString(qName.getNamespace().toString());
232         writeCodedString(qName.getFormattedRevision());
233     }
234
235     private void writeCodedString(String key) throws IOException {
236         Integer value = stringCodeMap.get(key);
237         if(value != null) {
238             output.writeByte(IS_CODE_VALUE);
239             output.writeInt(value);
240         } else {
241             if(key != null) {
242                 output.writeByte(IS_STRING_VALUE);
243                 stringCodeMap.put(key, Integer.valueOf(stringCodeMap.size()));
244                 output.writeUTF(key);
245             } else {
246                 output.writeByte(IS_NULL_VALUE);
247             }
248         }
249     }
250
251     private void writeObjSet(Set<?> set) throws IOException {
252         if(!set.isEmpty()){
253             output.writeInt(set.size());
254             for(Object o : set){
255                 if(o instanceof String){
256                     writeCodedString(o.toString());
257                 } else {
258                     throw new IllegalArgumentException("Expected value type to be String but was : " +
259                         o.toString());
260                 }
261             }
262         } else {
263             output.writeInt(0);
264         }
265     }
266
267     public void writeYangInstanceIdentifier(YangInstanceIdentifier identifier) throws IOException {
268         writeSignatureMarkerAndVersionIfNeeded();
269         writeYangInstanceIdentifierInternal(identifier);
270     }
271
272     private void writeYangInstanceIdentifierInternal(YangInstanceIdentifier identifier) throws IOException {
273         Iterable<YangInstanceIdentifier.PathArgument> pathArguments = identifier.getPathArguments();
274         int size = Iterables.size(pathArguments);
275         output.writeInt(size);
276
277         for(YangInstanceIdentifier.PathArgument pathArgument : pathArguments) {
278             writePathArgument(pathArgument);
279         }
280     }
281
282     public void writePathArgument(YangInstanceIdentifier.PathArgument pathArgument) throws IOException {
283
284         byte type = PathArgumentTypes.getSerializablePathArgumentType(pathArgument);
285
286         output.writeByte(type);
287
288         switch(type) {
289             case PathArgumentTypes.NODE_IDENTIFIER :
290
291                 YangInstanceIdentifier.NodeIdentifier nodeIdentifier =
292                     (YangInstanceIdentifier.NodeIdentifier) pathArgument;
293
294                 writeQName(nodeIdentifier.getNodeType());
295                 break;
296
297             case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
298
299                 YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifierWithPredicates =
300                     (YangInstanceIdentifier.NodeIdentifierWithPredicates) pathArgument;
301                 writeQName(nodeIdentifierWithPredicates.getNodeType());
302
303                 writeKeyValueMap(nodeIdentifierWithPredicates.getKeyValues());
304                 break;
305
306             case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE :
307
308                 YangInstanceIdentifier.NodeWithValue nodeWithValue =
309                     (YangInstanceIdentifier.NodeWithValue) pathArgument;
310
311                 writeQName(nodeWithValue.getNodeType());
312                 writeObject(nodeWithValue.getValue());
313                 break;
314
315             case PathArgumentTypes.AUGMENTATION_IDENTIFIER :
316
317                 YangInstanceIdentifier.AugmentationIdentifier augmentationIdentifier =
318                     (YangInstanceIdentifier.AugmentationIdentifier) pathArgument;
319
320                 // No Qname in augmentation identifier
321                 writeQNameSet(augmentationIdentifier.getPossibleChildNames());
322                 break;
323             default :
324                 throw new IllegalStateException("Unknown node identifier type is found : " + pathArgument.getClass().toString() );
325         }
326     }
327
328     private void writeKeyValueMap(Map<QName, Object> keyValueMap) throws IOException {
329         if(keyValueMap != null && !keyValueMap.isEmpty()) {
330             output.writeInt(keyValueMap.size());
331             Set<QName> qNameSet = keyValueMap.keySet();
332
333             for(QName qName : qNameSet) {
334                 writeQName(qName);
335                 writeObject(keyValueMap.get(qName));
336             }
337         } else {
338             output.writeInt(0);
339         }
340     }
341
342     private void writeQNameSet(Set<QName> children) throws IOException {
343         // Write each child's qname separately, if list is empty send count as 0
344         if(children != null && !children.isEmpty()) {
345             output.writeInt(children.size());
346             for(QName qName : children) {
347                 writeQName(qName);
348             }
349         } else {
350             LOG.debug("augmentation node does not have any child");
351             output.writeInt(0);
352         }
353     }
354
355     private void writeObject(Object value) throws IOException {
356
357         byte type = ValueTypes.getSerializableType(value);
358         // Write object type first
359         output.writeByte(type);
360
361         switch(type) {
362             case ValueTypes.BOOL_TYPE:
363                 output.writeBoolean((Boolean) value);
364                 break;
365             case ValueTypes.QNAME_TYPE:
366                 writeQName((QName) value);
367                 break;
368             case ValueTypes.INT_TYPE:
369                 output.writeInt((Integer) value);
370                 break;
371             case ValueTypes.BYTE_TYPE:
372                 output.writeByte((Byte) value);
373                 break;
374             case ValueTypes.LONG_TYPE:
375                 output.writeLong((Long) value);
376                 break;
377             case ValueTypes.SHORT_TYPE:
378                 output.writeShort((Short) value);
379                 break;
380             case ValueTypes.BITS_TYPE:
381                 writeObjSet((Set<?>) value);
382                 break;
383             case ValueTypes.BINARY_TYPE:
384                 byte[] bytes = (byte[]) value;
385                 output.writeInt(bytes.length);
386                 output.write(bytes);
387                 break;
388             case ValueTypes.YANG_IDENTIFIER_TYPE:
389                 writeYangInstanceIdentifierInternal((YangInstanceIdentifier) value);
390                 break;
391             case ValueTypes.NULL_TYPE :
392                 break;
393             default:
394                 output.writeUTF(value.toString());
395                 break;
396         }
397     }
398 }