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.ByteArrayInputStream;
12 import java.io.ByteArrayOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.InputStreamReader;
16 import java.io.Reader;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayDeque;
19 import java.util.ArrayList;
20 import java.util.Deque;
21 import java.util.Iterator;
22 import java.util.List;
24 import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
25 import org.opendaylight.restconf.openapi.model.SchemaEntity;
26 import org.opendaylight.restconf.openapi.model.security.Http;
27 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
28 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
31 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
37 public final class SchemasStream extends InputStream {
38 private static final String BASIC_AUTH_NAME = "basicAuth";
39 private static final Http OPEN_API_BASIC_AUTH = new Http("basic", null, null);
41 private static final String OBJECT_TYPE = "object";
42 private static final String INPUT_SUFFIX = "_input";
43 private static final String OUTPUT_SUFFIX = "_output";
45 private final Iterator<? extends Module> iterator;
46 private final OpenApiBodyWriter writer;
47 private final EffectiveModelContext context;
48 private final JsonGenerator generator;
49 private final ByteArrayOutputStream stream;
51 private Reader reader;
52 private boolean schemesWritten;
56 public SchemasStream(final EffectiveModelContext context, final OpenApiBodyWriter writer,
57 final JsonGenerator generator, final ByteArrayOutputStream stream,
58 final Iterator<? extends Module> iterator) {
59 this.iterator = iterator;
60 this.context = context;
62 this.generator = generator;
67 public int read() throws IOException {
69 generator.writeObjectFieldStart("components");
70 generator.writeObjectFieldStart("schemas");
72 reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
83 var read = reader.read();
85 if (iterator.hasNext()) {
86 reader = new InputStreamReader(new SchemaStream(toComponents(iterator.next()), writer),
87 StandardCharsets.UTF_8);
91 if (!schemesWritten) {
92 generator.writeEndObject();
93 reader = new InputStreamReader(new SecuritySchemesStream(writer, Map.of(BASIC_AUTH_NAME,
94 OPEN_API_BASIC_AUTH)), StandardCharsets.UTF_8);
96 schemesWritten = true;
99 generator.writeEndObject();
101 reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
104 return reader.read();
111 public int read(final byte[] array, final int off, final int len) throws IOException {
112 return super.read(array, off, len);
115 private Deque<SchemaEntity> toComponents(final Module module) {
116 final var result = new ArrayDeque<SchemaEntity>();
117 final var definitionNames = new DefinitionNames();
118 final var stack = SchemaInferenceStack.of(context);
119 final var moduleName = module.getName();
120 final var children = new ArrayList<DataSchemaNode>();
121 for (final var rpc : module.getRpcs()) {
122 stack.enterSchemaTree(rpc.getQName());
123 final var rpcName = rpc.getQName().getLocalName();
124 final var rpcInput = rpc.getInput();
125 if (!rpcInput.getChildNodes().isEmpty()) {
126 final var input = new SchemaEntity(rpcInput, moduleName + "_" + rpcName + INPUT_SUFFIX, null,
127 OBJECT_TYPE, stack, moduleName, false, definitionNames, EntityType.RPC);
129 stack.enterSchemaTree(rpcInput.getQName());
130 for (final var child : rpcInput.getChildNodes()) {
131 if (!children.contains(child)) {
133 processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName, false);
138 final var rpcOutput = rpc.getOutput();
139 if (!rpcOutput.getChildNodes().isEmpty()) {
140 final var output = new SchemaEntity(rpcOutput, moduleName + "_" + rpcName + OUTPUT_SUFFIX, null,
141 OBJECT_TYPE, stack, moduleName, false, definitionNames, EntityType.RPC);
143 stack.enterSchemaTree(rpcOutput.getQName());
144 for (final var child : rpcOutput.getChildNodes()) {
145 if (!children.contains(child)) {
147 processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName, false);
155 for (final var childNode : module.getChildNodes()) {
156 processDataAndActionNodes(childNode, moduleName, stack, definitionNames, result, moduleName,
162 private static void processDataAndActionNodes(final DataSchemaNode node, final String title,
163 final SchemaInferenceStack stack, final DefinitionNames definitionNames,
164 final ArrayDeque<SchemaEntity> result, final String parentName, final boolean isParentConfig) {
165 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
166 final var newTitle = title + "_" + node.getQName().getLocalName();
167 final String discriminator;
168 if (!definitionNames.isListedNode(node)) {
169 final var parentNameConfigLocalName = parentName + "_" + node.getQName().getLocalName();
170 final var names = List.of(parentNameConfigLocalName);
171 discriminator = definitionNames.pickDiscriminator(node, names);
173 discriminator = definitionNames.getDiscriminator(node);
175 final var child = new SchemaEntity(node, newTitle, discriminator, OBJECT_TYPE, stack, parentName,
176 isParentConfig, definitionNames, EntityType.NODE);
177 final var isConfig = node.isConfiguration() && isParentConfig;
179 stack.enterSchemaTree(node.getQName());
180 processActions(node, title, stack, definitionNames, result, parentName);
181 for (final var childNode : ((DataNodeContainer) node).getChildNodes()) {
182 processDataAndActionNodes(childNode, newTitle, stack, definitionNames, result, newTitle, isConfig);
185 } else if (node instanceof ChoiceSchemaNode choiceNode) {
186 // Process default case or first case
187 final var caseNode = choiceNode.getDefaultCase()
188 .orElseGet(() -> choiceNode.getCases().stream().findFirst()
189 .orElseThrow(() -> new IllegalStateException("No cases found in ChoiceSchemaNode")));
190 stack.enterSchemaTree(choiceNode.getQName());
191 stack.enterSchemaTree(caseNode.getQName());
192 for (final var childNode : caseNode.getChildNodes()) {
193 processDataAndActionNodes(childNode, title, stack, definitionNames, result, parentName, isParentConfig);
195 stack.exit(); // Exit the CaseSchemaNode context
196 stack.exit(); // Exit the ChoiceSchemaNode context
200 private static void processActions(final DataSchemaNode node, final String title, final SchemaInferenceStack stack,
201 final DefinitionNames definitionNames, final ArrayDeque<SchemaEntity> result, final String parentName) {
202 for (final var actionDef : ((ActionNodeContainer) node).getActions()) {
203 stack.enterSchemaTree(actionDef.getQName());
204 final var actionName = actionDef.getQName().getLocalName();
205 final var actionInput = actionDef.getInput();
206 if (!actionInput.getChildNodes().isEmpty()) {
207 final var input = new SchemaEntity(actionInput, title + "_" + actionName + INPUT_SUFFIX, null,
208 OBJECT_TYPE, stack, parentName, false, definitionNames, EntityType.RPC);
211 final var actionOutput = actionDef.getOutput();
212 if (!actionOutput.getChildNodes().isEmpty()) {
213 final var output = new SchemaEntity(actionOutput, title + "_" + actionName + OUTPUT_SUFFIX, null,
214 OBJECT_TYPE, stack, parentName, false, definitionNames, EntityType.RPC);
221 public enum EntityType {