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