Deduplicate MapNode key leaf values
[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         startLeaf(writer);
159         endLeaf(writer, readObject());
160     }
161
162     // Leaf inside a MapEntryNode, it can potentially be a key leaf, in which case we want to de-duplicate values.
163     private void streamLeaf(final NormalizedNodeStreamWriter writer, final NodeIdentifierWithPredicates entryId)
164             throws IOException {
165         final NodeIdentifier identifier = startLeaf(writer);
166         final Object value = readObject();
167         final Object entryValue = entryId.getValue(identifier.getNodeType());
168         endLeaf(writer, entryValue == null ? value : entryValue);
169     }
170
171     private NodeIdentifier startLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
172         final NodeIdentifier identifier = readNodeIdentifier();
173         LOG.trace("Streaming leaf node {}", identifier);
174         writer.startLeafNode(identifier);
175         return identifier;
176     }
177
178     private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
179         writer.scalarValue(value);
180         writer.endNode();
181     }
182
183     private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
184         final NodeIdentifier identifier = readNodeIdentifier();
185         LOG.trace("Streaming leaf set node {}", identifier);
186         writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
187         commonStreamLeafSet(writer, identifier);
188     }
189
190     private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
191         final NodeIdentifier identifier = readNodeIdentifier();
192         LOG.trace("Streaming ordered leaf set node {}", identifier);
193         writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
194         commonStreamLeafSet(writer, identifier);
195     }
196
197     private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
198             throws IOException {
199         lastLeafSetQName = identifier.getNodeType();
200         try {
201             commonStreamContainer(writer);
202         } finally {
203             // Make sure we never leak this
204             lastLeafSetQName = null;
205         }
206     }
207
208     private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
209         final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
210         final Object value = readObject();
211         final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
212         LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
213         writer.startLeafSetEntryNode(leafIdentifier);
214         writer.scalarValue(value);
215         writer.endNode();
216     }
217
218     private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
219         final NodeIdentifier identifier = readNodeIdentifier();
220         LOG.trace("Streaming map node {}", identifier);
221         writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
222         commonStreamContainer(writer);
223     }
224
225     private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
226         final NodeIdentifier identifier = readNodeIdentifier();
227         LOG.trace("Streaming ordered map node {}", identifier);
228         writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
229         commonStreamContainer(writer);
230     }
231
232     private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
233         final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
234         LOG.trace("Streaming map entry node {}", entryIdentifier);
235         writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
236
237         // Same loop as commonStreamContainer(), but ...
238         for (byte nodeType = input.readByte(); nodeType != NodeTypes.END_NODE; nodeType = input.readByte()) {
239             if (nodeType == NodeTypes.LEAF_NODE) {
240                 // ... leaf nodes may need de-duplication
241                 streamLeaf(writer, entryIdentifier);
242             } else {
243                 streamNormalizedNode(writer, nodeType);
244             }
245         }
246         writer.endNode();
247     }
248
249     private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
250         final NodeIdentifier identifier = readNodeIdentifier();
251         LOG.trace("Streaming unkeyed list node {}", identifier);
252         writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
253         commonStreamContainer(writer);
254     }
255
256     private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
257         final NodeIdentifier identifier = readNodeIdentifier();
258         LOG.trace("Streaming unkeyed list item node {}", identifier);
259         writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
260         commonStreamContainer(writer);
261     }
262
263     private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
264         for (byte nodeType = input.readByte(); nodeType != NodeTypes.END_NODE; nodeType = input.readByte()) {
265             streamNormalizedNode(writer, nodeType);
266         }
267         writer.endNode();
268     }
269
270     private DOMSource readDOMSource() throws IOException {
271         String xml = readObject().toString();
272         try {
273             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
274             factory.setNamespaceAware(true);
275             Element node = factory.newDocumentBuilder().parse(
276                     new InputSource(new StringReader(xml))).getDocumentElement();
277             return new DOMSource(node);
278         } catch (SAXException | ParserConfigurationException e) {
279             throw new IOException("Error parsing XML: " + xml, e);
280         }
281     }
282
283     @Override
284     public QName readQName() throws IOException {
285         // Read in the same sequence of writing
286         String localName = readCodedString();
287         String namespace = readCodedString();
288         String revision = Strings.emptyToNull(readCodedString());
289
290         return QNameFactory.create(localName, namespace, revision);
291     }
292
293     final String readCodedString() throws IOException {
294         final byte valueType = input.readByte();
295         switch (valueType) {
296             case TokenTypes.IS_NULL_VALUE:
297                 return null;
298             case TokenTypes.IS_CODE_VALUE:
299                 final int code = input.readInt();
300                 try {
301                     return codedStringMap.get(code);
302                 } catch (IndexOutOfBoundsException e) {
303                     throw new IOException("String code " + code + " was not found", e);
304                 }
305             case TokenTypes.IS_STRING_VALUE:
306                 final String value = input.readUTF().intern();
307                 codedStringMap.add(value);
308                 return value;
309             default:
310                 throw new IOException("Unhandled string value type " + valueType);
311         }
312     }
313
314     private Set<QName> readQNameSet() throws IOException {
315         // Read the children count
316         final int count = input.readInt();
317         final Set<QName> children = Sets.newHashSetWithExpectedSize(count);
318         for (int i = 0; i < count; i++) {
319             children.add(readQName());
320         }
321         return children;
322     }
323
324     AugmentationIdentifier readAugmentationIdentifier() throws IOException {
325         return AugmentationIdentifier.create(readQNameSet());
326     }
327
328     NodeIdentifier readNodeIdentifier() throws IOException {
329         return new NodeIdentifier(readQName());
330     }
331
332     private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
333         final QName qname = readQName();
334         final int count = input.readInt();
335         switch (count) {
336             case 0:
337                 return NodeIdentifierWithPredicates.of(qname);
338             case 1:
339                 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
340             default:
341                 // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
342                 final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
343                 final Object[] values = new Object[count];
344                 for (int i = 0; i < count; i++) {
345                     keys.add(readQName());
346                     values[i] = readObject();
347                 }
348
349                 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
350                     .instantiateWithValues(values));
351         }
352     }
353
354     private Object readObject() throws IOException {
355         byte objectType = input.readByte();
356         switch (objectType) {
357             case ValueTypes.BITS_TYPE:
358                 return readObjSet();
359
360             case ValueTypes.BOOL_TYPE:
361                 return input.readBoolean();
362
363             case ValueTypes.BYTE_TYPE:
364                 return input.readByte();
365
366             case ValueTypes.INT_TYPE:
367                 return input.readInt();
368
369             case ValueTypes.LONG_TYPE:
370                 return input.readLong();
371
372             case ValueTypes.QNAME_TYPE:
373                 return readQName();
374
375             case ValueTypes.SHORT_TYPE:
376                 return input.readShort();
377
378             case ValueTypes.STRING_TYPE:
379                 return input.readUTF();
380
381             case ValueTypes.STRING_BYTES_TYPE:
382                 return readStringBytes();
383
384             case ValueTypes.BIG_DECIMAL_TYPE:
385                 return new BigDecimal(input.readUTF());
386
387             case ValueTypes.BIG_INTEGER_TYPE:
388                 return new BigInteger(input.readUTF());
389
390             case ValueTypes.BINARY_TYPE:
391                 byte[] bytes = new byte[input.readInt()];
392                 input.readFully(bytes);
393                 return bytes;
394
395             case ValueTypes.YANG_IDENTIFIER_TYPE:
396                 return readYangInstanceIdentifierInternal();
397
398             case ValueTypes.EMPTY_TYPE:
399             // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
400             // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
401             // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
402             // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
403             // so we'd have to drop the leaf which might cause other issues.
404             case ValueTypes.NULL_TYPE:
405                 return Empty.getInstance();
406
407             default:
408                 return null;
409         }
410     }
411
412     private String readStringBytes() throws IOException {
413         byte[] bytes = new byte[input.readInt()];
414         input.readFully(bytes);
415         return new String(bytes, StandardCharsets.UTF_8);
416     }
417
418     @Override
419     public SchemaPath readSchemaPath() throws IOException {
420         final boolean absolute = input.readBoolean();
421         final int size = input.readInt();
422
423         final Builder<QName> qnames = ImmutableList.builderWithExpectedSize(size);
424         for (int i = 0; i < size; ++i) {
425             qnames.add(readQName());
426         }
427         return SchemaPath.create(qnames.build(), absolute);
428     }
429
430     @Override
431     public YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
432         return readYangInstanceIdentifierInternal();
433     }
434
435     private YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
436         int size = input.readInt();
437         final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
438         for (int i = 0; i < size; i++) {
439             pathArguments.add(readPathArgument());
440         }
441         return YangInstanceIdentifier.create(pathArguments.build());
442     }
443
444     private Set<String> readObjSet() throws IOException {
445         int count = input.readInt();
446         Set<String> children = new HashSet<>(count);
447         for (int i = 0; i < count; i++) {
448             children.add(readCodedString());
449         }
450         return children;
451     }
452
453     @Override
454     public PathArgument readPathArgument() throws IOException {
455         // read Type
456         int type = input.readByte();
457
458         switch (type) {
459             case PathArgumentTypes.AUGMENTATION_IDENTIFIER:
460                 return readAugmentationIdentifier();
461             case PathArgumentTypes.NODE_IDENTIFIER:
462                 return readNodeIdentifier();
463             case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
464                 return readNormalizedNodeWithPredicates();
465             case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE:
466                 return new NodeWithValue<>(readQName(), readObject());
467             default:
468                 // FIXME: throw hard error
469                 return null;
470         }
471     }
472 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.