552f1e96a2de23011832c18c0bc6c8d5e20a19af
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / cluster / datastore / node / utils / stream / LithiumNormalizedNodeInputStreamReader.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 package org.opendaylight.controller.cluster.datastore.node.utils.stream;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Strings;
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.collect.ImmutableList.Builder;
15 import com.google.common.collect.Sets;
16 import java.io.DataInput;
17 import java.io.IOException;
18 import java.io.StringReader;
19 import java.math.BigDecimal;
20 import java.math.BigInteger;
21 import java.nio.charset.StandardCharsets;
22 import java.util.ArrayList;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26 import javax.xml.parsers.DocumentBuilderFactory;
27 import javax.xml.parsers.ParserConfigurationException;
28 import javax.xml.transform.dom.DOMSource;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.opendaylight.controller.cluster.datastore.node.utils.QNameFactory;
31 import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate;
32 import org.opendaylight.yangtools.yang.common.Empty;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
40 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
41 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44 import org.w3c.dom.Element;
45 import org.xml.sax.InputSource;
46 import org.xml.sax.SAXException;
47
48 /**
49  * NormalizedNodeInputStreamReader reads the byte stream and constructs the normalized node including its children
50  * nodes. This process goes in recursive manner, where each NodeTypes object signifies the start of the object, except
51  * END_NODE. If a node can have children, then that node's end is calculated based on appearance of END_NODE.
52  */
53 class LithiumNormalizedNodeInputStreamReader extends ForwardingDataInput implements NormalizedNodeDataInput {
54
55     private static final Logger LOG = LoggerFactory.getLogger(LithiumNormalizedNodeInputStreamReader.class);
56
57     private final @NonNull DataInput input;
58
59     private final List<String> codedStringMap = new ArrayList<>();
60
61     private QName lastLeafSetQName;
62
63     LithiumNormalizedNodeInputStreamReader(final DataInput input) {
64         this.input = requireNonNull(input);
65     }
66
67     @Override
68     final DataInput delegate() {
69         return input;
70     }
71
72     @Override
73     public NormalizedNodeStreamVersion getVersion() throws IOException {
74         return NormalizedNodeStreamVersion.LITHIUM;
75     }
76
77     @Override
78     public void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
79         streamNormalizedNode(requireNonNull(writer), input.readByte());
80     }
81
82     private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
83         switch (nodeType) {
84             case NodeTypes.ANY_XML_NODE:
85                 streamAnyxml(writer);
86                 break;
87             case NodeTypes.AUGMENTATION_NODE:
88                 streamAugmentation(writer);
89                 break;
90             case NodeTypes.CHOICE_NODE:
91                 streamChoice(writer);
92                 break;
93             case NodeTypes.CONTAINER_NODE:
94                 streamContainer(writer);
95                 break;
96             case NodeTypes.LEAF_NODE:
97                 streamLeaf(writer);
98                 break;
99             case NodeTypes.LEAF_SET:
100                 streamLeafSet(writer);
101                 break;
102             case NodeTypes.ORDERED_LEAF_SET:
103                 streamOrderedLeafSet(writer);
104                 break;
105             case NodeTypes.LEAF_SET_ENTRY_NODE:
106                 streamLeafSetEntry(writer);
107                 break;
108             case NodeTypes.MAP_ENTRY_NODE:
109                 streamMapEntry(writer);
110                 break;
111             case NodeTypes.MAP_NODE:
112                 streamMap(writer);
113                 break;
114             case NodeTypes.ORDERED_MAP_NODE:
115                 streamOrderedMap(writer);
116                 break;
117             case NodeTypes.UNKEYED_LIST:
118                 streamUnkeyedList(writer);
119                 break;
120             case NodeTypes.UNKEYED_LIST_ITEM:
121                 streamUnkeyedListItem(writer);
122                 break;
123             default:
124                 throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
125         }
126     }
127
128     private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
129         final NodeIdentifier identifier = readNodeIdentifier();
130         LOG.trace("Streaming anyxml node {}", identifier);
131         writer.startAnyxmlNode(identifier);
132         writer.domSourceValue(readDOMSource());
133         writer.endNode();
134     }
135
136     private void streamAugmentation(final NormalizedNodeStreamWriter writer) throws IOException {
137         final AugmentationIdentifier augIdentifier = readAugmentationIdentifier();
138         LOG.trace("Streaming augmentation node {}", augIdentifier);
139         writer.startAugmentationNode(augIdentifier);
140         commonStreamContainer(writer);
141     }
142
143     private void streamChoice(final NormalizedNodeStreamWriter writer) throws IOException {
144         final NodeIdentifier identifier = readNodeIdentifier();
145         LOG.trace("Streaming choice node {}", identifier);
146         writer.startChoiceNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
147         commonStreamContainer(writer);
148     }
149
150     private void streamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
151         final NodeIdentifier identifier = readNodeIdentifier();
152         LOG.trace("Streaming container node {}", identifier);
153         writer.startContainerNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
154         commonStreamContainer(writer);
155     }
156
157     private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
158         final NodeIdentifier identifier = readNodeIdentifier();
159         LOG.trace("Streaming leaf node {}", identifier);
160         writer.startLeafNode(identifier);
161         writer.scalarValue(readObject());
162         writer.endNode();
163     }
164
165     private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
166         final NodeIdentifier identifier = readNodeIdentifier();
167         LOG.trace("Streaming leaf set node {}", identifier);
168         writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
169         commonStreamLeafSet(writer, identifier);
170     }
171
172     private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
173         final NodeIdentifier identifier = readNodeIdentifier();
174         LOG.trace("Streaming ordered leaf set node {}", identifier);
175         writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
176         commonStreamLeafSet(writer, identifier);
177     }
178
179     private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
180             throws IOException {
181         lastLeafSetQName = identifier.getNodeType();
182         try {
183             commonStreamContainer(writer);
184         } finally {
185             // Make sure we never leak this
186             lastLeafSetQName = null;
187         }
188     }
189
190     private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
191         final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
192         final Object value = readObject();
193         final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
194         LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
195         writer.startLeafSetEntryNode(leafIdentifier);
196         writer.scalarValue(value);
197         writer.endNode();
198     }
199
200     private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
201         final NodeIdentifier identifier = readNodeIdentifier();
202         LOG.trace("Streaming map node {}", identifier);
203         writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
204         commonStreamContainer(writer);
205     }
206
207     private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
208         final NodeIdentifier identifier = readNodeIdentifier();
209         LOG.trace("Streaming ordered map node {}", identifier);
210         writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
211         commonStreamContainer(writer);
212     }
213
214     private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
215         final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
216         LOG.trace("Streaming map entry node {}", entryIdentifier);
217         writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
218         commonStreamContainer(writer);
219     }
220
221     private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
222         final NodeIdentifier identifier = readNodeIdentifier();
223         LOG.trace("Streaming unkeyed list node {}", identifier);
224         writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
225         commonStreamContainer(writer);
226     }
227
228     private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
229         final NodeIdentifier identifier = readNodeIdentifier();
230         LOG.trace("Streaming unkeyed list item node {}", identifier);
231         writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
232         commonStreamContainer(writer);
233     }
234
235     private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
236         for (byte nodeType = input.readByte(); nodeType != NodeTypes.END_NODE; nodeType = input.readByte()) {
237             streamNormalizedNode(writer, nodeType);
238         }
239         writer.endNode();
240     }
241
242     private DOMSource readDOMSource() throws IOException {
243         String xml = readObject().toString();
244         try {
245             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
246             factory.setNamespaceAware(true);
247             Element node = factory.newDocumentBuilder().parse(
248                     new InputSource(new StringReader(xml))).getDocumentElement();
249             return new DOMSource(node);
250         } catch (SAXException | ParserConfigurationException e) {
251             throw new IOException("Error parsing XML: " + xml, e);
252         }
253     }
254
255     @Override
256     public QName readQName() throws IOException {
257         // Read in the same sequence of writing
258         String localName = readCodedString();
259         String namespace = readCodedString();
260         String revision = Strings.emptyToNull(readCodedString());
261
262         return QNameFactory.create(localName, namespace, revision);
263     }
264
265     final String readCodedString() throws IOException {
266         final byte valueType = input.readByte();
267         switch (valueType) {
268             case TokenTypes.IS_NULL_VALUE:
269                 return null;
270             case TokenTypes.IS_CODE_VALUE:
271                 final int code = input.readInt();
272                 try {
273                     return codedStringMap.get(code);
274                 } catch (IndexOutOfBoundsException e) {
275                     throw new IOException("String code " + code + " was not found", e);
276                 }
277             case TokenTypes.IS_STRING_VALUE:
278                 final String value = input.readUTF().intern();
279                 codedStringMap.add(value);
280                 return value;
281             default:
282                 throw new IOException("Unhandled string value type " + valueType);
283         }
284     }
285
286     private Set<QName> readQNameSet() throws IOException {
287         // Read the children count
288         final int count = input.readInt();
289         final Set<QName> children = Sets.newHashSetWithExpectedSize(count);
290         for (int i = 0; i < count; i++) {
291             children.add(readQName());
292         }
293         return children;
294     }
295
296     AugmentationIdentifier readAugmentationIdentifier() throws IOException {
297         return AugmentationIdentifier.create(readQNameSet());
298     }
299
300     NodeIdentifier readNodeIdentifier() throws IOException {
301         return new NodeIdentifier(readQName());
302     }
303
304     private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
305         final QName qname = readQName();
306         final int count = input.readInt();
307         switch (count) {
308             case 0:
309                 return NodeIdentifierWithPredicates.of(qname);
310             case 1:
311                 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
312             default:
313                 // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
314                 final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
315                 final Object[] values = new Object[count];
316                 for (int i = 0; i < count; i++) {
317                     keys.add(readQName());
318                     values[i] = readObject();
319                 }
320
321                 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
322                     .instantiateWithValues(values));
323         }
324     }
325
326     private Object readObject() throws IOException {
327         byte objectType = input.readByte();
328         switch (objectType) {
329             case ValueTypes.BITS_TYPE:
330                 return readObjSet();
331
332             case ValueTypes.BOOL_TYPE:
333                 return input.readBoolean();
334
335             case ValueTypes.BYTE_TYPE:
336                 return input.readByte();
337
338             case ValueTypes.INT_TYPE:
339                 return input.readInt();
340
341             case ValueTypes.LONG_TYPE:
342                 return input.readLong();
343
344             case ValueTypes.QNAME_TYPE:
345                 return readQName();
346
347             case ValueTypes.SHORT_TYPE:
348                 return input.readShort();
349
350             case ValueTypes.STRING_TYPE:
351                 return input.readUTF();
352
353             case ValueTypes.STRING_BYTES_TYPE:
354                 return readStringBytes();
355
356             case ValueTypes.BIG_DECIMAL_TYPE:
357                 return new BigDecimal(input.readUTF());
358
359             case ValueTypes.BIG_INTEGER_TYPE:
360                 return new BigInteger(input.readUTF());
361
362             case ValueTypes.BINARY_TYPE:
363                 byte[] bytes = new byte[input.readInt()];
364                 input.readFully(bytes);
365                 return bytes;
366
367             case ValueTypes.YANG_IDENTIFIER_TYPE:
368                 return readYangInstanceIdentifierInternal();
369
370             case ValueTypes.EMPTY_TYPE:
371             // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
372             // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
373             // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
374             // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
375             // so we'd have to drop the leaf which might cause other issues.
376             case ValueTypes.NULL_TYPE:
377                 return Empty.getInstance();
378
379             default:
380                 return null;
381         }
382     }
383
384     private String readStringBytes() throws IOException {
385         byte[] bytes = new byte[input.readInt()];
386         input.readFully(bytes);
387         return new String(bytes, StandardCharsets.UTF_8);
388     }
389
390     @Override
391     public SchemaPath readSchemaPath() throws IOException {
392         final boolean absolute = input.readBoolean();
393         final int size = input.readInt();
394
395         final Builder<QName> qnames = ImmutableList.builderWithExpectedSize(size);
396         for (int i = 0; i < size; ++i) {
397             qnames.add(readQName());
398         }
399         return SchemaPath.create(qnames.build(), absolute);
400     }
401
402     @Override
403     public YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
404         return readYangInstanceIdentifierInternal();
405     }
406
407     private YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
408         int size = input.readInt();
409         final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
410         for (int i = 0; i < size; i++) {
411             pathArguments.add(readPathArgument());
412         }
413         return YangInstanceIdentifier.create(pathArguments.build());
414     }
415
416     private Set<String> readObjSet() throws IOException {
417         int count = input.readInt();
418         Set<String> children = new HashSet<>(count);
419         for (int i = 0; i < count; i++) {
420             children.add(readCodedString());
421         }
422         return children;
423     }
424
425     @Override
426     public PathArgument readPathArgument() throws IOException {
427         // read Type
428         int type = input.readByte();
429
430         switch (type) {
431             case PathArgumentTypes.AUGMENTATION_IDENTIFIER:
432                 return readAugmentationIdentifier();
433             case PathArgumentTypes.NODE_IDENTIFIER:
434                 return readNodeIdentifier();
435             case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
436                 return readNormalizedNodeWithPredicates();
437             case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE:
438                 return new NodeWithValue<>(readQName(), readObject());
439             default:
440                 // FIXME: throw hard error
441                 return null;
442         }
443     }
444 }