080348ef67247ac032f9ce4a81bee84fbd38b8b6
[netconf.git] / restconf / restconf-openapi / src / main / java / org / opendaylight / restconf / openapi / impl / SchemasStream.java
1 /*
2  * Copyright (c) 2023 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.openapi.impl;
9
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;
38
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";
43
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;
50
51     private Reader reader;
52     private ReadableByteChannel channel;
53     private boolean eof;
54
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;
60         this.writer = writer;
61         this.isForSingleModule = isForSingleModule;
62         this.stream = stream;
63         this.generator = generator;
64     }
65
66     @Override
67     public int read() throws IOException {
68         if (eof) {
69             return -1;
70         }
71         if (reader == null) {
72             generator.writeObjectFieldStart("schemas");
73             generator.flush();
74             reader = new BufferedReader(
75                 new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
76             stream.reset();
77         }
78         var read = reader.read();
79         while (read == -1) {
80             if (iterator.hasNext()) {
81                 reader = new BufferedReader(new InputStreamReader(
82                     new SchemaStream(toComponents(iterator.next(), context, isForSingleModule), writer),
83                         StandardCharsets.UTF_8));
84                 read = reader.read();
85                 continue;
86             }
87             generator.writeEndObject();
88             generator.flush();
89             reader = new BufferedReader(
90                 new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
91             stream.reset();
92             eof = true;
93             return reader.read();
94         }
95         return read;
96     }
97
98     @Override
99     public int read(final byte[] array, final int off, final int len) throws IOException {
100         if (eof) {
101             return -1;
102         }
103         if (channel == null) {
104             generator.writeObjectFieldStart("schemas");
105             generator.flush();
106             channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
107             stream.reset();
108         }
109         var read = channel.read(ByteBuffer.wrap(array, off, len));
110         while (read == -1) {
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));
115                 continue;
116             }
117             generator.writeEndObject();
118             generator.flush();
119             channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
120             stream.reset();
121             eof = true;
122             return channel.read(ByteBuffer.wrap(array, off, len));
123         }
124         return read;
125     }
126
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");
135         }
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);
144                 result.add(input);
145                 stack.enterSchemaTree(rpcInput.getQName());
146                 for (final var child : rpcInput.getChildNodes()) {
147                     if (!children.contains(child)) {
148                         children.add(child);
149                         processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName,
150                             false);
151                     }
152                 }
153                 stack.exit();
154             }
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);
159                 result.add(output);
160                 stack.enterSchemaTree(rpcOutput.getQName());
161                 for (final var child : rpcOutput.getChildNodes()) {
162                     if (!children.contains(child)) {
163                         children.add(child);
164                         processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName,
165                             false);
166                     }
167                 }
168                 stack.exit();
169             }
170             stack.exit();
171         }
172
173         for (final var childNode : module.getChildNodes()) {
174             processDataAndActionNodes(childNode, moduleName, stack, definitionNames, result, moduleName,
175                 true);
176         }
177         return result;
178     }
179
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);
190             } else {
191                 discriminator = definitionNames.getDiscriminator(node);
192             }
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;
196             result.add(child);
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);
201             }
202             stack.exit();
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);
212             }
213             stack.exit(); // Exit the CaseSchemaNode context
214             stack.exit(); // Exit the ChoiceSchemaNode context
215         }
216     }
217
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);
227                 result.add(input);
228             }
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);
233                 result.add(output);
234             }
235             stack.exit();
236         }
237     }
238
239     public enum EntityType {
240         NODE,
241         RPC
242     }
243 }