Iterate over models in PathsStream 21/110121/7
authorlubos-cicut <lubos.cicut@pantheon.tech>
Fri, 2 Feb 2024 09:05:54 +0000 (10:05 +0100)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Wed, 7 Feb 2024 12:54:28 +0000 (12:54 +0000)
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 <lubos.cicut@pantheon.tech>
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsStream.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiEntity.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathsEntity.java [deleted file]

index 6adcc7fa6e41f786bcd0fbd8a2a7be7e5b8c2668..6a158907b6781e42b0c2d5f4282b9f6df055a854 100644 (file)
@@ -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 (file)
index 0000000..fa401e9
--- /dev/null
@@ -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<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();
+    }
+}
index 9f5671168bab167961680ad234e677487a1ba39f..2a54717c83f20c5b5bb92334a06afc3814e89199 100644 (file)
@@ -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<? extends Module> modules;
+    private final Iterator<? extends Module> 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<? 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;
@@ -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<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;
index fa99d2094f523fba096ed9f37a68ad2259ef5275..fd53bcf48c2cf25fff236d533949798f70ec5c12 100644 (file)
@@ -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 (file)
index 838c57c..0000000
+++ /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<PathEntity> paths;
-
-    public PathsEntity(final @NonNull Deque<PathEntity> 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();
-    }
-}