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