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.restconf.openapi.impl;
10 import com.fasterxml.jackson.core.JsonGenerator;
11 import java.io.BufferedReader;
12 import java.io.ByteArrayInputStream;
13 import java.io.ByteArrayOutputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.InputStreamReader;
17 import java.io.Reader;
18 import java.nio.ByteBuffer;
19 import java.nio.channels.Channels;
20 import java.nio.channels.ReadableByteChannel;
21 import java.nio.charset.StandardCharsets;
22 import java.util.ArrayDeque;
23 import java.util.ArrayList;
24 import java.util.Deque;
25 import java.util.Iterator;
26 import java.util.List;
27 import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
28 import org.opendaylight.restconf.openapi.model.SchemaEntity;
29 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
30 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.Module;
37 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
39 public final class SchemasStream extends InputStream {
40 private static final String OBJECT_TYPE = "object";
41 private static final String INPUT_SUFFIX = "_input";
42 private static final String OUTPUT_SUFFIX = "_output";
44 private final Iterator<? extends Module> iterator;
45 private final OpenApiBodyWriter writer;
46 private final EffectiveModelContext context;
47 private final boolean isForSingleModule;
48 private final ByteArrayOutputStream stream;
49 private final JsonGenerator generator;
51 private Reader reader;
52 private ReadableByteChannel channel;
55 public SchemasStream(final EffectiveModelContext context, final OpenApiBodyWriter writer,
56 final Iterator<? extends Module> iterator, final boolean isForSingleModule,
57 final ByteArrayOutputStream stream, final JsonGenerator generator) {
58 this.iterator = iterator;
59 this.context = context;
61 this.isForSingleModule = isForSingleModule;
63 this.generator = generator;
67 public int read() throws IOException {
72 generator.writeObjectFieldStart("schemas");
74 reader = new BufferedReader(
75 new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
78 var read = reader.read();
80 if (iterator.hasNext()) {
81 reader = new BufferedReader(new InputStreamReader(
82 new SchemaStream(toComponents(iterator.next(), context, isForSingleModule), writer),
83 StandardCharsets.UTF_8));
87 generator.writeEndObject();
89 reader = new BufferedReader(
90 new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
99 public int read(final byte[] array, final int off, final int len) throws IOException {
103 if (channel == null) {
104 generator.writeObjectFieldStart("schemas");
106 channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
109 var read = channel.read(ByteBuffer.wrap(array, off, len));
111 if (iterator.hasNext()) {
112 channel = Channels.newChannel(
113 new SchemaStream(toComponents(iterator.next(), context, isForSingleModule), writer));
114 read = channel.read(ByteBuffer.wrap(array, off, len));
117 generator.writeEndObject();
119 channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
122 return channel.read(ByteBuffer.wrap(array, off, len));
127 private static Deque<SchemaEntity> toComponents(final Module module, final EffectiveModelContext context,
128 final boolean isForSingleModule) {
129 final var result = new ArrayDeque<SchemaEntity>();
130 final var definitionNames = new DefinitionNames();
131 final var stack = SchemaInferenceStack.of(context);
132 final var moduleName = module.getName();
133 if (isForSingleModule) {
134 definitionNames.addUnlinkedName(moduleName + "_module");
136 final var children = new ArrayList<DataSchemaNode>();
137 for (final var rpc : module.getRpcs()) {
138 stack.enterSchemaTree(rpc.getQName());
139 final var rpcName = rpc.getQName().getLocalName();
140 final var rpcInput = rpc.getInput();
141 if (!rpcInput.getChildNodes().isEmpty()) {
142 final var input = new SchemaEntity(rpcInput, moduleName + "_" + rpcName + INPUT_SUFFIX, null,
143 OBJECT_TYPE, stack, moduleName, false, definitionNames, EntityType.RPC);
145 stack.enterSchemaTree(rpcInput.getQName());
146 for (final var child : rpcInput.getChildNodes()) {
147 if (!children.contains(child)) {
149 processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName,
155 final var rpcOutput = rpc.getOutput();
156 if (!rpcOutput.getChildNodes().isEmpty()) {
157 final var output = new SchemaEntity(rpcOutput, moduleName + "_" + rpcName + OUTPUT_SUFFIX, null,
158 OBJECT_TYPE, stack, moduleName, false, definitionNames, EntityType.RPC);
160 stack.enterSchemaTree(rpcOutput.getQName());
161 for (final var child : rpcOutput.getChildNodes()) {
162 if (!children.contains(child)) {
164 processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName,
173 for (final var childNode : module.getChildNodes()) {
174 processDataAndActionNodes(childNode, moduleName, stack, definitionNames, result, moduleName,
180 private static void processDataAndActionNodes(final DataSchemaNode node, final String title,
181 final SchemaInferenceStack stack, final DefinitionNames definitionNames,
182 final ArrayDeque<SchemaEntity> result, final String parentName, final boolean isParentConfig) {
183 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
184 final var newTitle = title + "_" + node.getQName().getLocalName();
185 final String discriminator;
186 if (!definitionNames.isListedNode(node)) {
187 final var parentNameConfigLocalName = parentName + "_" + node.getQName().getLocalName();
188 final var names = List.of(parentNameConfigLocalName);
189 discriminator = definitionNames.pickDiscriminator(node, names);
191 discriminator = definitionNames.getDiscriminator(node);
193 final var child = new SchemaEntity(node, newTitle, discriminator, OBJECT_TYPE, stack, parentName,
194 isParentConfig, definitionNames, EntityType.NODE);
195 final var isConfig = node.isConfiguration() && isParentConfig;
197 stack.enterSchemaTree(node.getQName());
198 processActions(node, title, stack, definitionNames, result, parentName);
199 for (final var childNode : ((DataNodeContainer) node).getChildNodes()) {
200 processDataAndActionNodes(childNode, newTitle, stack, definitionNames, result, newTitle, isConfig);
203 } else if (node instanceof ChoiceSchemaNode choiceNode && !choiceNode.getCases().isEmpty()) {
204 // Process default case or first case
205 final var caseNode = choiceNode.getDefaultCase()
206 .orElseGet(() -> choiceNode.getCases().stream().findFirst()
207 .orElseThrow(() -> new IllegalStateException("No cases found in ChoiceSchemaNode")));
208 stack.enterSchemaTree(choiceNode.getQName());
209 stack.enterSchemaTree(caseNode.getQName());
210 for (final var childNode : caseNode.getChildNodes()) {
211 processDataAndActionNodes(childNode, title, stack, definitionNames, result, parentName, isParentConfig);
213 stack.exit(); // Exit the CaseSchemaNode context
214 stack.exit(); // Exit the ChoiceSchemaNode context
218 private static void processActions(final DataSchemaNode node, final String title, final SchemaInferenceStack stack,
219 final DefinitionNames definitionNames, final ArrayDeque<SchemaEntity> result, final String parentName) {
220 for (final var actionDef : ((ActionNodeContainer) node).getActions()) {
221 stack.enterSchemaTree(actionDef.getQName());
222 final var actionName = actionDef.getQName().getLocalName();
223 final var actionInput = actionDef.getInput();
224 if (!actionInput.getChildNodes().isEmpty()) {
225 final var input = new SchemaEntity(actionInput, title + "_" + actionName + INPUT_SUFFIX, null,
226 OBJECT_TYPE, stack, parentName, false, definitionNames, EntityType.RPC);
229 final var actionOutput = actionDef.getOutput();
230 if (!actionOutput.getChildNodes().isEmpty()) {
231 final var output = new SchemaEntity(actionOutput, title + "_" + actionName + OUTPUT_SUFFIX, null,
232 OBJECT_TYPE, stack, parentName, false, definitionNames, EntityType.RPC);
239 public enum EntityType {