2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.yangtools.yang.data.api.schema.stream;
10 import static java.util.Objects.requireNonNull;
12 import java.io.InputStream;
13 import java.util.List;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
15 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
16 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
17 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
18 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
19 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
20 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
21 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
24 * Interface to parsing of {@link InputStream}s containing YANG-modeled data. While the design of this interface is
25 * guided by what a typical implementation of a <a href="https://www.rfc-editor.org/rfc/rfc8040">RESTCONF</a> server or
26 * client might require, and it is not limited solely to that use case and should be used wherever its methods provide
27 * the required semantics.
30 * The core assumption is that the user knows the general context in which a particular document, provided as an
31 * {@link InputStream}, needs to be interpreted.
34 * In RESTCONF that context is provided by the HTTP request method and the HTTP request URI. On the server side these
35 * expect to be differentiated between requests to
37 * <li>invoke an {@code rpc} or an {@code action}, catered to by
38 * {@link #parseInput(EffectiveStatementInference, InputStream)}</li>
39 * <li>replace the contents of a particular data store, catered to by
40 * {@link #parseDatastore(QName, Unqualified, InputStream)}<li>
41 * <li>create, replace or otherwise modify a directly identified data store resource, catered to by
42 * {@link #parseData(EffectiveStatementInference, InputStream)}</li>
43 * <li>create an indirectly identified data store resource, catered to by
44 * {@link #parseChildData(EffectiveStatementInference, InputStream)}</li>
46 * On the client side, these are similarly differentiated between responses to
48 * <li>invoke an {@code rpc} or an {@code action}, catered to by
49 * {@link #parseOutput(EffectiveStatementInference, InputStream)}</li>
50 * <li>replace the contents of a particular data store, catered to by
51 * {@link #parseDatastore(QName, Unqualified, InputStream)}<li>
52 * <li>create, replace or otherwise modify a directly identified data store resource, catered to by
53 * {@link #parseData(EffectiveStatementInference, InputStream)}</li>
57 public interface InputStreamNormalizer {
61 * This interface uses EffectiveStatementInference in places where YangInstanceIdentifier might be convenient. This
62 * is on purpose, as we want to provide an interface between standards-based yang-model-api and provide enough rope
63 * for integration with YangInstanceIdentifier, but not require users to necessarily use it.
65 * The reason for that is that an empty YangInstanceIdentifier is not really a YANG construct, but rather something
66 * yang-data-tree-api (mis)uses.
68 * Futhermore we do not want to force users to provide a YangInstanceIdentifier for efficiency reasons. In the case
69 * of RESTCONF, which is guiding the design here, the caller would acquire a YangInstanceIdentifier through parsing
70 * the request URL. That means the caller was dealing with yang-model-api and therefore have likely seen
71 * a SchemaInferenceStack corresponding to that identifier and can take a snapshot in the form or an
72 * EffectiveStatementInference. This has the added benefit of keeping semantics clear: we expect inferences to be
73 * the result of YANG-defined processing without introducing the additional friction of having to deal with the
74 * differences in data tree addressing. Again, we provide enough rope to do bridge that gap easily if the user needs
77 * Another case for not exposing YangInstanceIdentifier-based methods is that implementations of this interface are
78 * expected to be bound to an EffectiveModelContext, but we do not want to expose that via this this interface
79 * extending EffectiveModelContextProvider -- at the end of the day implementations may provide the required
80 * functionality through hard-coding against some concrete set of of YANG models.
82 * PrefixAndData is using an explicit List<PathArgument> instead of a relative YangInstanceIdentifier in order to
83 * make a clear distinction of use: the prefix is meant to be interpreted and must not be confused with something
84 * that can, for example, be stored as a 'type instance-identifier' value or a DataTreeSnapshot.readNode() argument.
86 * Similar reasoning goes for the use of EffectiveStatementInference: it is a generalised concept, which could be
87 * to reduce the number of methods in this interface, each method places explicit requirements on what an acceptable
88 * EffectiveStatementInference argument looks like. This is done on purpose, so that we bind to explicit semantics
89 * of that particular method, e.g. being explicit about semantics of a method rather than overloading methods with
90 * multiple semantic modes.
94 * A DTO capturing the result of
95 * {@link InputStreamNormalizer#parseChildData(EffectiveStatementInference, InputStream)}.
97 * @param prefix {@link YangInstanceIdentifier} steps that need to be concatenated to the request path to form
98 * a {@link YangInstanceIdentifier} pointing to the immediate parent of {@link #result}.
99 * @param result a {@link NormalizationResult}
101 record PrefixAndResult(List<PathArgument> prefix, NormalizationResult<?> result) {
103 * Default constructor.
105 * @param prefix {@link YangInstanceIdentifier} steps that need to be concatenated to the request path to form
106 * a {@link YangInstanceIdentifier} pointing to the immediate parent of {@link #result}.
107 * @param result parsed data
109 public PrefixAndResult {
110 prefix = List.copyOf(prefix);
111 requireNonNull(result);
116 * Parse the contents of an {@link InputStream} as the contents of a data store.
119 * This method's signature is a bit counter-intuitive. {@code rootNamespace} and {@code rootName} collectively
120 * encode the expected root element, which may not be expressed in the underlying YANG data model.
123 * The reason for this is that YANG does not define an explicit {@link NodeIdentifier} of the datastore root
124 * resource, but protocol encodings require this conceptual root to be encapsulated in protocol documents and the
125 * approaches taken differ from protocol to protocol. NETCONF operates in terms of YANG-modeled RPC operations,
126 * where this conceptual root is given an anchor -- {@code get-config} output's {@code anyxml data}. RESTCONF
127 * operates in terms of HTTP payloads and while it models such an anchor, it is rather unnatural
128 * {@code container data} with description defining its magic properties and it is not feasible for YANG parser
129 * to help us with that.
132 * Therefore this method takes the name of the root element in two arguments, which together define its value in
133 * both JSON-based (module + localName} and XML-based (namespace + localName) encodings. Implementations of this
134 * method are expected to use this information and treat the root element outside of their usual YANG-informed
138 * For example, XML parsers will pick {@code containerName.getNodeType().getNamespace()} to match the root element's
139 * namespace and {@code containerName.getNodeType().getLocalName()} to match the element's local name. JSON parsers,
140 * on the other hand, will use {@code moduleName} and {@code rootName.getLocalName()} to match the top-level JSON
141 * object's sole named member.
143 * @param containerName expected root container name
144 * @param moduleName module name corresponding to {@code containerName}
145 * @param stream the {@link InputStream} to parse
146 * @return parsed {@link ContainerNode} corresponding to the data store root, with its {@link ContainerNode#name()}
147 * equal to {@code containerName}.
148 * @throws NullPointerException if any argument is {@code null}
149 * @throws NormalizationException if an error occurs
151 NormalizationResult<ContainerNode> parseDatastore(NodeIdentifier containerName, Unqualified moduleName,
152 InputStream stream) throws NormalizationException;
155 * Parse the contents of an {@link InputStream} as a data resource.
157 * @param inference pointer to the data resource
158 * @param stream the {@link InputStream} to parse
159 * @return Parsed {@link NormalizedNode} corresponding the requested resource
160 * @throws NullPointerException if any argument is {@code null}
161 * @throws IllegalArgumentException if {@code inference} does not to point to a resource recognized by this parser
162 * @throws NormalizationException if an error occurs
164 NormalizationResult<?> parseData(EffectiveStatementInference inference, InputStream stream)
165 throws NormalizationException;
168 * Parse the contents of an {@link InputStream} as a child data resource.
170 * @param parentInference pointer to the parent of the data resource
171 * @param stream the {@link InputStream} to parse
172 * @return A {@link PrefixAndResult} containing parsed resource data and any {@link YangInstanceIdentifier} steps
173 * that need to be appended between {@code inference} and the parsed {@link NormalizedNode}
174 * @throws NullPointerException if any argument is {@code null}
175 * @throws IllegalArgumentException if {@code inference} does not to point to a resource recognized by this parser
176 * @throws NormalizationException if an error occurs
178 PrefixAndResult parseChildData(EffectiveStatementInference parentInference, InputStream stream)
179 throws NormalizationException;
182 * Parse the contents of an {@link InputStream} as an operation {@code input}.
184 * @param operationInference pointer to the operation
185 * @param stream the {@link InputStream} to parse
186 * @return Parsed {@link ContainerNode} corresponding to the operation input
187 * @throws NullPointerException if any argument is {@code null}
188 * @throws IllegalArgumentException if {@code inference} does not to point to an operation recognized by this parser
189 * @throws NormalizationException if an error occurs
191 NormalizationResult<ContainerNode> parseInput(EffectiveStatementInference operationInference, InputStream stream)
192 throws NormalizationException;
195 * Parse the contents of an {@link InputStream} as on operation {@code output}.
197 * @param operationInference pointer to the operation
198 * @param stream the {@link InputStream} to parse
199 * @return Parsed {@link ContainerNode} corresponding to the operation output
200 * @throws NullPointerException if any argument is {@code null}
201 * @throws IllegalArgumentException if {@code inference} does not to point to an operation recognized by this parser
202 * @throws NormalizationException if an error occurs
204 NormalizationResult<ContainerNode> parseOutput(EffectiveStatementInference operationInference, InputStream stream)
205 throws NormalizationException;