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