--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech s.r.o and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.openapi.impl;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.Deque;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.OpenApiEntity;
+import org.opendaylight.restconf.openapi.model.PathEntity;
+
+public final class PathStream extends InputStream {
+ private final Deque<PathEntity> stack;
+ private final OpenApiBodyWriter writer;
+
+ private Reader reader;
+ private ReadableByteChannel channel;
+
+ public PathStream(final Deque<PathEntity> paths, final OpenApiBodyWriter writer) {
+ this.stack = paths;
+ this.writer = writer;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (reader == null) {
+ if (stack.isEmpty()) {
+ return -1;
+ }
+ reader = new BufferedReader(
+ new InputStreamReader(new ByteArrayInputStream(writeNextEntity(stack.pop())), StandardCharsets.UTF_8));
+ }
+
+ var read = reader.read();
+ while (read == -1) {
+ if (stack.isEmpty()) {
+ return -1;
+ }
+ reader = new BufferedReader(
+ new InputStreamReader(new ByteArrayInputStream(writeNextEntity(stack.pop())), StandardCharsets.UTF_8));
+ read = reader.read();
+ }
+
+ return read;
+ }
+
+ @Override
+ public int read(final byte[] array, final int off, final int len) throws IOException {
+ if (channel == null) {
+ if (stack.isEmpty()) {
+ return -1;
+ }
+ channel = Channels.newChannel(new ByteArrayInputStream(writeNextEntity(stack.pop())));
+ }
+ var read = channel.read(ByteBuffer.wrap(array, off, len));
+ while (read == -1) {
+ if (stack.isEmpty()) {
+ return -1;
+ }
+ channel = Channels.newChannel(new ByteArrayInputStream(writeNextEntity(stack.pop())));
+ read = channel.read(ByteBuffer.wrap(array, off, len));
+ }
+ return read;
+ }
+
+ private byte[] writeNextEntity(final OpenApiEntity entity) throws IOException {
+ writer.writeTo(entity, null, null, null, null, null, null);
+ return writer.readFrom();
+ }
+}
import static org.opendaylight.restconf.openapi.util.RestDocgenUtil.resolveFullNameFromNode;
import static org.opendaylight.restconf.openapi.util.RestDocgenUtil.resolvePathArgumentsName;
+import com.fasterxml.jackson.core.JsonGenerator;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Deque;
+import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
import org.opendaylight.restconf.openapi.model.DeleteEntity;
import org.opendaylight.restconf.openapi.model.GetEntity;
-import org.opendaylight.restconf.openapi.model.OpenApiEntity;
import org.opendaylight.restconf.openapi.model.ParameterEntity;
import org.opendaylight.restconf.openapi.model.ParameterSchemaEntity;
import org.opendaylight.restconf.openapi.model.PatchEntity;
import org.opendaylight.restconf.openapi.model.PathEntity;
-import org.opendaylight.restconf.openapi.model.PathsEntity;
import org.opendaylight.restconf.openapi.model.PostEntity;
import org.opendaylight.restconf.openapi.model.PutEntity;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
public final class PathsStream extends InputStream {
- private final Collection<? extends Module> modules;
+ private final Iterator<? extends Module> iterator;
private final OpenApiBodyWriter writer;
private final EffectiveModelContext schemaContext;
private final String deviceName;
private final String basePath;
private final boolean isForSingleModule;
private final boolean includeDataStore;
+ private final ByteArrayOutputStream stream;
+ private final JsonGenerator generator;
private static final String OPERATIONS = "operations";
private static final String DATA = "data";
private boolean hasAddedDataStore;
private Reader reader;
private ReadableByteChannel channel;
+ private boolean eof;
public PathsStream(final EffectiveModelContext schemaContext, final OpenApiBodyWriter writer,
final String deviceName, final String urlPrefix, final boolean isForSingleModule,
- final boolean includeDataStore, final Collection<? extends Module> modules, final String basePath) {
- this.modules = modules;
+ final boolean includeDataStore, final Iterator<? extends Module> iterator, final String basePath,
+ final ByteArrayOutputStream stream, final JsonGenerator generator) {
+ this.iterator = iterator;
this.writer = writer;
this.schemaContext = schemaContext;
this.isForSingleModule = isForSingleModule;
this.urlPrefix = urlPrefix;
this.includeDataStore = includeDataStore;
this.basePath = basePath;
+ this.stream = stream;
+ this.generator = generator;
hasRootPostLink = false;
hasAddedDataStore = false;
}
@Override
public int read() throws IOException {
+ if (eof) {
+ return -1;
+ }
if (reader == null) {
+ generator.writeObjectFieldStart("paths");
+ generator.flush();
+ reader = new BufferedReader(
+ new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
+ stream.reset();
+ }
+ var read = reader.read();
+ while (read == -1) {
+ if (iterator.hasNext()) {
+ reader = new BufferedReader(
+ new InputStreamReader(new PathStream(toPaths(iterator.next()), writer), StandardCharsets.UTF_8));
+ read = reader.read();
+ continue;
+ }
+ generator.writeEndObject();
+ generator.flush();
reader = new BufferedReader(
- new InputStreamReader(new ByteArrayInputStream(writeNextEntity(new PathsEntity(toPaths()))),
- StandardCharsets.UTF_8));
+ new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8));
+ stream.reset();
+ eof = true;
+ return reader.read();
}
- return reader.read();
+ return read;
}
@Override
public int read(final byte[] array, final int off, final int len) throws IOException {
+ if (eof) {
+ return -1;
+ }
if (channel == null) {
- channel = Channels.newChannel(new ByteArrayInputStream(writeNextEntity(new PathsEntity(toPaths()))));
+ generator.writeObjectFieldStart("paths");
+ generator.flush();
+ channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
+ stream.reset();
}
- return channel.read(ByteBuffer.wrap(array, off, len));
- }
-
- private byte[] writeNextEntity(final OpenApiEntity next) throws IOException {
- writer.writeTo(next, null, null, null, null, null, null);
- return writer.readFrom();
+ var read = channel.read(ByteBuffer.wrap(array, off, len));
+ while (read == -1) {
+ if (iterator.hasNext()) {
+ channel = Channels.newChannel(new PathStream(toPaths(iterator.next()), writer));
+ read = channel.read(ByteBuffer.wrap(array, off, len));
+ continue;
+ }
+ generator.writeEndObject();
+ generator.flush();
+ channel = Channels.newChannel(new ByteArrayInputStream(stream.toByteArray()));
+ stream.reset();
+ eof = true;
+ return channel.read(ByteBuffer.wrap(array, off, len));
+ }
+ return read;
}
- private Deque<PathEntity> toPaths() {
+ private Deque<PathEntity> toPaths(final Module module) {
final var result = new ArrayDeque<PathEntity>();
- for (final var module : modules) {
- if (includeDataStore && !hasAddedDataStore) {
- final var dataPath = basePath + DATA + urlPrefix;
- result.add(new PathEntity(dataPath, null, null, null,
- new GetEntity(null, deviceName, "data", null, null, false),
- null));
- final var operationsPath = basePath + OPERATIONS + urlPrefix;
- result.add(new PathEntity(operationsPath, null, null, null,
- new GetEntity(null, deviceName, "operations", null, null, false),
- null));
- hasAddedDataStore = true;
- }
- // RPC operations (via post) - RPCs have their own path
- for (final var rpc : module.getRpcs()) {
- final var localName = rpc.getQName().getLocalName();
- final var post = new PostEntity(rpc, deviceName, module.getName(), new ArrayList<>(), localName, null);
- final var resolvedPath = basePath + OPERATIONS + urlPrefix + "/" + module.getName() + ":" + localName;
- final var entity = new PathEntity(resolvedPath, post, null, null, null, null);
- result.add(entity);
- }
- for (final var node : module.getChildNodes()) {
- final var moduleName = module.getName();
- final boolean isConfig = node.isConfiguration();
- final var nodeLocalName = node.getQName().getLocalName();
+ if (includeDataStore && !hasAddedDataStore) {
+ final var dataPath = basePath + DATA + urlPrefix;
+ result.add(new PathEntity(dataPath, null, null, null,
+ new GetEntity(null, deviceName, "data", null, null, false),
+ null));
+ final var operationsPath = basePath + OPERATIONS + urlPrefix;
+ result.add(new PathEntity(operationsPath, null, null, null,
+ new GetEntity(null, deviceName, "operations", null, null, false),
+ null));
+ hasAddedDataStore = true;
+ }
+ // RPC operations (via post) - RPCs have their own path
+ for (final var rpc : module.getRpcs()) {
+ final var localName = rpc.getQName().getLocalName();
+ final var post = new PostEntity(rpc, deviceName, module.getName(), new ArrayList<>(), localName, null);
+ final var resolvedPath = basePath + OPERATIONS + urlPrefix + "/" + module.getName() + ":" + localName;
+ final var entity = new PathEntity(resolvedPath, post, null, null, null, null);
+ result.add(entity);
+ }
+ for (final var node : module.getChildNodes()) {
+ final var moduleName = module.getName();
+ final boolean isConfig = node.isConfiguration();
+ final var nodeLocalName = node.getQName().getLocalName();
- if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
- if (isConfig && !hasRootPostLink && isForSingleModule) {
- final var resolvedPath = basePath + DATA + urlPrefix;
- result.add(new PathEntity(resolvedPath, new PostEntity(node, deviceName, moduleName,
- new ArrayList<>(), nodeLocalName, module), null, null, null, null));
- hasRootPostLink = true;
- }
- //process first node
- final var pathParams = new ArrayList<ParameterEntity>();
- final var localName = moduleName + ":" + nodeLocalName;
- final var path = urlPrefix + "/" + processPath(node, pathParams, localName);
- processChildNode(node, pathParams, moduleName, result, path, nodeLocalName, isConfig, schemaContext,
- deviceName, basePath, null);
+ if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
+ if (isConfig && !hasRootPostLink && isForSingleModule) {
+ final var resolvedPath = basePath + DATA + urlPrefix;
+ result.add(new PathEntity(resolvedPath, new PostEntity(node, deviceName, moduleName,
+ new ArrayList<>(), nodeLocalName, module), null, null, null, null));
+ hasRootPostLink = true;
}
+ //process first node
+ final var pathParams = new ArrayList<ParameterEntity>();
+ final var localName = moduleName + ":" + nodeLocalName;
+ final var path = urlPrefix + "/" + processPath(node, pathParams, localName);
+ processChildNode(node, pathParams, moduleName, result, path, nodeLocalName, isConfig, schemaContext,
+ deviceName, basePath, null);
}
}
return result;