WIP: Draft reading YANG context into OpenApiInputStream 58/108258/16
authorIvan Hrasko <ivan.hrasko@pantheon.tech>
Thu, 24 Aug 2023 15:01:05 +0000 (17:01 +0200)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Fri, 8 Dec 2023 13:52:11 +0000 (13:52 +0000)
We would like to create JSON document according to
OpenApi specification from EffectiveModelContext.

We are going to use OpenApiInputStream to create overall
document structure by deploying nested input streams.

For example we are creating nested input stream for 'paths'
where we are iterating over EffectiveModelContext multiple times
(for each module) to read each module's paths by dedicated input
stream. This way we will never have in memory more paths than paths
for one module.

The same applies for schemas.

Some parts of the document can be done without iterating over
EffectiveModelContext, like 'info', 'servers', etc. These are just
read from their respective entities where we even can use hard-coded
or constant values.

JIRA: NETCONF-938
Change-Id: I75db8424e5559786f402a6ed79d35b2ff8ec647e
Signed-off-by: Ivan Hrasko <ivan.hrasko@pantheon.tech>
22 files changed:
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/InfoStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiInputStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiVersionStream.java [new file with mode: 0644]
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/PathsSteam.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SchemaStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SchemasStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SecuritySchemesStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SecurityStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/ServersStream.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/jaxrs/OpenApiBodyWriter.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ComponentsEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/InfoEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiEntity.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiVersionEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OperationEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathsEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PostEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/SchemaEntity.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ServerEntity.java [new file with mode: 0644]
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ServersEntity.java [new file with mode: 0644]

diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/InfoStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/InfoStream.java
new file mode 100644 (file)
index 0000000..f2e36bd
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.InfoEntity;
+import org.opendaylight.restconf.openapi.model.OpenApiEntity;
+
+public final class InfoStream extends InputStream {
+    private final InfoEntity entity;
+    private final OpenApiBodyWriter writer;
+
+    private Reader reader;
+
+    public InfoStream(final InfoEntity entity, final OpenApiBodyWriter writer) {
+        this.entity = entity;
+        this.writer = writer;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (reader == null) {
+            reader = new InputStreamReader(new ByteArrayInputStream(writeNextEntity(entity)), StandardCharsets.UTF_8);
+        }
+        return reader.read();
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+
+    private byte[] writeNextEntity(final OpenApiEntity next) throws IOException {
+        writer.writeTo(next, null, null, null, null, null, null);
+        return writer.readFrom();
+    }
+}
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
new file mode 100644 (file)
index 0000000..018a9b1
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonFactoryBuilder;
+import com.fasterxml.jackson.core.JsonGenerator;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.Info;
+import org.opendaylight.restconf.openapi.model.InfoEntity;
+import org.opendaylight.restconf.openapi.model.OpenApiVersionEntity;
+import org.opendaylight.restconf.openapi.model.Server;
+import org.opendaylight.restconf.openapi.model.ServerEntity;
+import org.opendaylight.restconf.openapi.model.ServersEntity;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+
+public final class OpenApiInputStream extends InputStream {
+    private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+    private final JsonGenerator generator = new JsonFactoryBuilder().build().createGenerator(stream);
+    private final Deque<InputStream> stack = new ArrayDeque<>();
+
+    private Reader reader;
+
+    private boolean eof;
+
+    public OpenApiInputStream(final EffectiveModelContext context, final String openApiVersion, final Info info,
+            final List<Server> servers, final List<Map<String, List<String>>> security) throws IOException {
+        final OpenApiBodyWriter writer = new OpenApiBodyWriter(generator, stream);
+        stack.add(new OpenApiVersionStream(new OpenApiVersionEntity(), writer));
+        stack.add(new InfoStream(new InfoEntity(info.version(), info.title(), info.description()), writer));
+        stack.add(new ServersStream(new ServersEntity(
+            List.of(new ServerEntity(servers.iterator().next().url()))), writer));
+        stack.add(new PathsSteam(context, writer, generator, stream));
+        stack.add(new SchemasStream(context, writer, generator, stream));
+        stack.add(new SecurityStream(writer));
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (eof) {
+            return -1;
+        }
+        if (reader == null) {
+            generator.writeStartObject();
+            generator.flush();
+            reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
+            stream.reset();
+        }
+
+        var read = reader.read();
+        while (read == -1) {
+            if (stack.isEmpty()) {
+                generator.writeEndObject();
+                generator.flush();
+                reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
+                stream.reset();
+                eof = true;
+                return reader.read();
+            }
+            reader = new InputStreamReader(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 {
+        return super.read(array, off, len);
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiVersionStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiVersionStream.java
new file mode 100644 (file)
index 0000000..a37c9f2
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.OpenApiEntity;
+import org.opendaylight.restconf.openapi.model.OpenApiVersionEntity;
+
+public final class OpenApiVersionStream extends InputStream {
+    private final OpenApiVersionEntity entity;
+    private final OpenApiBodyWriter writer;
+
+    private Reader reader;
+
+    public OpenApiVersionStream(final OpenApiVersionEntity entity, final OpenApiBodyWriter writer) {
+        this.entity = entity;
+        this.writer = writer;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (reader == null) {
+            reader = new InputStreamReader(new ByteArrayInputStream(writeNextEntity(entity)), StandardCharsets.UTF_8);
+        }
+        return reader.read();
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+
+    private byte[] writeNextEntity(final OpenApiEntity next) throws IOException {
+        writer.writeTo(next, null, null, null, null, null, null);
+        return writer.readFrom();
+    }
+}
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..2baddf3
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+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;
+
+    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 InputStreamReader(new ByteArrayInputStream(writeNextEntity(stack.pop())),
+                StandardCharsets.UTF_8);
+        }
+
+        var read = reader.read();
+        while (read == -1) {
+            if (stack.isEmpty()) {
+                return -1;
+            }
+            reader = 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 {
+        return super.read(array, off, len);
+    }
+
+    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/PathsSteam.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/PathsSteam.java
new file mode 100644 (file)
index 0000000..6c31d2c
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.PathEntity;
+import org.opendaylight.restconf.openapi.model.PostEntity;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+public final class PathsSteam extends InputStream {
+    private final Iterator<? extends Module> iterator;
+    private final JsonGenerator generator;
+    private final OpenApiBodyWriter writer;
+    private final ByteArrayOutputStream stream;
+
+    private Reader reader;
+    private boolean eof;
+
+    public PathsSteam(final EffectiveModelContext context, final OpenApiBodyWriter writer,
+            final JsonGenerator generator, final ByteArrayOutputStream stream) {
+        iterator = context.getModules().iterator();
+        this.generator = generator;
+        this.writer = writer;
+        this.stream = stream;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (eof) {
+            return -1;
+        }
+        if (reader == null) {
+            generator.writeObjectFieldStart("paths");
+            generator.flush();
+            reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
+            stream.reset();
+        }
+
+        var read = reader.read();
+        while (read == -1) {
+            if (iterator.hasNext()) {
+                reader = new InputStreamReader(new PathStream(toPaths(iterator.next()), writer),
+                    StandardCharsets.UTF_8);
+                read = reader.read();
+                continue;
+            }
+            generator.writeEndObject();
+            generator.flush();
+            reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
+            stream.reset();
+            eof = true;
+            return reader.read();
+        }
+
+        return read;
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+
+    private static Deque<PathEntity> toPaths(final Module module) {
+        final var result = new ArrayDeque<PathEntity>();
+        // RPC operations (via post) - RPCs have their own path
+        for (final var rpc : module.getRpcs()) {
+            // TODO connect path with payload
+            final var post = new PostEntity(rpc, "controller", module.getName());
+            final String resolvedPath = "rests/operations/" + "/" + module.getName() + ":"
+                + rpc.getQName().getLocalName();
+            final var entity = new PathEntity(resolvedPath, post);
+            result.add(entity);
+        }
+
+        /**
+         * TODO
+         * for (final var container : module.getChildNodes()) {
+         *             // get
+         *             // post
+         *             // put
+         *             // patch
+         *             // delete
+         *
+         *             // add path into deque
+         *         }
+         */
+        return result;
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SchemaStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SchemaStream.java
new file mode 100644 (file)
index 0000000..de53e0c
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+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.SchemaEntity;
+
+public final class SchemaStream extends InputStream {
+    private final Deque<SchemaEntity> stack;
+    private final OpenApiBodyWriter writer;
+
+    private Reader reader;
+
+    public SchemaStream(final Deque<SchemaEntity> schemas, final OpenApiBodyWriter writer) {
+        this.stack = schemas;
+        this.writer = writer;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (reader == null) {
+            if (stack.isEmpty()) {
+                return -1;
+            }
+            reader = new InputStreamReader(new ByteArrayInputStream(writeNextEntity(stack.pop())),
+                StandardCharsets.UTF_8);
+        }
+
+        var read = reader.read();
+        while (read == -1) {
+            if (stack.isEmpty()) {
+                return -1;
+            }
+            reader = 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 {
+        return super.read(array, off, len);
+    }
+
+    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/SchemasStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SchemasStream.java
new file mode 100644 (file)
index 0000000..630beba
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.SchemaEntity;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+public final class SchemasStream extends InputStream {
+    private final Iterator<? extends Module> iterator;
+    private final OpenApiBodyWriter writer;
+    private final JsonGenerator generator;
+    private final ByteArrayOutputStream stream;
+
+    private Reader reader;
+    private boolean schemesWritten;
+    private boolean eof;
+    private boolean eos;
+
+    public SchemasStream(final EffectiveModelContext context, final OpenApiBodyWriter writer,
+            final JsonGenerator generator, final ByteArrayOutputStream stream) {
+        iterator = context.getModules().iterator();
+        this.writer = writer;
+        this.generator = generator;
+        this.stream = stream;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (reader == null) {
+            generator.writeObjectFieldStart("components");
+            generator.writeObjectFieldStart("schemas");
+            generator.flush();
+            reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
+            stream.reset();
+        }
+        if (eof) {
+            return -1;
+        }
+        if (eos) {
+            eof = true;
+            return reader.read();
+        }
+
+        var read = reader.read();
+        while (read == -1) {
+            if (iterator.hasNext()) {
+                reader = new InputStreamReader(new SchemaStream(toComponents(iterator.next()), writer),
+                    StandardCharsets.UTF_8);
+                read = reader.read();
+                continue;
+            }
+            if (!schemesWritten) {
+                reader = new InputStreamReader(new SecuritySchemesStream(writer), StandardCharsets.UTF_8);
+                read = reader.read();
+                schemesWritten = true;
+                continue;
+            }
+            generator.writeEndObject();
+            generator.writeEndObject();
+            generator.flush();
+            reader = new InputStreamReader(new ByteArrayInputStream(stream.toByteArray()), StandardCharsets.UTF_8);
+            stream.reset();
+            eos = true;
+            return reader.read();
+        }
+
+        return read;
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+
+    private static Deque<SchemaEntity> toComponents(final Module module) {
+        final var result = new ArrayDeque<SchemaEntity>();
+        for (final var rpc : module.getRpcs()) {
+            final var moduleName = module.getName();
+            final var rpcName = rpc.getQName().getLocalName();
+            final var input = new SchemaEntity(rpc.getInput(), moduleName + "_" + rpcName + "_input", "object");
+            result.add(input);
+            final var output = new SchemaEntity(rpc.getOutput(), moduleName + "_" + rpcName + "_output", "object");
+            result.add(output);
+        }
+
+        // actions
+        // child nodes
+        // etc.
+        return result;
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SecuritySchemesStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SecuritySchemesStream.java
new file mode 100644 (file)
index 0000000..e259020
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+
+public final class SecuritySchemesStream extends InputStream {
+    public SecuritySchemesStream(final OpenApiBodyWriter writer) {
+    }
+
+    @Override
+    public int read() throws IOException {
+        return -1;
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SecurityStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/SecurityStream.java
new file mode 100644 (file)
index 0000000..9e019f6
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+
+public final class SecurityStream extends InputStream {
+    public SecurityStream(final OpenApiBodyWriter writer) {
+    }
+
+    @Override
+    public int read() throws IOException {
+        return -1;
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/ServersStream.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/ServersStream.java
new file mode 100644 (file)
index 0000000..672195a
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import org.opendaylight.restconf.openapi.jaxrs.OpenApiBodyWriter;
+import org.opendaylight.restconf.openapi.model.OpenApiEntity;
+import org.opendaylight.restconf.openapi.model.ServersEntity;
+
+public final class ServersStream extends InputStream {
+    private ServersEntity entity;
+    private OpenApiBodyWriter writer;
+
+    private Reader reader;
+
+    public ServersStream(final ServersEntity entity, final OpenApiBodyWriter writer) {
+        this.entity = entity;
+        this.writer = writer;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (reader == null) {
+            reader = new InputStreamReader(new ByteArrayInputStream(writeNextEntity(entity)), StandardCharsets.UTF_8);
+        }
+        return reader.read();
+    }
+
+    @Override
+    public int read(final byte[] array, final int off, final int len) throws IOException {
+        return super.read(array, off, len);
+    }
+
+    private byte[] writeNextEntity(final OpenApiEntity next) throws IOException {
+        writer.writeTo(next, null, null, null, null, null, null);
+        return writer.readFrom();
+    }
+}
index 93eb6faeecc723a670e2617bf01fd38389978bd2..f8e6657568e923f201b445790ed8bd67bdee8f37 100644 (file)
@@ -7,8 +7,8 @@
  */
 package org.opendaylight.restconf.openapi.jaxrs;
 
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonFactoryBuilder;
+import com.fasterxml.jackson.core.JsonGenerator;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.lang.annotation.Annotation;
@@ -26,7 +26,13 @@ import org.opendaylight.restconf.openapi.model.OpenApiEntity;
 @Provider
 @Produces(MediaType.APPLICATION_JSON)
 public final class OpenApiBodyWriter implements MessageBodyWriter<OpenApiEntity> {
-    private final JsonFactory factory = new JsonFactoryBuilder().build();
+    private final JsonGenerator generator;
+    private final ByteArrayOutputStream stream;
+
+    public OpenApiBodyWriter(final JsonGenerator generator, final ByteArrayOutputStream stream) {
+        this.generator = generator;
+        this.stream = stream;
+    }
 
     @Override
     public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
@@ -34,12 +40,18 @@ public final class OpenApiBodyWriter implements MessageBodyWriter<OpenApiEntity>
         return OpenApiEntity.class.isAssignableFrom(type);
     }
 
+    // TODO IMO we can get rid of implementing MessageBodyWriter
     @Override
     public void writeTo(final OpenApiEntity entity, final Class<?> type, final Type genericType,
             final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
             final OutputStream entityStream) throws IOException {
-        try (var generator = factory.createGenerator(entityStream)) {
-            entity.generate(generator);
-        }
+        entity.generate(generator);
+        generator.flush();
+    }
+
+    public byte[] readFrom() {
+        final var bytes = stream.toByteArray();
+        stream.reset();
+        return bytes;
     }
 }
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ComponentsEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ComponentsEntity.java
new file mode 100644 (file)
index 0000000..fc7b5d6
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+
+/**
+ * TODO use this class to simplify SchemasStream.
+ */
+public final class ComponentsEntity extends OpenApiEntity {
+
+    @Override
+    public void generate(final JsonGenerator generator) throws IOException {
+        generator.writeObjectFieldStart("components");
+        generator.writeEndObject();
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/InfoEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/InfoEntity.java
new file mode 100644 (file)
index 0000000..f1ad49a
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+
+public final class InfoEntity extends OpenApiEntity {
+    private final String version;
+    private final String title;
+    private final String description;
+
+    public InfoEntity(final String version, final String title, final String description) {
+        this.version = version;
+        this.title = title;
+        this.description = description;
+    }
+
+    @Override
+    public void generate(@NonNull JsonGenerator generator) throws IOException {
+        generator.writeObjectFieldStart("info");
+        if (version != null) {
+            generator.writeStringField("version", version);
+        }
+        if (title != null) {
+            generator.writeStringField("title", title);
+        }
+        if (description != null) {
+            generator.writeStringField("description", description);
+        }
+        generator.writeEndObject();
+    }
+}
index c4050b0f38d2ee841173a22d74cca0bfd11da47e..c43f362811ab85b6e65b550757b432aad60bf3ff 100644 (file)
@@ -14,7 +14,8 @@ import org.eclipse.jdt.annotation.NonNull;
 /**
  * A response entity for complex generated type.
  */
-public abstract sealed class OpenApiEntity permits SchemaEntity {
+public abstract sealed class OpenApiEntity permits SchemaEntity, ComponentsEntity, InfoEntity, OpenApiVersionEntity,
+        OperationEntity, PathEntity, PathsEntity, ServerEntity, ServersEntity {
     /**
      * Generate JSON events into specified generator.
      *
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiVersionEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiVersionEntity.java
new file mode 100644 (file)
index 0000000..4e0389c
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+
+public final class OpenApiVersionEntity extends OpenApiEntity {
+    @Override
+    public void generate(@NonNull JsonGenerator generator) throws IOException {
+        generator.writeStringField("openapi", "3.0.3");
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OperationEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OperationEntity.java
new file mode 100644 (file)
index 0000000..9eba585
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
+
+/**
+ * Archetype for an Operation.
+ */
+public abstract sealed class OperationEntity extends OpenApiEntity permits PostEntity {
+    private final OperationDefinition schema;
+    private final String deviceName;
+    private final String moduleName;
+
+    protected OperationDefinition schema() {
+        return schema;
+    }
+
+    protected String deviceName() {
+        return deviceName;
+    }
+
+    protected String moduleName() {
+        return moduleName;
+    }
+
+    public OperationEntity(final OperationDefinition schema, final String deviceName, final String moduleName) {
+        this.schema = schema;
+        this.deviceName = deviceName;
+        this.moduleName = moduleName;
+    }
+
+    @Override
+    public void generate(@NonNull JsonGenerator generator) throws IOException {
+        generator.writeObjectFieldStart(operation());
+        final var deprecated = deprecated();
+        if (deprecated != null) {
+            generator.writeBooleanField("deprecated", deprecated);
+        }
+        final var description = description();
+        if (description != null) {
+            generator.writeStringField("description", description);
+        }
+        final var summary = summary();
+        if (summary != null) {
+            generator.writeStringField("summary", summary);
+        }
+        generator.writeEndObject();
+    }
+
+    protected abstract String operation();
+
+    @Nullable Boolean deprecated() {
+        return Boolean.FALSE;
+    }
+
+    @Nullable String description() {
+        return null;
+    }
+
+    @Nullable abstract String summary();
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PathEntity.java
new file mode 100644 (file)
index 0000000..2b01747
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+import java.util.Objects;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+
+public final class PathEntity extends OpenApiEntity {
+    private final @NonNull String path;
+    private final @Nullable OperationEntity post;
+
+    public PathEntity(final String path, final OperationEntity post) {
+        this.path = Objects.requireNonNull(path);
+        this.post = post;
+    }
+
+    @Override
+    public void generate(@NonNull JsonGenerator generator) throws IOException {
+        generator.writeObjectFieldStart(path);
+        final var ref = ref();
+        if (ref != null) {
+            generator.writeStringField("$ref", ref);
+        }
+        final var summary = summary();
+        if (summary != null) {
+            generator.writeStringField("summary", summary);
+        }
+        final var description = description();
+        if (ref != null) {
+            generator.writeStringField("description", description);
+        }
+        final var postOperation = post();
+        if (postOperation != null) {
+            postOperation.generate(generator);
+        }
+        final var putOperation = put();
+        if (putOperation != null) {
+            putOperation.generate(generator);
+        }
+        final var patchOperation = patch();
+        if (patchOperation != null) {
+            patchOperation.generate(generator);
+        }
+        generator.writeEndObject();
+    }
+
+    @Nullable String ref() {
+        return null;
+    }
+
+    @Nullable String summary() {
+        return null;
+    }
+
+    @Nullable String description() {
+        return null;
+    }
+
+    @Nullable OperationEntity post() {
+        return post;
+    }
+
+    @Nullable OperationEntity put() {
+        return null;
+    }
+
+    @Nullable OperationEntity patch() {
+        return null;
+    }
+}
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
new file mode 100644 (file)
index 0000000..e5f296a
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+
+/**
+ * TODO use this class to simplify PathsStream.
+ */
+public final class PathsEntity extends OpenApiEntity {
+
+    @Override
+    public void generate(final JsonGenerator generator) throws IOException {
+        generator.writeObjectFieldStart("paths");
+        generator.writeEndObject();
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PostEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PostEntity.java
new file mode 100644 (file)
index 0000000..7ef36a5
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 javax.ws.rs.HttpMethod;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
+
+public final class PostEntity extends OperationEntity {
+    private static final String SUMMARY_TEMPLATE = "%s - %s - %s - %s";
+    private static final String INPUT_SUFFIX = "_input";
+
+    public PostEntity(final OperationDefinition schema, final String deviceName, final String moduleName) {
+        super(schema, deviceName, moduleName);
+    }
+
+    protected String operation() {
+        return "post";
+    }
+
+    @Nullable String summary() {
+        final var operationName = schema().getQName().getLocalName() + INPUT_SUFFIX;
+        return SUMMARY_TEMPLATE.formatted(HttpMethod.POST, deviceName(), moduleName(), operationName);
+    }
+}
index 6a155dc7da23904f9a35f7ce3f72feccc0a43fff..f51ada632b5a378dc9f24d582c18009a11c45fc8 100644 (file)
@@ -13,15 +13,18 @@ import com.fasterxml.jackson.core.JsonGenerator;
 import java.io.IOException;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 
 /**
  * Archetype for a Schema.
  */
 public final class SchemaEntity extends OpenApiEntity {
+    private final @NonNull SchemaNode value;
     private final @NonNull String title;
     private final @NonNull String type;
 
-    public SchemaEntity(final @NonNull String title, final @NonNull String type) {
+    public SchemaEntity(final @NonNull SchemaNode value, final @NonNull String title, @NonNull final String type) {
+        this.value = requireNonNull(value);
         this.title = requireNonNull(title);
         this.type = requireNonNull(type);
     }
@@ -58,7 +61,7 @@ public final class SchemaEntity extends OpenApiEntity {
     }
 
     private @Nullable String description() {
-        return null;
+        return value.getDescription().orElse(null);
     }
 
     private @Nullable String reference() {
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ServerEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ServerEntity.java
new file mode 100644 (file)
index 0000000..eb6b195
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
+
+public final class ServerEntity extends OpenApiEntity {
+    private final String url;
+
+    public ServerEntity(final String url) {
+        this.url = url;
+    }
+
+    @Override
+    public void generate(@NonNull final JsonGenerator generator) throws IOException {
+        if (url != null) {
+            generator.writeStartObject();
+            generator.writeStringField("url", url);
+            generator.writeEndObject();
+        }
+    }
+}
diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ServersEntity.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/ServersEntity.java
new file mode 100644 (file)
index 0000000..c050dd3
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonGenerator;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+
+public final class ServersEntity extends OpenApiEntity {
+    private final List<ServerEntity> servers;
+
+    public ServersEntity(final List<ServerEntity> servers) {
+        this.servers = servers;
+    }
+
+    @Override
+    public void generate(@NonNull final JsonGenerator generator) throws IOException {
+        generator.writeArrayFieldStart("servers");
+        for (final var server : servers) {
+            server.generate(generator);
+        }
+        generator.writeEndArray();
+    }
+}