88d3a533ec2897c38905444fdf3182004585aaa0
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / spi / NormalizedNodeWriter.java
1 /*
2  * Copyright (c) 2024 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.restconf.server.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.io.Closeable;
13 import java.io.Flushable;
14 import java.io.IOException;
15 import java.util.List;
16 import java.util.Set;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.restconf.api.query.DepthParam;
20 import org.opendaylight.yangtools.yang.common.QName;
21 import org.opendaylight.yangtools.yang.data.api.schema.AnydataNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.AnyxmlNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
25 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
34
35 /**
36  * This is an experimental iterator over a {@link NormalizedNode}. This is essentially
37  * the opposite of a {@link javax.xml.stream.XMLStreamReader} -- unlike instantiating an iterator over
38  * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows
39  * us to write multiple nodes.
40  */
41 @NonNullByDefault
42 public abstract class NormalizedNodeWriter implements Flushable, Closeable {
43     protected final NormalizedNodeStreamWriter writer;
44
45     NormalizedNodeWriter(final NormalizedNodeStreamWriter writer) {
46         this.writer = requireNonNull(writer);
47     }
48
49     /**
50      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
51      *
52      * @param writer Back-end writer
53      * @param maxDepth Maximal depth to write
54      * @return A new instance.
55      */
56     public static final NormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
57             final @Nullable DepthParam maxDepth) {
58         return forStreamWriter(writer, true,  maxDepth, null);
59     }
60
61     /**
62      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}.
63      *
64      * @param writer Back-end writer
65      * @param maxDepth Maximal depth to write
66      * @param fields Selected child nodes to write
67      * @return A new instance.
68      */
69     public static final NormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
70             final @Nullable DepthParam maxDepth, final @Nullable List<Set<QName>> fields) {
71         return forStreamWriter(writer, true,  maxDepth, fields);
72     }
73
74     /**
75      * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple
76      * {@link #forStreamWriter(NormalizedNodeStreamWriter, DepthParam, List)} method, this allows the caller to
77      * switch off RFC6020 XML compliance, providing better throughput. The reason is that the XML mapping rules in
78      * RFC6020 require the encoding to emit leaf nodes which participate in a list's key first and in the order in which
79      * they are defined in the key. For JSON, this requirement is completely relaxed and leaves can be ordered in any
80      * way we see fit. The former requires a bit of work: first a lookup for each key and then for each emitted node we
81      * need to check whether it was already emitted.
82      *
83      * @param writer Back-end writer
84      * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant.
85      * @param depth Maximal depth to write
86      * @param fields Selected child nodes to write
87      * @return A new instance.
88      */
89     public static final NormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer,
90             final boolean orderKeyLeaves, final @Nullable DepthParam depth, final @Nullable List<Set<QName>> fields) {
91         return new DefaultNormalizedNodeWriter(writer, !orderKeyLeaves, depth, fields);
92     }
93
94     @Override
95     public final void flush() throws IOException {
96         writer.flush();
97     }
98
99     @Override
100     public final void close() throws IOException {
101         writer.flush();
102         writer.close();
103     }
104
105     /**
106      * Iterate over the provided {@link NormalizedNode} and emit write events to the encapsulated
107      * {@link NormalizedNodeStreamWriter}.
108      *
109      * @param node Node
110      * @return {@code ParameterAwareNormalizedNodeWriter}
111      * @throws IOException when thrown from the backing writer.
112      */
113     public final NormalizedNodeWriter write(final NormalizedNode node) throws IOException {
114         if (node instanceof ContainerNode n) {
115             writeContainer(n);
116         } else if (node instanceof MapNode n) {
117             writeMap(n);
118         } else if (node instanceof MapEntryNode n) {
119             writeMapEntry(n);
120         } else if (node instanceof LeafNode<?> n) {
121             writeLeaf(n);
122         } else if (node instanceof ChoiceNode n) {
123             writeChoice(n);
124         } else if (node instanceof UnkeyedListNode n) {
125             writeUnkeyedList(n);
126         } else if (node instanceof UnkeyedListEntryNode n) {
127             writeUnkeyedListEntry(n);
128         } else if (node instanceof LeafSetNode<?> n) {
129             writeLeafSet(n);
130         } else if (node instanceof LeafSetEntryNode<?> n) {
131             writeLeafSetEntry(n);
132         } else if (node instanceof AnydataNode<?> n) {
133             writeAnydata(n);
134         } else if (node instanceof AnyxmlNode<?> n) {
135             writeAnyxml(n);
136         } else {
137             throw new IOException("Unhandled contract " + node.contract().getSimpleName());
138         }
139         return this;
140     }
141
142     protected abstract void writeAnydata(AnydataNode<?> node) throws IOException;
143
144     protected abstract void writeAnyxml(AnyxmlNode<?> node) throws IOException;
145
146     protected abstract void writeChoice(ChoiceNode node) throws IOException;
147
148     protected abstract void writeContainer(ContainerNode node) throws IOException;
149
150     protected abstract void writeLeaf(LeafNode<?> node) throws IOException;
151
152     protected abstract void writeLeafSet(LeafSetNode<?> node) throws IOException;
153
154     protected abstract void writeLeafSetEntry(LeafSetEntryNode<?> node) throws IOException;
155
156     protected abstract void writeMap(MapNode node) throws IOException;
157
158     protected abstract void writeMapEntry(MapEntryNode node) throws IOException;
159
160     protected abstract void writeUnkeyedList(UnkeyedListNode node) throws IOException;
161
162     protected abstract void writeUnkeyedListEntry(UnkeyedListEntryNode node) throws IOException;
163 }