From 653ef0dc73d85c21c5040370c5b1c1ebb96f8c16 Mon Sep 17 00:00:00 2001 From: lubos-cicut Date: Fri, 2 Feb 2024 10:05:54 +0100 Subject: [PATCH] Iterate over models in PathsStream Use originally intended iterative approach to get next chunk of paths (for one model) in PathsStream. JIRA: NETCONF-1241 Change-Id: I1358e94d387f944f72e12fae5421d35b312481f2 Signed-off-by: lubos-cicut --- .../openapi/impl/OpenApiInputStream.java | 4 +- .../restconf/openapi/impl/PathStream.java | 83 ++++++++++ .../restconf/openapi/impl/PathsStream.java | 148 +++++++++++------- .../restconf/openapi/model/OpenApiEntity.java | 3 +- .../restconf/openapi/model/PathsEntity.java | 32 ---- 5 files changed, 180 insertions(+), 90 deletions(-) create mode 100644 restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathStream.java delete mode 100644 restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathsEntity.java diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java index 6adcc7fa6e..6a158907b6 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java @@ -52,8 +52,8 @@ public final class OpenApiInputStream extends InputStream { stack.add(new OpenApiVersionStream(new OpenApiVersionEntity(), writer)); stack.add(new InfoStream(new InfoEntity(title), writer)); stack.add(new ServersStream(new ServersEntity(List.of(new ServerEntity(url))), writer)); - stack.add(new PathsStream(context, writer, deviceName, urlPrefix, isForSingleModule, includeDataStore, modules, - basePath)); + stack.add(new PathsStream(context, writer, deviceName, urlPrefix, isForSingleModule, includeDataStore, + modules.iterator(), basePath, stream, generator)); stack.add(new ComponentsStream(context, writer, generator, stream, modules.iterator(), isForSingleModule)); stack.add(new SecurityStream(writer, new SecurityEntity(security))); } diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathStream.java new file mode 100644 index 0000000000..fa401e957c --- /dev/null +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathStream.java @@ -0,0 +1,83 @@ +/* + * 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 stack; + private final OpenApiBodyWriter writer; + + private Reader reader; + private ReadableByteChannel channel; + + public PathStream(final Deque 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(); + } +} diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsStream.java index 9f5671168b..2a54717c83 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsStream.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsStream.java @@ -10,8 +10,10 @@ package org.opendaylight.restconf.openapi.impl; 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; @@ -22,19 +24,17 @@ import java.nio.channels.ReadableByteChannel; 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; @@ -59,7 +59,7 @@ import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition; public final class PathsStream extends InputStream { - private final Collection modules; + private final Iterator iterator; private final OpenApiBodyWriter writer; private final EffectiveModelContext schemaContext; private final String deviceName; @@ -67,6 +67,8 @@ public final class PathsStream extends InputStream { 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"; @@ -74,11 +76,13 @@ public final class PathsStream extends InputStream { 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 modules, final String basePath) { - this.modules = modules; + final boolean includeDataStore, final Iterator iterator, final String basePath, + final ByteArrayOutputStream stream, final JsonGenerator generator) { + this.iterator = iterator; this.writer = writer; this.schemaContext = schemaContext; this.isForSingleModule = isForSingleModule; @@ -86,74 +90,110 @@ public final class PathsStream extends InputStream { 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 toPaths() { + private Deque toPaths(final Module module) { final var result = new ArrayDeque(); - 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(); - 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(); + 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; diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiEntity.java index fa99d2094f..fd53bcf48c 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiEntity.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiEntity.java @@ -15,8 +15,7 @@ import org.eclipse.jdt.annotation.NonNull; * A response entity for complex generated type. */ public abstract sealed class OpenApiEntity permits ComponentsEntity, InfoEntity, OpenApiVersionEntity, OperationEntity, - PathEntity, PathsEntity, SchemaEntity, SchemasEntity, SecurityEntity, SecuritySchemesEntity, ServerEntity, - ServersEntity { + PathEntity, SchemaEntity, SchemasEntity, SecurityEntity, SecuritySchemesEntity, ServerEntity, ServersEntity { /** * Generate JSON events into specified generator. * diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathsEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathsEntity.java deleted file mode 100644 index 838c57cf72..0000000000 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathsEntity.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.model; - -import static java.util.Objects.requireNonNull; - -import com.fasterxml.jackson.core.JsonGenerator; -import java.io.IOException; -import java.util.Deque; -import org.eclipse.jdt.annotation.NonNull; - -public final class PathsEntity extends OpenApiEntity { - private final @NonNull Deque paths; - - public PathsEntity(final @NonNull Deque paths) { - this.paths = requireNonNull(paths); - } - - @Override - public void generate(final @NonNull JsonGenerator generator) throws IOException { - generator.writeObjectFieldStart("paths"); - for (final var path : paths) { - path.generate(generator); - } - generator.writeEndObject(); - } -} -- 2.36.6