Junos device: Fix IllegalStateException
[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.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;
23 import java.util.Map;
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;
36
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);
40
41     private static final String OBJECT_TYPE = "object";
42     private static final String INPUT_SUFFIX = "_input";
43     private static final String OUTPUT_SUFFIX = "_output";
44
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;
50
51     private Reader reader;
52     private boolean schemesWritten;
53     private boolean eof;
54     private boolean eos;
55
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;
61         this.writer = writer;
62         this.generator = generator;
63         this.stream = stream;
64     }
65
66     @Override
67     public int read() throws IOException {
68         if (reader == null) {
69             generator.writeObjectFieldStart("components");
70             generator.writeObjectFieldStart("schemas");
71             generator.flush();
72             reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
73             stream.reset();
74         }
75         if (eof) {
76             return -1;
77         }
78         if (eos) {
79             eof = true;
80             return reader.read();
81         }
82
83         var read = reader.read();
84         while (read == -1) {
85             if (iterator.hasNext()) {
86                 reader = new InputStreamReader(new SchemaStream(toComponents(iterator.next()), writer),
87                     StandardCharsets.UTF_8);
88                 read = reader.read();
89                 continue;
90             }
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);
95                 read = reader.read();
96                 schemesWritten = true;
97                 continue;
98             }
99             generator.writeEndObject();
100             generator.flush();
101             reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
102             stream.reset();
103             eos = true;
104             return reader.read();
105         }
106
107         return read;
108     }
109
110     @Override
111     public int read(final byte[] array, final int off, final int len) throws IOException {
112         return super.read(array, off, len);
113     }
114
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);
128                 result.add(input);
129                 stack.enterSchemaTree(rpcInput.getQName());
130                 for (final var child : rpcInput.getChildNodes()) {
131                     if (!children.contains(child)) {
132                         children.add(child);
133                         processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName, false);
134                     }
135                 }
136                 stack.exit();
137             }
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);
142                 result.add(output);
143                 stack.enterSchemaTree(rpcOutput.getQName());
144                 for (final var child : rpcOutput.getChildNodes()) {
145                     if (!children.contains(child)) {
146                         children.add(child);
147                         processDataAndActionNodes(child, moduleName, stack, definitionNames, result, moduleName, false);
148                     }
149                 }
150                 stack.exit();
151             }
152             stack.exit();
153         }
154
155         for (final var childNode : module.getChildNodes()) {
156             processDataAndActionNodes(childNode, moduleName, stack, definitionNames, result, moduleName,
157                 true);
158         }
159         return result;
160     }
161
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);
172             } else {
173                 discriminator = definitionNames.getDiscriminator(node);
174             }
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;
178             result.add(child);
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);
183             }
184             stack.exit();
185         } else if (node instanceof ChoiceSchemaNode choiceNode && !choiceNode.getCases().isEmpty()) {
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);
194             }
195             stack.exit(); // Exit the CaseSchemaNode context
196             stack.exit(); // Exit the ChoiceSchemaNode context
197         }
198     }
199
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);
209                 result.add(input);
210             }
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);
215                 result.add(output);
216             }
217             stack.exit();
218         }
219     }
220
221     public enum EntityType {
222         NODE,
223         RPC
224     }
225 }