2 * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.cluster.datastore.node.utils.stream;
10 import static java.util.Objects.requireNonNull;
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;
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;
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.
51 abstract class AbstractLithiumDataInput extends AbstractNormalizedNodeDataInput {
53 private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataInput.class);
55 private final List<String> codedStringMap = new ArrayList<>();
57 private QName lastLeafSetQName;
59 AbstractLithiumDataInput(final DataInput input) {
64 public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
65 streamNormalizedNode(requireNonNull(writer), input.readByte());
68 private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
70 case NodeTypes.ANY_XML_NODE:
73 case NodeTypes.AUGMENTATION_NODE:
74 streamAugmentation(writer);
76 case NodeTypes.CHOICE_NODE:
79 case NodeTypes.CONTAINER_NODE:
80 streamContainer(writer);
82 case NodeTypes.LEAF_NODE:
85 case NodeTypes.LEAF_SET:
86 streamLeafSet(writer);
88 case NodeTypes.ORDERED_LEAF_SET:
89 streamOrderedLeafSet(writer);
91 case NodeTypes.LEAF_SET_ENTRY_NODE:
92 streamLeafSetEntry(writer);
94 case NodeTypes.MAP_ENTRY_NODE:
95 streamMapEntry(writer);
97 case NodeTypes.MAP_NODE:
100 case NodeTypes.ORDERED_MAP_NODE:
101 streamOrderedMap(writer);
103 case NodeTypes.UNKEYED_LIST:
104 streamUnkeyedList(writer);
106 case NodeTypes.UNKEYED_LIST_ITEM:
107 streamUnkeyedListItem(writer);
110 throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
114 private void streamAnyxml(final NormalizedNodeStreamWriter writer) throws IOException {
115 final NodeIdentifier identifier = readNodeIdentifier();
116 LOG.trace("Streaming anyxml node {}", identifier);
117 writer.startAnyxmlNode(identifier);
118 writer.domSourceValue(readDOMSource());
122 private void streamAugmentation(final NormalizedNodeStreamWriter writer) throws IOException {
123 final AugmentationIdentifier augIdentifier = readAugmentationIdentifier();
124 LOG.trace("Streaming augmentation node {}", augIdentifier);
125 writer.startAugmentationNode(augIdentifier);
126 commonStreamContainer(writer);
129 private void streamChoice(final NormalizedNodeStreamWriter writer) throws IOException {
130 final NodeIdentifier identifier = readNodeIdentifier();
131 LOG.trace("Streaming choice node {}", identifier);
132 writer.startChoiceNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
133 commonStreamContainer(writer);
136 private void streamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
137 final NodeIdentifier identifier = readNodeIdentifier();
138 LOG.trace("Streaming container node {}", identifier);
139 writer.startContainerNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
140 commonStreamContainer(writer);
143 private void streamLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
145 endLeaf(writer, readObject());
148 // Leaf inside a MapEntryNode, it can potentially be a key leaf, in which case we want to de-duplicate values.
149 private void streamLeaf(final NormalizedNodeStreamWriter writer, final NodeIdentifierWithPredicates entryId)
151 final NodeIdentifier identifier = startLeaf(writer);
152 final Object value = readObject();
153 final Object entryValue = entryId.getValue(identifier.getNodeType());
154 endLeaf(writer, entryValue == null ? value : entryValue);
157 private NodeIdentifier startLeaf(final NormalizedNodeStreamWriter writer) throws IOException {
158 final NodeIdentifier identifier = readNodeIdentifier();
159 LOG.trace("Streaming leaf node {}", identifier);
160 writer.startLeafNode(identifier);
164 private static void endLeaf(final NormalizedNodeStreamWriter writer, final Object value) throws IOException {
165 writer.scalarValue(value);
169 private void streamLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
170 final NodeIdentifier identifier = readNodeIdentifier();
171 LOG.trace("Streaming leaf set node {}", identifier);
172 writer.startLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
173 commonStreamLeafSet(writer, identifier);
176 private void streamOrderedLeafSet(final NormalizedNodeStreamWriter writer) throws IOException {
177 final NodeIdentifier identifier = readNodeIdentifier();
178 LOG.trace("Streaming ordered leaf set node {}", identifier);
179 writer.startOrderedLeafSet(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
180 commonStreamLeafSet(writer, identifier);
183 private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
185 lastLeafSetQName = identifier.getNodeType();
187 commonStreamContainer(writer);
189 // Make sure we never leak this
190 lastLeafSetQName = null;
194 private void streamLeafSetEntry(final NormalizedNodeStreamWriter writer) throws IOException {
195 final QName name = lastLeafSetQName != null ? lastLeafSetQName : readQName();
196 final Object value = readObject();
197 final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(name, value);
198 LOG.trace("Streaming leaf set entry node {}, value {}", leafIdentifier, value);
199 writer.startLeafSetEntryNode(leafIdentifier);
200 writer.scalarValue(value);
204 private void streamMap(final NormalizedNodeStreamWriter writer) throws IOException {
205 final NodeIdentifier identifier = readNodeIdentifier();
206 LOG.trace("Streaming map node {}", identifier);
207 writer.startMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
208 commonStreamContainer(writer);
211 private void streamOrderedMap(final NormalizedNodeStreamWriter writer) throws IOException {
212 final NodeIdentifier identifier = readNodeIdentifier();
213 LOG.trace("Streaming ordered map node {}", identifier);
214 writer.startOrderedMapNode(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
215 commonStreamContainer(writer);
218 private void streamMapEntry(final NormalizedNodeStreamWriter writer) throws IOException {
219 final NodeIdentifierWithPredicates entryIdentifier = readNormalizedNodeWithPredicates();
220 LOG.trace("Streaming map entry node {}", entryIdentifier);
221 writer.startMapEntryNode(entryIdentifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
223 // Same loop as commonStreamContainer(), but ...
224 for (byte nodeType = input.readByte(); nodeType != NodeTypes.END_NODE; nodeType = input.readByte()) {
225 if (nodeType == NodeTypes.LEAF_NODE) {
226 // ... leaf nodes may need de-duplication
227 streamLeaf(writer, entryIdentifier);
229 streamNormalizedNode(writer, nodeType);
235 private void streamUnkeyedList(final NormalizedNodeStreamWriter writer) throws IOException {
236 final NodeIdentifier identifier = readNodeIdentifier();
237 LOG.trace("Streaming unkeyed list node {}", identifier);
238 writer.startUnkeyedList(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
239 commonStreamContainer(writer);
242 private void streamUnkeyedListItem(final NormalizedNodeStreamWriter writer) throws IOException {
243 final NodeIdentifier identifier = readNodeIdentifier();
244 LOG.trace("Streaming unkeyed list item node {}", identifier);
245 writer.startUnkeyedListItem(identifier, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
246 commonStreamContainer(writer);
249 private void commonStreamContainer(final NormalizedNodeStreamWriter writer) throws IOException {
250 for (byte nodeType = input.readByte(); nodeType != NodeTypes.END_NODE; nodeType = input.readByte()) {
251 streamNormalizedNode(writer, nodeType);
256 private DOMSource readDOMSource() throws IOException {
257 String xml = readObject().toString();
259 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
260 factory.setNamespaceAware(true);
261 Element node = factory.newDocumentBuilder().parse(
262 new InputSource(new StringReader(xml))).getDocumentElement();
263 return new DOMSource(node);
264 } catch (SAXException | ParserConfigurationException e) {
265 throw new IOException("Error parsing XML: " + xml, e);
269 final QName defaultReadQName() throws IOException {
270 // Read in the same sequence of writing
271 String localName = readCodedString();
272 String namespace = readCodedString();
273 String revision = Strings.emptyToNull(readCodedString());
275 return QNameFactory.create(localName, namespace, revision);
278 final String readCodedString() throws IOException {
279 final byte valueType = input.readByte();
281 case TokenTypes.IS_NULL_VALUE:
283 case TokenTypes.IS_CODE_VALUE:
284 final int code = input.readInt();
286 return codedStringMap.get(code);
287 } catch (IndexOutOfBoundsException e) {
288 throw new IOException("String code " + code + " was not found", e);
290 case TokenTypes.IS_STRING_VALUE:
291 final String value = input.readUTF().intern();
292 codedStringMap.add(value);
295 throw new IOException("Unhandled string value type " + valueType);
299 private Set<QName> readQNameSet() throws IOException {
300 // Read the children count
301 final int count = input.readInt();
302 final Set<QName> children = Sets.newHashSetWithExpectedSize(count);
303 for (int i = 0; i < count; i++) {
304 children.add(readQName());
309 abstract AugmentationIdentifier readAugmentationIdentifier() throws IOException;
311 abstract NodeIdentifier readNodeIdentifier() throws IOException;
313 final AugmentationIdentifier defaultReadAugmentationIdentifier() throws IOException {
314 return AugmentationIdentifier.create(readQNameSet());
317 private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
318 final QName qname = readQName();
319 final int count = input.readInt();
322 return NodeIdentifierWithPredicates.of(qname);
324 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
326 // ImmutableList is used by ImmutableOffsetMapTemplate for lookups, hence we use that.
327 final Builder<QName> keys = ImmutableList.builderWithExpectedSize(count);
328 final Object[] values = new Object[count];
329 for (int i = 0; i < count; i++) {
330 keys.add(readQName());
331 values[i] = readObject();
334 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
335 .instantiateWithValues(values));
339 private Object readObject() throws IOException {
340 byte objectType = input.readByte();
341 switch (objectType) {
342 case ValueTypes.BITS_TYPE:
345 case ValueTypes.BOOL_TYPE:
346 return input.readBoolean();
348 case ValueTypes.BYTE_TYPE:
349 return input.readByte();
351 case ValueTypes.INT_TYPE:
352 return input.readInt();
354 case ValueTypes.LONG_TYPE:
355 return input.readLong();
357 case ValueTypes.QNAME_TYPE:
360 case ValueTypes.SHORT_TYPE:
361 return input.readShort();
363 case ValueTypes.STRING_TYPE:
364 return input.readUTF();
366 case ValueTypes.STRING_BYTES_TYPE:
367 return readStringBytes();
369 case ValueTypes.BIG_DECIMAL_TYPE:
370 return new BigDecimal(input.readUTF());
372 case ValueTypes.BIG_INTEGER_TYPE:
373 return new BigInteger(input.readUTF());
375 case ValueTypes.BINARY_TYPE:
376 byte[] bytes = new byte[input.readInt()];
377 input.readFully(bytes);
380 case ValueTypes.YANG_IDENTIFIER_TYPE:
381 return readYangInstanceIdentifierInternal();
383 case ValueTypes.EMPTY_TYPE:
384 // Leaf nodes no longer allow null values and thus we no longer emit null values. Previously, the "empty"
385 // yang type was represented as null so we translate an incoming null value to Empty. It was possible for
386 // a BI user to set a string leaf to null and we're rolling the dice here but the chances for that are
387 // very low. We'd have to know the yang type but, even if we did, we can't let a null value pass upstream
388 // so we'd have to drop the leaf which might cause other issues.
389 case ValueTypes.NULL_TYPE:
390 return Empty.getInstance();
397 private String readStringBytes() throws IOException {
398 byte[] bytes = new byte[input.readInt()];
399 input.readFully(bytes);
400 return new String(bytes, StandardCharsets.UTF_8);
404 public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
405 return readYangInstanceIdentifierInternal();
408 private YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException {
409 int size = input.readInt();
410 final Builder<PathArgument> pathArguments = ImmutableList.builderWithExpectedSize(size);
411 for (int i = 0; i < size; i++) {
412 pathArguments.add(readPathArgument());
414 return YangInstanceIdentifier.create(pathArguments.build());
417 private Set<String> readObjSet() throws IOException {
418 int count = input.readInt();
419 Set<String> children = new HashSet<>(count);
420 for (int i = 0; i < count; i++) {
421 children.add(readCodedString());
427 public final PathArgument readPathArgument() throws IOException {
429 int type = input.readByte();
432 case PathArgumentTypes.AUGMENTATION_IDENTIFIER:
433 return readAugmentationIdentifier();
434 case PathArgumentTypes.NODE_IDENTIFIER:
435 return readNodeIdentifier();
436 case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
437 return readNormalizedNodeWithPredicates();
438 case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE:
439 return new NodeWithValue<>(readQName(), readObject());
441 // FIXME: throw hard error