e8b7547a1ee8c0346a7c0784ddc68ec811ba6e37
[controller.git] / opendaylight / md-sal / sal-clustering-commons / src / main / java / org / opendaylight / controller / cluster / datastore / node / utils / stream / AbstractLithiumDataOutput.java
1 /*
2  * Copyright (c) 2014 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 com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static com.google.common.base.Verify.verifyNotNull;
13 import static java.util.Objects.requireNonNull;
14
15 import com.google.common.annotations.VisibleForTesting;
16 import com.google.common.collect.ImmutableMap;
17 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
18 import java.io.DataOutput;
19 import java.io.IOException;
20 import java.io.StringWriter;
21 import java.math.BigDecimal;
22 import java.math.BigInteger;
23 import java.nio.charset.StandardCharsets;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Optional;
28 import java.util.Set;
29 import javax.xml.transform.TransformerException;
30 import javax.xml.transform.TransformerFactory;
31 import javax.xml.transform.TransformerFactoryConfigurationError;
32 import javax.xml.transform.dom.DOMSource;
33 import javax.xml.transform.stream.StreamResult;
34 import org.eclipse.jdt.annotation.NonNull;
35 import org.opendaylight.yangtools.yang.common.Empty;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.common.QNameModule;
38 import org.opendaylight.yangtools.yang.common.Revision;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * "original" type mapping. Baseline is Lithium but it really was introduced in Oxygen, where {@code type empty} was
50  * remapped from null.
51  *
52  * <p>
53  * {@code uint8}, {@code uint16}, {@code uint32} use java.lang types with widening, hence their value types overlap with
54  * mapping of {@code int16}, {@code int32} and {@code int64}, making that difference indiscernible without YANG schema
55  * knowledge.
56  */
57 abstract class AbstractLithiumDataOutput extends AbstractNormalizedNodeDataOutput {
58     private static final Logger LOG = LoggerFactory.getLogger(AbstractLithiumDataOutput.class);
59     private static final ImmutableMap<Class<?>, Byte> KNOWN_TYPES = ImmutableMap.<Class<?>, Byte>builder()
60             .put(String.class, ValueTypes.STRING_TYPE)
61             .put(Byte.class, ValueTypes.BYTE_TYPE)
62             .put(Integer.class, ValueTypes.INT_TYPE)
63             .put(Long.class, ValueTypes.LONG_TYPE)
64             .put(Boolean.class, ValueTypes.BOOL_TYPE)
65             .put(QName.class, ValueTypes.QNAME_TYPE)
66             .put(Short.class, ValueTypes.SHORT_TYPE)
67             .put(BigInteger.class, ValueTypes.BIG_INTEGER_TYPE)
68             .put(BigDecimal.class, ValueTypes.BIG_DECIMAL_TYPE)
69             .put(byte[].class, ValueTypes.BINARY_TYPE)
70             .put(Empty.class, ValueTypes.EMPTY_TYPE)
71             .build();
72
73     private final Map<String, Integer> stringCodeMap = new HashMap<>();
74
75     private QName lastLeafSetQName;
76     private boolean inSimple;
77
78     AbstractLithiumDataOutput(final DataOutput output) {
79         super(output);
80     }
81
82     @Override
83     public final void startLeafNode(final NodeIdentifier name) throws IOException {
84         LOG.trace("Starting a new leaf node");
85         startNode(name, NodeTypes.LEAF_NODE);
86         inSimple = true;
87     }
88
89     @Override
90     public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
91         LOG.trace("Starting a new leaf set");
92         commonStartLeafSet(name, NodeTypes.LEAF_SET);
93     }
94
95     @Override
96     public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
97         LOG.trace("Starting a new ordered leaf set");
98         commonStartLeafSet(name, NodeTypes.ORDERED_LEAF_SET);
99     }
100
101     private void commonStartLeafSet(final NodeIdentifier name, final byte nodeType) throws IOException {
102         startNode(name, nodeType);
103         lastLeafSetQName = name.getNodeType();
104     }
105
106     @Override
107     public final void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
108         LOG.trace("Starting a new leaf set entry node");
109
110         output.writeByte(NodeTypes.LEAF_SET_ENTRY_NODE);
111
112         // lastLeafSetQName is set if the parent LeafSetNode was previously written. Otherwise this is a
113         // stand alone LeafSetEntryNode so write out it's name here.
114         if (lastLeafSetQName == null) {
115             writeQNameInternal(name.getNodeType());
116         }
117         inSimple = true;
118     }
119
120     @Override
121     public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
122         LOG.trace("Starting a new container node");
123         startNode(name, NodeTypes.CONTAINER_NODE);
124     }
125
126     @Override
127     public final void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint)
128             throws IOException {
129         LOG.trace("Starting a new yang modeled anyXml node");
130         startNode(name, NodeTypes.YANG_MODELED_ANY_XML_NODE);
131     }
132
133     @Override
134     public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
135         LOG.trace("Starting a new unkeyed list");
136         startNode(name, NodeTypes.UNKEYED_LIST);
137     }
138
139     @Override
140     public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
141         LOG.trace("Starting a new unkeyed list item");
142         startNode(name, NodeTypes.UNKEYED_LIST_ITEM);
143     }
144
145     @Override
146     public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
147         LOG.trace("Starting a new map node");
148         startNode(name, NodeTypes.MAP_NODE);
149     }
150
151     @Override
152     public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
153             throws IOException {
154         LOG.trace("Starting a new map entry node");
155         startNode(identifier, NodeTypes.MAP_ENTRY_NODE);
156         writeKeyValueMap(identifier.entrySet());
157     }
158
159     @Override
160     public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
161         LOG.trace("Starting a new ordered map node");
162         startNode(name, NodeTypes.ORDERED_MAP_NODE);
163     }
164
165     @Override
166     public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
167         LOG.trace("Starting a new choice node");
168         startNode(name, NodeTypes.CHOICE_NODE);
169     }
170
171     @Override
172     public final void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
173         requireNonNull(identifier, "Node identifier should not be null");
174         LOG.trace("Starting a new augmentation node");
175
176         output.writeByte(NodeTypes.AUGMENTATION_NODE);
177         writeAugmentationIdentifier(identifier);
178     }
179
180     @Override
181     public final void startAnyxmlNode(final NodeIdentifier name) throws IOException {
182         LOG.trace("Starting any xml node");
183         startNode(name, NodeTypes.ANY_XML_NODE);
184         inSimple = true;
185     }
186
187     @Override
188     public final void scalarValue(final Object value) throws IOException {
189         writeObject(value);
190     }
191
192     @Override
193     public final void domSourceValue(final DOMSource value) throws IOException {
194         try {
195             StreamResult xmlOutput = new StreamResult(new StringWriter());
196             TransformerFactory.newInstance().newTransformer().transform(value, xmlOutput);
197             writeObject(xmlOutput.getWriter().toString());
198         } catch (TransformerException | TransformerFactoryConfigurationError e) {
199             throw new IOException("Error writing anyXml", e);
200         }
201     }
202
203     @Override
204     public final void endNode() throws IOException {
205         LOG.trace("Ending the node");
206         if (!inSimple) {
207             lastLeafSetQName = null;
208             output.writeByte(NodeTypes.END_NODE);
209         }
210         inSimple = false;
211     }
212
213     @Override
214     @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST",
215             justification = "The casts in the switch clauses are indirectly confirmed via the determination of 'type'.")
216     final void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
217         final byte type = PathArgumentTypes.getSerializablePathArgumentType(pathArgument);
218         output.writeByte(type);
219
220         switch (type) {
221             case PathArgumentTypes.NODE_IDENTIFIER:
222                 NodeIdentifier nodeIdentifier = (NodeIdentifier) pathArgument;
223                 writeQNameInternal(nodeIdentifier.getNodeType());
224                 break;
225             case PathArgumentTypes.NODE_IDENTIFIER_WITH_PREDICATES:
226                 NodeIdentifierWithPredicates nodeIdentifierWithPredicates =
227                     (NodeIdentifierWithPredicates) pathArgument;
228                 writeQNameInternal(nodeIdentifierWithPredicates.getNodeType());
229                 writeKeyValueMap(nodeIdentifierWithPredicates.entrySet());
230                 break;
231             case PathArgumentTypes.NODE_IDENTIFIER_WITH_VALUE:
232                 NodeWithValue<?> nodeWithValue = (NodeWithValue<?>) pathArgument;
233                 writeQNameInternal(nodeWithValue.getNodeType());
234                 writeObject(nodeWithValue.getValue());
235                 break;
236             case PathArgumentTypes.AUGMENTATION_IDENTIFIER:
237                 // No Qname in augmentation identifier
238                 writeAugmentationIdentifier((AugmentationIdentifier) pathArgument);
239                 break;
240             default:
241                 throw new IllegalStateException("Unknown node identifier type is found : "
242                         + pathArgument.getClass().toString());
243         }
244     }
245
246     final void defaultWriteAugmentationIdentifier(final @NonNull AugmentationIdentifier aid) throws IOException {
247         final Set<QName> qnames = aid.getPossibleChildNames();
248         // Write each child's qname separately, if list is empty send count as 0
249         if (!qnames.isEmpty()) {
250             output.writeInt(qnames.size());
251             for (QName qname : qnames) {
252                 writeQNameInternal(qname);
253             }
254         } else {
255             LOG.debug("augmentation node does not have any child");
256             output.writeInt(0);
257         }
258     }
259
260     final void defaultWriteQName(final QName qname) throws IOException {
261         writeString(qname.getLocalName());
262         writeModule(qname.getModule());
263     }
264
265     final void defaultWriteModule(final QNameModule module) throws IOException {
266         writeString(module.getNamespace().toString());
267         final Optional<Revision> revision = module.getRevision();
268         if (revision.isPresent()) {
269             writeString(revision.get().toString());
270         } else {
271             writeByte(TokenTypes.IS_NULL_VALUE);
272         }
273     }
274
275     abstract void writeModule(QNameModule module) throws IOException;
276
277     abstract void writeAugmentationIdentifier(@NonNull AugmentationIdentifier aid) throws IOException;
278
279     private void startNode(final PathArgument arg, final byte nodeType) throws IOException {
280         requireNonNull(arg, "Node identifier should not be null");
281         checkState(!inSimple, "Attempted to start a child in a simple node");
282
283         // First write the type of node
284         output.writeByte(nodeType);
285         // Write Start Tag
286         writeQNameInternal(arg.getNodeType());
287     }
288
289     private void writeObjSet(final Set<?> set) throws IOException {
290         output.writeInt(set.size());
291         for (Object o : set) {
292             checkArgument(o instanceof String, "Expected value type to be String but was %s (%s)", o.getClass(), o);
293             writeString((String) o);
294         }
295     }
296
297     private void writeObject(final Object value) throws IOException {
298         byte type = getSerializableType(value);
299         // Write object type first
300         output.writeByte(type);
301
302         switch (type) {
303             case ValueTypes.BOOL_TYPE:
304                 output.writeBoolean((Boolean) value);
305                 break;
306             case ValueTypes.QNAME_TYPE:
307                 writeQNameInternal((QName) value);
308                 break;
309             case ValueTypes.INT_TYPE:
310                 output.writeInt((Integer) value);
311                 break;
312             case ValueTypes.BYTE_TYPE:
313                 output.writeByte((Byte) value);
314                 break;
315             case ValueTypes.LONG_TYPE:
316                 output.writeLong((Long) value);
317                 break;
318             case ValueTypes.SHORT_TYPE:
319                 output.writeShort((Short) value);
320                 break;
321             case ValueTypes.BITS_TYPE:
322                 writeObjSet((Set<?>) value);
323                 break;
324             case ValueTypes.BINARY_TYPE:
325                 byte[] bytes = (byte[]) value;
326                 output.writeInt(bytes.length);
327                 output.write(bytes);
328                 break;
329             case ValueTypes.YANG_IDENTIFIER_TYPE:
330                 writeYangInstanceIdentifierInternal((YangInstanceIdentifier) value);
331                 break;
332             case ValueTypes.EMPTY_TYPE:
333                 break;
334             case ValueTypes.STRING_BYTES_TYPE:
335                 final byte[] valueBytes = value.toString().getBytes(StandardCharsets.UTF_8);
336                 output.writeInt(valueBytes.length);
337                 output.write(valueBytes);
338                 break;
339             default:
340                 output.writeUTF(value.toString());
341                 break;
342         }
343     }
344
345     private void writeKeyValueMap(final Set<Entry<QName, Object>> entrySet) throws IOException {
346         if (!entrySet.isEmpty()) {
347             output.writeInt(entrySet.size());
348             for (Entry<QName, Object> entry : entrySet) {
349                 writeQNameInternal(entry.getKey());
350                 writeObject(entry.getValue());
351             }
352         } else {
353             output.writeInt(0);
354         }
355     }
356
357     private void writeString(final @NonNull String string) throws IOException {
358         final Integer value = stringCodeMap.get(verifyNotNull(string));
359         if (value == null) {
360             stringCodeMap.put(string, stringCodeMap.size());
361             writeByte(TokenTypes.IS_STRING_VALUE);
362             writeUTF(string);
363         } else {
364             writeByte(TokenTypes.IS_CODE_VALUE);
365             writeInt(value);
366         }
367     }
368
369     @VisibleForTesting
370     static final byte getSerializableType(final Object node) {
371         final Byte type = KNOWN_TYPES.get(requireNonNull(node).getClass());
372         if (type != null) {
373             if (type == ValueTypes.STRING_TYPE
374                     && ((String) node).length() >= ValueTypes.STRING_BYTES_LENGTH_THRESHOLD) {
375                 return ValueTypes.STRING_BYTES_TYPE;
376             }
377             return type;
378         }
379
380         if (node instanceof Set) {
381             return ValueTypes.BITS_TYPE;
382         }
383
384         if (node instanceof YangInstanceIdentifier) {
385             return ValueTypes.YANG_IDENTIFIER_TYPE;
386         }
387
388         throw new IllegalArgumentException("Unknown value type " + node.getClass().getSimpleName());
389     }
390 }