Iterate over models in PathsStream
[netconf.git] / restconf / restconf-openapi / src / main / java / org / opendaylight / restconf / openapi / impl / PathsStream.java
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;