022668a3b8a30e3594087a18b1a04ffea2e76311
[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.NodeSchemaEntity;
29 import org.opendaylight.restconf.openapi.model.RpcSchemaEntity;
30 import org.opendaylight.restconf.openapi.model.SchemaEntity;
31 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
32 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
35 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.Module;
39 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
40
41 public final class SchemasStream extends InputStream {
42     private static final String OBJECT_TYPE = "object";
43     private static final String INPUT_SUFFIX = "_input";
44     private static final String OUTPUT_SUFFIX = "_output";
45
46     private final Iterator<? extends Module> iterator;
47     private final OpenApiBodyWriter writer;
48     private final EffectiveModelContext context;
49     private final boolean isForSingleModule;
50     private final ByteArrayOutputStream stream;
51     private final JsonGenerator generator;
52
53     private Reader reader;
54     private ReadableByteChannel channel;
55     private boolean eof;
56
57     public SchemasStream(final EffectiveModelContext context, final OpenApiBodyWriter writer,
58             final Iterator<? extends Module> iterator, final boolean isForSingleModule,
59             final ByteArrayOutputStream stream, final JsonGenerator generator) {
60         this.iterator = iterator;
61         this.context = context;
62         this.writer = writer;
63         this.isForSingleModule = isForSingleModule;
64         this.stream = stream;
65         this.generator = generator;
66     }
67
68     @Override
69     public int read() throws IOException {
70         if (eof) {
71             return -1;
72         }
73         if (reader == null) {
74             generator.writeObjectFieldStart("schemas");
75             generator.flush();
76             reader = new BufferedReader(
77                 new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
78             stream.reset();
79         }
80         var read = reader.read();
81         while (read == -1) {
82             if (iterator.hasNext()) {
83                 reader = new BufferedReader(new InputStreamReader(
84                     new SchemaStream(toComponents(iterator.next(), context, isForSingleModule), writer),
85                         StandardCharsets.UTF_8));
86                 read = reader.read();
87                 continue;
88             }
89             generator.writeEndObject();
90             generator.flush();
91             reader = new BufferedReader(
92                 new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
93             stream.reset();
94             eof = true;
95             return reader.read();
96         }
97         return read;
98     }
99
100     @Override
101     public int read(final byte[] array, final int off, final int len) throws IOException {
102         if (eof) {
103             return -1;
104         }
105         if (channel == null) {
106             generator.writeObjectFieldStart("schemas");
107             generator.flush();
108             channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
109             stream.reset();
110         }
111         var read = channel.read(ByteBuffer.wrap(array, off, len));
112         while (read == -1) {
113             if (iterator.hasNext()) {
114                 channel = Channels.newChannel(
115                     new SchemaStream(toComponents(iterator.next(), context, isForSingleModule), writer));
116                 read = channel.read(ByteBuffer.wrap(array, off, len));
117                 continue;
118             }
119             generator.writeEndObject();
120             generator.flush();
121             channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
122             stream.reset();
123             eof = true;
124             return channel.read(ByteBuffer.wrap(array, off, len));
125         }
126         return read;
127     }
128
129     private static Deque<SchemaEntity> toComponents(final Module module, final EffectiveModelContext context,
130             final boolean isForSingleModule) {
131         final var result = new ArrayDeque<SchemaEntity>();
132         final var definitionNames = new DefinitionNames();
133         final var stack = SchemaInferenceStack.of(context);
134         final var moduleName = module.getName();
135         if (isForSingleModule) {
136             definitionNames.addUnlinkedName(moduleName + "_module");
137         }
138         final var children = new ArrayList<DataSchemaNode>();
139         for (final var rpc : module.getRpcs()) {
140             stack.enterSchemaTree(rpc.getQName());
141             final var rpcName = rpc.getQName().getLocalName();
142             final var rpcInput = rpc.getInput();
143             if (!rpcInput.getChildNodes().isEmpty()) {
144                 final var input = new RpcSchemaEntity(rpcInput, moduleName + "_" + rpcName + INPUT_SUFFIX, null,
145                     OBJECT_TYPE, stack, moduleName, false, definitionNames);
146                 result.add(input);
147                 stack.enterSchemaTree(rpcInput.getQName());
148                 for (final var child : rpcInput.getChildNodes()) {
149                     if (!children.contains(child)) {
150                         children.add(child);
151                         processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName,
152                             false);
153                     }
154                 }
155                 stack.exit();
156             }
157             final var rpcOutput = rpc.getOutput();
158             if (!rpcOutput.getChildNodes().isEmpty()) {
159                 final var output = new RpcSchemaEntity(rpcOutput, moduleName + "_" + rpcName + OUTPUT_SUFFIX, null,
160                     OBJECT_TYPE, stack, moduleName, false, definitionNames);
161                 result.add(output);
162                 stack.enterSchemaTree(rpcOutput.getQName());
163                 for (final var child : rpcOutput.getChildNodes()) {
164                     if (!children.contains(child)) {
165                         children.add(child);
166                         processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName,
167                             false);
168                     }
169                 }
170                 stack.exit();
171             }
172             stack.exit();
173         }
174
175         for (final var childNode : module.getChildNodes()) {
176             processDataAndActionNodes(childNode, moduleName, stack, definitionNames, result, moduleName,
177                 true);
178         }
179         return result;
180     }
181
182     private static void processDataAndActionNodes(final DataSchemaNode node, final String title,
183             final SchemaInferenceStack stack, final DefinitionNames definitionNames,
184             final ArrayDeque<SchemaEntity> result, final String parentName, final boolean isParentConfig) {
185         if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
186             final var newTitle = title + "_" + node.getQName().getLocalName();
187             final String discriminator;
188             if (!definitionNames.isListedNode(node)) {
189                 final var parentNameConfigLocalName = parentName + "_" + node.getQName().getLocalName();
190                 final var names = List.of(parentNameConfigLocalName);
191                 discriminator = definitionNames.pickDiscriminator(node, names);
192             } else {
193                 discriminator = definitionNames.getDiscriminator(node);
194             }
195             final var child = new NodeSchemaEntity(node, newTitle, discriminator, OBJECT_TYPE, stack, parentName,
196                 isParentConfig, definitionNames);
197             final var isConfig = node.isConfiguration() && isParentConfig;
198             result.add(child);
199             stack.enterSchemaTree(node.getQName());
200             processActions(node, title, stack, definitionNames, result, parentName);
201             for (final var childNode : ((DataNodeContainer) node).getChildNodes()) {
202                 processDataAndActionNodes(childNode, newTitle, stack, definitionNames, result, newTitle, isConfig);
203             }
204             stack.exit();
205         } else if (node instanceof ChoiceSchemaNode choiceNode && !choiceNode.getCases().isEmpty()) {
206             // Process default case or first case
207             final var caseNode = choiceNode.getDefaultCase()
208                 .orElseGet(() -> choiceNode.getCases().stream().findFirst()
209                     .orElseThrow(() -> new IllegalStateException("No cases found in ChoiceSchemaNode")));
210             stack.enterSchemaTree(choiceNode.getQName());
211             stack.enterSchemaTree(caseNode.getQName());
212             for (final var childNode : caseNode.getChildNodes()) {
213                 processDataAndActionNodes(childNode, title, stack, definitionNames, result, parentName, isParentConfig);
214             }
215             stack.exit(); // Exit the CaseSchemaNode context
216             stack.exit(); // Exit the ChoiceSchemaNode context
217         }
218     }
219
220     private static void processActions(final DataSchemaNode node, final String title, final SchemaInferenceStack stack,
221             final DefinitionNames definitionNames, final ArrayDeque<SchemaEntity> result, final String parentName) {
222         for (final var actionDef : ((ActionNodeContainer) node).getActions()) {
223             stack.enterSchemaTree(actionDef.getQName());
224             final var actionName = actionDef.getQName().getLocalName();
225             final var actionInput = actionDef.getInput();
226             if (!actionInput.getChildNodes().isEmpty()) {
227                 final var input = new RpcSchemaEntity(actionInput, title + "_" + actionName + INPUT_SUFFIX, null,
228                     OBJECT_TYPE, stack, parentName, false, definitionNames);
229                 result.add(input);
230             }
231             final var actionOutput = actionDef.getOutput();
232             if (!actionOutput.getChildNodes().isEmpty()) {
233                 final var output = new RpcSchemaEntity(actionOutput, title + "_" + actionName + OUTPUT_SUFFIX, null,
234                     OBJECT_TYPE, stack, parentName, false, definitionNames);
235                 result.add(output);
236             }
237             stack.exit();
238         }
239     }
240 }