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.util.codec;
10 import static java.util.Objects.requireNonNull;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.util.ArrayList;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.yangtools.yang.common.QName;
17 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
18 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
20 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
21 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
22 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
23 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
24 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
25 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.stream.InputStreamNormalizer;
27 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizationException;
28 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizationResult;
29 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
30 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
31 import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.DataTreeEffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
34 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
37 * An {@link AbstractCodecFactory} which additionally provides services defined in {@link InputStreamNormalizer}.
40 * This class existsonly because both JSON and XML implementations of {@link InputStreamNormalizer} are naturally hosted
41 * in their respective {@link AbstractCodecFactory} implementations and therefore it is a convenient place to share
42 * common implementation bits.
44 public abstract class AbstractInputStreamNormalizer<T extends TypeAwareCodec<?, ?, ?>>
45 extends AbstractCodecFactory<T> implements InputStreamNormalizer {
46 protected AbstractInputStreamNormalizer(final @NonNull EffectiveModelContext schemaContext,
47 final @NonNull CodecCache<T> cache) {
48 super(schemaContext, cache);
52 public final NormalizationResult<ContainerNode> parseDatastore(final NodeIdentifier containerName,
53 final Unqualified moduleName, final InputStream stream) throws NormalizationException {
55 return parseDatastore(requireNonNull(stream), requireNonNull(containerName), requireNonNull(moduleName));
56 } catch (IOException | IllegalArgumentException e) {
57 throw NormalizationException.ofCause(e);
61 protected abstract @NonNull NormalizationResult<ContainerNode> parseDatastore(@NonNull InputStream stream,
62 @NonNull NodeIdentifier containerName, @NonNull Unqualified moduleName)
63 throws IOException, NormalizationException;
66 public final NormalizationResult<?> parseData(final EffectiveStatementInference inference, final InputStream stream)
67 throws NormalizationException {
68 final var stack = checkInferenceNotEmpty(inference);
69 final var stmt = stack.currentStatement();
70 if (!(stmt instanceof DataTreeEffectiveStatement<?> dataStmt)) {
71 throw new IllegalArgumentException("Invalid inference statement " + stmt);
74 final NormalizationResult<?> data;
76 data = parseData(stack, requireNonNull(stream));
77 } catch (IOException | IllegalArgumentException e) {
78 throw NormalizationException.ofCause(e);
80 return checkNodeName(data, dataStmt.argument());
83 protected abstract @NonNull NormalizationResult<?> parseData(@NonNull SchemaInferenceStack stack,
84 @NonNull InputStream stream) throws IOException, NormalizationException;
87 public final PrefixAndResult parseChildData(final EffectiveStatementInference inference, final InputStream stream)
88 throws NormalizationException {
89 checkInference(inference);
91 final NormalizationResult<?> normalized;
93 normalized = parseChildData(requireNonNull(stream), inference);
94 } catch (IOException | IllegalArgumentException e) {
95 throw NormalizationException.ofCause(e);
98 final var prefix = new ArrayList<@NonNull PathArgument>();
99 var data = normalized.data();
100 var metadata = normalized.metadata();
101 var mountPoints = normalized.mountPoints();
103 // Deal with the semantic differences of what "child" means in NormalizedNode versus in YANG data tree
106 // NormalizedNode structure has 'choice' statements visible and addressable, whereas YANG data tree makes
107 // them completely transparent.
109 // Therefore we need to peel any ChoiceNode from the result and shift them to the prefix. Since each choice was
110 // created implicitly to contain the element mentioned in the stream.
111 while (data instanceof ChoiceNode choice) {
112 prefix.add(choice.name());
113 data = choice.body().iterator().next();
116 // NormalizedNode structure has 'list' and 'leaf-list' statements visible and addressable, whereas YANG data
117 // tree addressing can only point to individual instances. RFC8040 section 4.4.1 states:
119 // The message-body is expected to contain the
120 // content of a child resource to create within the parent (target
121 // resource). The message-body MUST contain exactly one instance of the
122 // expected data resource. The data model for the child tree is the
123 // subtree, as defined by YANG for the child resource.
125 // Therefore we need to peel any UnkeyedListNode, MapNode and LeafSetNodes from the top-level and shift them
126 // to the prefix. Note that from the parser perspective, each such node can legally contain zero, one or more
127 // entries, but this method is restricted to allowing only a single entry.
128 if (data instanceof MapNode || data instanceof LeafSetNode || data instanceof UnkeyedListNode) {
129 final var dataName = data.name();
130 final var body = ((NormalizedNodeContainer<?>) data).body();
131 final var size = body.size();
132 if (body.size() != 1) {
133 throw NormalizationException.ofMessage(
134 "Exactly one instance of " + dataName.getNodeType() + " is required, " + size + " supplied");
138 prefix.add(dataName);
139 data = body.iterator().next();
140 if (metadata != null) {
141 metadata = metadata.getChildren().get(dataName);
143 if (mountPoints != null) {
144 mountPoints = mountPoints.getChildren().get(dataName);
148 return new PrefixAndResult(prefix, new NormalizationResult<>(data, metadata, mountPoints));
151 protected abstract @NonNull NormalizationResult<?> parseChildData(@NonNull InputStream stream,
152 @NonNull EffectiveStatementInference inference) throws IOException, NormalizationException;
155 public final NormalizationResult<ContainerNode> parseInput(final EffectiveStatementInference inference,
156 final InputStream stream) throws NormalizationException {
157 final var stack = checkInferenceNotEmpty(inference);
158 final var stmt = stack.currentStatement();
159 final QName expected;
160 if (stmt instanceof RpcEffectiveStatement rpc) {
161 expected = rpc.input().argument();
162 } else if (stmt instanceof ActionEffectiveStatement action) {
163 expected = action.input().argument();
165 throw new IllegalArgumentException("Invalid inference statement " + stmt);
167 return parseInputOutput(stream, stack, expected);
171 public final NormalizationResult<ContainerNode> parseOutput(final EffectiveStatementInference inference,
172 final InputStream stream) throws NormalizationException {
173 final var stack = checkInferenceNotEmpty(inference);
174 final var stmt = stack.currentStatement();
175 final QName expected;
176 if (stmt instanceof RpcEffectiveStatement rpc) {
177 expected = rpc.output().argument();
178 } else if (stmt instanceof ActionEffectiveStatement action) {
179 expected = action.output().argument();
181 throw new IllegalArgumentException("Invalid inference statement " + stmt);
183 return parseInputOutput(stream, stack, expected);
186 private @NonNull NormalizationResult<ContainerNode> parseInputOutput(final @NonNull InputStream stream,
187 final @NonNull SchemaInferenceStack stack, final @NonNull QName expected) throws NormalizationException {
188 final NormalizationResult<?> data;
190 data = parseInputOutput(stack, expected, requireNonNull(stream));
191 } catch (IOException | IllegalArgumentException e) {
192 throw NormalizationException.ofCause(e);
194 return checkNodeContainer(data);
197 protected abstract @NonNull NormalizationResult<?> parseInputOutput(@NonNull SchemaInferenceStack stack,
198 @NonNull QName expected, @NonNull InputStream stream) throws IOException, NormalizationException;
200 private void checkInference(final EffectiveStatementInference inference) {
201 final var modelContext = inference.getEffectiveModelContext();
202 final var local = getEffectiveModelContext();
203 if (!local.equals(modelContext)) {
204 throw new IllegalArgumentException("Mismatched inference, expecting model context " + local);
208 private @NonNull SchemaInferenceStack checkInferenceNotEmpty(final EffectiveStatementInference inference) {
209 checkInference(inference);
210 final var stack = SchemaInferenceStack.ofInference(inference);
211 if (stack.isEmpty()) {
212 throw new IllegalArgumentException("Inference must not be empty");
217 @SuppressWarnings("unchecked")
218 protected static final @NonNull NormalizationResult<ContainerNode> checkNodeContainer(
219 final NormalizationResult<?> result) throws NormalizationException {
220 final var data = result.data();
221 if (data instanceof ContainerNode) {
222 return (NormalizationResult<ContainerNode>) result;
224 throw NormalizationException.ofMessage("Unexpected payload type " + data.contract());
227 protected static final @NonNull NormalizationResult<?> checkNodeName(final NormalizationResult<?> result,
228 final QName expected) throws NormalizationException {
229 final var qname = result.data().name().getNodeType();
230 if (qname.equals(expected)) {
233 throw NormalizationException.ofMessage(
234 "Payload name " + qname + " is different from identifier name " + expected);