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.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;
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.
53 class LithiumNormalizedNodeInputStreamReader extends ForwardingDataInput implements NormalizedNodeDataInput {
55 private static final Logger LOG = LoggerFactory.getLogger(LithiumNormalizedNodeInputStreamReader.class);
57 private final @NonNull DataInput input;
59 private final List<String> codedStringMap = new ArrayList<>();
61 private QName lastLeafSetQName;
63 LithiumNormalizedNodeInputStreamReader(final DataInput input) {
64 this.input = requireNonNull(input);
68 final DataInput delegate() {
73 public NormalizedNodeStreamVersion getVersion() throws IOException {
74 return NormalizedNodeStreamVersion.LITHIUM;
78 public void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
79 streamNormalizedNode(requireNonNull(writer), input.readByte());
82 private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final byte nodeType) throws IOException {
84 case NodeTypes.ANY_XML_NODE:
87 case NodeTypes.AUGMENTATION_NODE:
88 streamAugmentation(writer);
90 case NodeTypes.CHOICE_NODE:
93 case NodeTypes.CONTAINER_NODE:
94 streamContainer(writer);
96 case NodeTypes.LEAF_NODE:
99 case NodeTypes.LEAF_SET:
100 streamLeafSet(writer);
102 case NodeTypes.ORDERED_LEAF_SET:
103 streamOrderedLeafSet(writer);
105 case NodeTypes.LEAF_SET_ENTRY_NODE:
106 streamLeafSetEntry(writer);
108 case NodeTypes.MAP_ENTRY_NODE:
109 streamMapEntry(writer);
111 case NodeTypes.MAP_NODE:
114 case NodeTypes.ORDERED_MAP_NODE:
115 streamOrderedMap(writer);
117 case NodeTypes.UNKEYED_LIST:
118 streamUnkeyedList(writer);
120 case NodeTypes.UNKEYED_LIST_ITEM:
121 streamUnkeyedListItem(writer);
124 throw new InvalidNormalizedNodeStreamException("Unexpected node " + nodeType);
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());
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);
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);
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);
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());
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);
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);
179 private void commonStreamLeafSet(final NormalizedNodeStreamWriter writer, final NodeIdentifier identifier)
181 lastLeafSetQName = identifier.getNodeType();
183 commonStreamContainer(writer);
185 // Make sure we never leak this
186 lastLeafSetQName = null;
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);
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);
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);
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);
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);
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);
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);
242 private DOMSource readDOMSource() throws IOException {
243 String xml = readObject().toString();
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);
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());
262 return QNameFactory.create(localName, namespace, revision);
265 final String readCodedString() throws IOException {
266 final byte valueType = input.readByte();
268 case TokenTypes.IS_NULL_VALUE:
270 case TokenTypes.IS_CODE_VALUE:
271 final int code = input.readInt();
273 return codedStringMap.get(code);
274 } catch (IndexOutOfBoundsException e) {
275 throw new IOException("String code " + code + " was not found", e);
277 case TokenTypes.IS_STRING_VALUE:
278 final String value = input.readUTF().intern();
279 codedStringMap.add(value);
282 throw new IOException("Unhandled string value type " + valueType);
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());
296 AugmentationIdentifier readAugmentationIdentifier() throws IOException {
297 return AugmentationIdentifier.create(readQNameSet());
300 NodeIdentifier readNodeIdentifier() throws IOException {
301 return new NodeIdentifier(readQName());
304 private NodeIdentifierWithPredicates readNormalizedNodeWithPredicates() throws IOException {
305 final QName qname = readQName();
306 final int count = input.readInt();
309 return NodeIdentifierWithPredicates.of(qname);
311 return NodeIdentifierWithPredicates.of(qname, readQName(), readObject());
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();
321 return NodeIdentifierWithPredicates.of(qname, ImmutableOffsetMapTemplate.ordered(keys.build())
322 .instantiateWithValues(values));
326 private Object readObject() throws IOException {
327 byte objectType = input.readByte();
328 switch (objectType) {
329 case ValueTypes.BITS_TYPE:
332 case ValueTypes.BOOL_TYPE:
333 return input.readBoolean();
335 case ValueTypes.BYTE_TYPE:
336 return input.readByte();
338 case ValueTypes.INT_TYPE:
339 return input.readInt();
341 case ValueTypes.LONG_TYPE:
342 return input.readLong();
344 case ValueTypes.QNAME_TYPE:
347 case ValueTypes.SHORT_TYPE:
348 return input.readShort();
350 case ValueTypes.STRING_TYPE:
351 return input.readUTF();
353 case ValueTypes.STRING_BYTES_TYPE:
354 return readStringBytes();
356 case ValueTypes.BIG_DECIMAL_TYPE:
357 return new BigDecimal(input.readUTF());
359 case ValueTypes.BIG_INTEGER_TYPE:
360 return new BigInteger(input.readUTF());
362 case ValueTypes.BINARY_TYPE:
363 byte[] bytes = new byte[input.readInt()];
364 input.readFully(bytes);
367 case ValueTypes.YANG_IDENTIFIER_TYPE:
368 return readYangInstanceIdentifierInternal();
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();
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);
391 public SchemaPath readSchemaPath() throws IOException {
392 final boolean absolute = input.readBoolean();
393 final int size = input.readInt();
395 final Builder<QName> qnames = ImmutableList.builderWithExpectedSize(size);
396 for (int i = 0; i < size; ++i) {
397 qnames.add(readQName());
399 return SchemaPath.create(qnames.build(), absolute);
403 public YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
404 return readYangInstanceIdentifierInternal();
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());
413 return YangInstanceIdentifier.create(pathArguments.build());
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());
426 public PathArgument readPathArgument() throws IOException {
428 int type = input.readByte();
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());
440 // FIXME: throw hard error