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