Merge branch 'master' of ../controller
[yangtools.git] / yang / rfc7952-data-util / src / main / java / org / opendaylight / yangtools / rfc7952 / data / util / NormalizedMetadataWriter.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.yangtools.rfc7952.data.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import java.io.Closeable;
15 import java.io.Flushable;
16 import java.io.IOException;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata;
19 import org.opendaylight.yangtools.rfc7952.data.api.StreamWriterMetadataExtension;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
21 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
23 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
24
25 /**
26  * A utility class to attach {@link NormalizedMetadata} into a NormalizedNode stream, such as the one produced by
27  * {@link NormalizedNodeWriter}, so that a target {@link NormalizedNodeStreamWriter} sees both data and metadata in
28  * the stream. A typical use would like this:
29  *
30  * <p>
31  * <code>
32  *   // Data for output
33  *   NormalizedNode&lt;?, ?&gt; data;
34  *   // Metadata for output
35  *   NormalizedMetadata metadata;
36  *
37  *   // Target output writer
38  *   NormalizedNodeStreamWriter output = ...;
39  *   // Metadata writer
40  *   NormalizedMetadataStreamWriter metaWriter = NormalizedMetadataWriter.forStreamWriter(output);
41  *
42  *   // Write a normalized node and its metadata
43  *   dataWriter.write(data, metadata);
44  * </code>
45  *
46  * <p>
47  * This class is NOT thread-safe.
48  *
49  * @author Robert Varga
50  */
51 @Beta
52 // FIXME: 5.0.0: consider moving this class to api to keep related stuff together
53 public final class NormalizedMetadataWriter implements Closeable, Flushable {
54     private final NormalizedNodeStreamWriter writer;
55     private final boolean orderKeyLeaves;
56
57     private NormalizedMetadataWriter(final NormalizedNodeStreamWriter writer, final boolean orderKeyLeaves) {
58         this.writer = requireNonNull(writer);
59         this.orderKeyLeaves = orderKeyLeaves;
60     }
61
62     /**
63      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple
64      * {@link #forStreamWriter(NormalizedNodeStreamWriter)} method, this allows the caller to switch off RFC6020 XML
65      * compliance, providing better throughput. The reason is that the XML mapping rules in RFC6020 require
66      * the encoding to emit leaf nodes which participate in a list's key first and in the order in which they are
67      * defined in the key. For JSON, this requirement is completely relaxed and leaves can be ordered in any way we
68      * see fit. The former requires a bit of work: first a lookup for each key and then for each emitted node we need
69      * to check whether it was already emitted.
70      *
71      * @param writer Back-end writer
72      * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
73      * @return A new instance.
74      */
75     public static @NonNull NormalizedMetadataWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
76             final boolean orderKeyLeaves) {
77         return new NormalizedMetadataWriter(writer, orderKeyLeaves);
78     }
79
80     /**
81      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. This is a convenience method for
82      * {@code forStreamWriter(writer, true)}.
83      *
84      * @param writer Back-end writer
85      * @return A new instance.
86      */
87     public static @NonNull NormalizedMetadataWriter forStreamWriter(final NormalizedNodeStreamWriter writer) {
88         return forStreamWriter(writer, true);
89     }
90
91     /**
92      * Iterate over the provided {@link NormalizedNode} and {@link NormalizedMetadata} and emit write events to the
93      * encapsulated {@link NormalizedNodeStreamWriter}.
94      *
95      * @param data NormalizedNode data
96      * @param metadata {@link NormalizedMetadata} metadata
97      * @return NormalizedNodeWriter this
98      * @throws NullPointerException if any argument is null
99      * @throws IllegalArgumentException if metadata does not match data
100      * @throws IOException when thrown from the backing writer.
101      */
102     public @NonNull NormalizedMetadataWriter write(final NormalizedNode<?, ?> data, final NormalizedMetadata metadata)
103             throws IOException {
104         final PathArgument dataId = data.getIdentifier();
105         final PathArgument metaId = metadata.getIdentifier();
106         checkArgument(dataId.equals(metaId), "Mismatched data %s and metadata %s", dataId, metaId);
107
108         final StreamWriterMetadataExtension metaWriter = writer.getExtensions()
109                 .getInstance(StreamWriterMetadataExtension.class);
110         final NormalizedNodeStreamWriter delegate = metaWriter == null ? writer
111                 : new NormalizedNodeStreamWriterMetadataDecorator(writer, metaWriter, metadata);
112
113         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(delegate, orderKeyLeaves);
114         nnWriter.write(data);
115         nnWriter.flush();
116         return this;
117     }
118
119     @Override
120     public void close() throws IOException {
121         try {
122             writer.flush();
123         } finally {
124             writer.close();
125         }
126     }
127
128     @Override
129     public void flush() throws IOException {
130         writer.flush();
131     }
132 }