From b85da3f350f0e282f04d2a8a66f7c07d8dadc3c1 Mon Sep 17 00:00:00 2001 From: Ivan Hrasko Date: Tue, 10 Oct 2023 15:53:55 +0200 Subject: [PATCH] Adapt API to OpenApiObject removal Adapted REST API to use new logic. Adapted tests for OpenApiInputStream. JIRA: NETCONF-938 Change-Id: Idc6a35bb29f77c37e7ac70e873fd6b720fc5584d Signed-off-by: Ivan Hrasko Signed-off-by: lubos-cicut Signed-off-by: Yaroslav Lastivka --- .../restconf/openapi/api/OpenApiService.java | 9 +- .../impl/BaseYangOpenApiGenerator.java | 96 +++------------- .../openapi/impl/OpenApiInputStream.java | 19 ++-- .../openapi/impl/OpenApiServiceImpl.java | 33 +++--- .../restconf/openapi/impl/PathsStream.java | 5 +- .../restconf/openapi/impl/SchemasStream.java | 5 +- .../openapi/jaxrs/JaxbContextResolver.java | 26 +++++ .../restconf/openapi/model/InfoEntity.java | 20 ++-- .../restconf/openapi/model/OpenApiObject.java | 23 ---- .../restconf/openapi/model/security/Http.java | 2 - .../model/security/SecuritySchemeObject.java | 5 - .../mountpoints/MountPointOpenApi.java | 106 ++++-------------- .../openapi/impl/AbstractDocumentTest.java | 13 ++- 13 files changed, 115 insertions(+), 247 deletions(-) create mode 100644 restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/jaxrs/JaxbContextResolver.java delete mode 100644 restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiObject.java diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/api/OpenApiService.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/api/OpenApiService.java index e6d2237e7e..7a243e049d 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/api/OpenApiService.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/api/OpenApiService.java @@ -7,6 +7,7 @@ */ package org.opendaylight.restconf.openapi.api; +import java.io.IOException; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -28,7 +29,7 @@ public interface OpenApiService { @GET @Path("/single") @Produces(MediaType.APPLICATION_JSON) - Response getAllModulesDoc(@Context UriInfo uriInfo); + Response getAllModulesDoc(@Context UriInfo uriInfo) throws IOException; /** * Generates Swagger compliant document listing APIs for module. @@ -37,7 +38,7 @@ public interface OpenApiService { @Path("/{module}({revision})") @Produces(MediaType.APPLICATION_JSON) Response getDocByModule(@PathParam("module") String module, @PathParam("revision") String revision, - @Context UriInfo uriInfo); + @Context UriInfo uriInfo) throws IOException; /** * Redirects to embedded swagger ui. @@ -65,7 +66,7 @@ public interface OpenApiService { @Produces(MediaType.APPLICATION_JSON) Response getMountDocByModule(@PathParam("instance") String instanceNum, @PathParam("module") String module, @PathParam("revision") String revision, - @Context UriInfo uriInfo); + @Context UriInfo uriInfo) throws IOException; /** * Generates Swagger compliant document listing APIs for all modules of mount point. @@ -73,5 +74,5 @@ public interface OpenApiService { @GET @Path("/mounts/{instance}") @Produces(MediaType.APPLICATION_JSON) - Response getMountDoc(@PathParam("instance") String instanceNum, @Context UriInfo uriInfo); + Response getMountDoc(@PathParam("instance") String instanceNum, @Context UriInfo uriInfo) throws IOException; } diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/BaseYangOpenApiGenerator.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/BaseYangOpenApiGenerator.java index 2f6648b474..9661103fa1 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/BaseYangOpenApiGenerator.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/BaseYangOpenApiGenerator.java @@ -18,14 +18,12 @@ import static org.opendaylight.restconf.openapi.util.RestDocgenUtil.resolveFullN import static org.opendaylight.restconf.openapi.util.RestDocgenUtil.resolvePathArgumentsName; import com.google.common.base.Preconditions; -import com.google.common.collect.Range; import java.io.IOException; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -36,15 +34,10 @@ import java.util.stream.Collectors; import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.dom.api.DOMSchemaService; -import org.opendaylight.restconf.openapi.model.Components; -import org.opendaylight.restconf.openapi.model.Info; -import org.opendaylight.restconf.openapi.model.OpenApiObject; import org.opendaylight.restconf.openapi.model.Operation; import org.opendaylight.restconf.openapi.model.Parameter; import org.opendaylight.restconf.openapi.model.Path; import org.opendaylight.restconf.openapi.model.Schema; -import org.opendaylight.restconf.openapi.model.Server; -import org.opendaylight.restconf.openapi.model.security.Http; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.Revision; import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; @@ -74,20 +67,9 @@ public abstract class BaseYangOpenApiGenerator { private static final Logger LOG = LoggerFactory.getLogger(BaseYangOpenApiGenerator.class); private static final String CONTROLLER_RESOURCE_NAME = "Controller"; - - public static final String API_VERSION = "1.0.0"; - public static final String OPEN_API_VERSION = "3.0.3"; public static final String BASE_PATH = "/"; public static final String MODULE_NAME_SUFFIX = "_module"; - public static final String BASIC_AUTH_NAME = "basicAuth"; - public static final Http OPEN_API_BASIC_AUTH = new Http("basic", null, null); - public static final List>> SECURITY = List.of(Map.of(BASIC_AUTH_NAME, List.of())); - public static final String DESCRIPTION = """ - We are providing full API for configurational data which can be edited (by POST, PUT, PATCH and DELETE). - For operational data we only provide GET API.\n - For majority of request you can see only config data in examples. That is because we can show only one example - per request. The exception when you can see operational data in example is when data are representing - operational (config false) container with no config data in it."""; + public static final List>> SECURITY = List.of(Map.of("basicAuth", List.of())); private final DOMSchemaService schemaService; @@ -95,62 +77,27 @@ public abstract class BaseYangOpenApiGenerator { this.schemaService = requireNonNull(schemaService); } - public OpenApiObject getControllerModulesDoc(final UriInfo uriInfo, final DefinitionNames definitionNames) { + public OpenApiInputStream getControllerModulesDoc(final UriInfo uriInfo) throws IOException { final var context = requireNonNull(schemaService.getGlobalContext()); final var schema = createSchemaFromUriInfo(uriInfo); final var host = createHostFromUriInfo(uriInfo); final var title = "Controller modules of RESTCONF"; - final var info = new Info(API_VERSION, title, DESCRIPTION); - final var servers = List.of(new Server(schema + "://" + host + BASE_PATH)); - - final var paths = new HashMap(); - final var schemas = new HashMap(); - for (final var module : getSortedModules(context)) { - LOG.debug("Working on [{},{}]...", module.getName(), module.getQNameModule().getRevision().orElse(null)); - schemas.putAll(getSchemas(module, context, definitionNames, false)); - paths.putAll(getPaths(module, "", CONTROLLER_RESOURCE_NAME, context, definitionNames, false)); - } - - final var components = new Components(schemas, Map.of(BASIC_AUTH_NAME, OPEN_API_BASIC_AUTH)); - return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY); + final var url = schema + "://" + host + BASE_PATH; + final var modules = context.getModules(); + return new OpenApiInputStream(context, title, url, SECURITY, CONTROLLER_RESOURCE_NAME, "",false, false, + modules); } - public static Set filterByRange(final SortedSet modules, final Range range) { - if (range.equals(Range.all())) { - return modules; - } - final int begin = range.lowerEndpoint(); - final int end = range.upperEndpoint(); - - Module firstModule = null; - - final Iterator iterator = modules.iterator(); - int counter = 0; - while (iterator.hasNext() && counter < end) { - final Module module = iterator.next(); - if (containsListOrContainer(module.getChildNodes()) || !module.getRpcs().isEmpty()) { - if (counter == begin) { - firstModule = module; - } - counter++; - } - } - - if (iterator.hasNext()) { - return modules.subSet(firstModule, iterator.next()); - } else { - return modules.tailSet(firstModule); - } - } - - public OpenApiObject getApiDeclaration(final String module, final String revision, final UriInfo uriInfo) { + public OpenApiInputStream getApiDeclaration(final String module, final String revision, final UriInfo uriInfo) + throws IOException { final EffectiveModelContext schemaContext = schemaService.getGlobalContext(); Preconditions.checkState(schemaContext != null); return getApiDeclaration(module, revision, uriInfo, schemaContext, "", CONTROLLER_RESOURCE_NAME); } - public OpenApiObject getApiDeclaration(final String moduleName, final String revision, final UriInfo uriInfo, - final EffectiveModelContext schemaContext, final String context, final @NonNull String deviceName) { + public OpenApiInputStream getApiDeclaration(final String moduleName, final String revision, final UriInfo uriInfo, + final EffectiveModelContext schemaContext, final String urlPrefix, final @NonNull String deviceName) + throws IOException { final Optional rev; try { @@ -165,13 +112,11 @@ public abstract class BaseYangOpenApiGenerator { final var schema = createSchemaFromUriInfo(uriInfo); final var host = createHostFromUriInfo(uriInfo); - final var info = new Info(API_VERSION, module.getName(), DESCRIPTION); - final var servers = List.of(new Server(schema + "://" + host + BASE_PATH)); - final var definitionNames = new DefinitionNames(); - final var schemas = getSchemas(module, schemaContext, definitionNames, true); - final var components = new Components(schemas, Map.of(BASIC_AUTH_NAME, OPEN_API_BASIC_AUTH)); - final var paths = getPaths(module, context, deviceName, schemaContext, definitionNames, true); - return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY); + final var title = module.getName(); + final var url = schema + "://" + host + BASE_PATH; + final var modules = List.of(module); + return new OpenApiInputStream(schemaContext, title, url, SECURITY, deviceName, urlPrefix, true, false, + modules); } public String createHostFromUriInfo(final UriInfo uriInfo) { @@ -300,15 +245,6 @@ public abstract class BaseYangOpenApiGenerator { } } - private static boolean containsListOrContainer(final Iterable nodes) { - for (final DataSchemaNode child : nodes) { - if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) { - return true; - } - } - return false; - } - private static Path operations(final DataSchemaNode node, final String moduleName, final String deviceName, final List pathParams, final boolean isConfig, final String parentName, final DefinitionNames definitionNames, final String fullName) { 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 209f259ac6..7a7f391980 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 @@ -17,18 +17,18 @@ import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; +import java.util.Collection; 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.SecurityEntity; -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; +import org.opendaylight.yangtools.yang.model.api.Module; public final class OpenApiInputStream extends InputStream { private final ByteArrayOutputStream stream = new ByteArrayOutputStream(); @@ -39,18 +39,17 @@ public final class OpenApiInputStream extends InputStream { private boolean eof; - public OpenApiInputStream(final EffectiveModelContext context, final String openApiVersion, final Info info, - final List servers, final List>> security, final String deviceName, - final String urlPrefix, final boolean isForSingleModule, final boolean includeDataStore) + public OpenApiInputStream(final EffectiveModelContext context, final String title, final String url, + final List>> security, final String deviceName, final String urlPrefix, + final boolean isForSingleModule, final boolean includeDataStore, final Collection modules) 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 InfoStream(new InfoEntity(title), writer)); + stack.add(new ServersStream(new ServersEntity(List.of(new ServerEntity(url))), writer)); stack.add(new PathsStream(context, writer, generator, stream, deviceName, urlPrefix, isForSingleModule, - includeDataStore)); - stack.add(new SchemasStream(context, writer, generator, stream)); + includeDataStore, modules.iterator())); + stack.add(new SchemasStream(context, writer, generator, stream, modules.iterator())); stack.add(new SecurityStream(writer, new SecurityEntity(security))); } diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiServiceImpl.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiServiceImpl.java index 4b8b31c3d4..89eedd6238 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiServiceImpl.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/OpenApiServiceImpl.java @@ -10,6 +10,7 @@ package org.opendaylight.restconf.openapi.impl; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import javax.inject.Inject; @@ -20,7 +21,6 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMSchemaService; import org.opendaylight.restconf.openapi.api.OpenApiService; import org.opendaylight.restconf.openapi.model.MountPointInstance; -import org.opendaylight.restconf.openapi.model.OpenApiObject; import org.opendaylight.restconf.openapi.mountpoints.MountPointOpenApi; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -68,20 +68,19 @@ public final class OpenApiServiceImpl implements OpenApiService { } @Override - public Response getAllModulesDoc(final UriInfo uriInfo) { - final DefinitionNames definitionNames = new DefinitionNames(); - final OpenApiObject doc = openApiGeneratorRFC8040.getControllerModulesDoc(uriInfo, definitionNames); - return Response.ok(doc).build(); + public Response getAllModulesDoc(final UriInfo uriInfo) throws IOException { + final OpenApiInputStream stream = openApiGeneratorRFC8040.getControllerModulesDoc(uriInfo); + return Response.ok(stream).build(); } /** * Generates Swagger compliant document listing APIs for module. */ @Override - public Response getDocByModule(final String module, final String revision, final UriInfo uriInfo) { - return Response.ok( - openApiGeneratorRFC8040.getApiDeclaration(module, revision, uriInfo)) - .build(); + public Response getDocByModule(final String module, final String revision, final UriInfo uriInfo) + throws IOException { + final OpenApiInputStream stream = openApiGeneratorRFC8040.getApiDeclaration(module, revision, uriInfo); + return Response.ok(stream).build(); } /** @@ -103,17 +102,17 @@ public final class OpenApiServiceImpl implements OpenApiService { @Override public Response getMountDocByModule(final String instanceNum, final String module, - final String revision, final UriInfo uriInfo) { - final OpenApiObject api = mountPointOpenApiRFC8040.getMountPointApi(uriInfo, Long.parseLong(instanceNum), - module, revision); - return Response.ok(api).build(); + final String revision, final UriInfo uriInfo) throws IOException { + final OpenApiInputStream stream = + mountPointOpenApiRFC8040.getMountPointApi(uriInfo, Long.parseLong(instanceNum), module, revision); + return Response.ok(stream).build(); } @Override - public Response getMountDoc(final String instanceNum, final UriInfo uriInfo) { + public Response getMountDoc(final String instanceNum, final UriInfo uriInfo) throws IOException { final String stringPageNum = uriInfo.getQueryParameters().getFirst(PAGE_NUM); - final OpenApiObject api = mountPointOpenApiRFC8040.getMountPointApi(uriInfo, - Long.parseLong(instanceNum), stringPageNum); - return Response.ok(api).build(); + final OpenApiInputStream stream = + mountPointOpenApiRFC8040.getMountPointApi(uriInfo, Long.parseLong(instanceNum), stringPageNum); + return Response.ok(stream).build(); } } 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 adca13602e..f6cb5e950e 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 @@ -77,8 +77,9 @@ public final class PathsStream extends InputStream { public PathsStream(final EffectiveModelContext schemaContext, final OpenApiBodyWriter writer, final JsonGenerator generator, final ByteArrayOutputStream stream, final String deviceName, - final String urlPrefix, final boolean isForSingleModule, final boolean includeDataStore) { - iterator = schemaContext.getModules().iterator(); + final String urlPrefix, final boolean isForSingleModule, final boolean includeDataStore, + final Iterator iterator) { + this.iterator = iterator; this.generator = generator; this.writer = writer; this.stream = stream; 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 index 24cc2f173e..3ef816f57b 100644 --- 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 @@ -54,8 +54,9 @@ public final class SchemasStream extends InputStream { private boolean eos; public SchemasStream(final EffectiveModelContext context, final OpenApiBodyWriter writer, - final JsonGenerator generator, final ByteArrayOutputStream stream) { - iterator = context.getModules().iterator(); + final JsonGenerator generator, final ByteArrayOutputStream stream, + final Iterator iterator) { + this.iterator = iterator; this.context = context; this.writer = writer; this.generator = generator; diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/jaxrs/JaxbContextResolver.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/jaxrs/JaxbContextResolver.java new file mode 100644 index 0000000000..3102331a52 --- /dev/null +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/jaxrs/JaxbContextResolver.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. 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.jaxrs; + +import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; + +@Provider +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class JaxbContextResolver implements ContextResolver { + + @Override + public ObjectMapper getContext(final Class klass) { + return null; // must return null so that JAX-RS can continue context search + } +} 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 index f1ad49ac07..ab67d2acc9 100644 --- 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 @@ -12,28 +12,26 @@ 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; + private static final String DESCRIPTION = """ + We are providing full API for configurational data which can be edited (by POST, PUT, PATCH and DELETE). + For operational data we only provide GET API.\n + For majority of request you can see only config data in examples. That is because we can show only one example + per request. The exception when you can see operational data in example is when data are representing + operational (config false) container with no config data in it."""; - public InfoEntity(final String version, final String title, final String description) { - this.version = version; + public InfoEntity(final String title) { 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); - } + generator.writeStringField("version", "1.0.0"); if (title != null) { generator.writeStringField("title", title); } - if (description != null) { - generator.writeStringField("description", description); - } + generator.writeStringField("description", DESCRIPTION); generator.writeEndObject(); } } diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiObject.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiObject.java deleted file mode 100644 index c7de610330..0000000000 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/OpenApiObject.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import java.util.List; -import java.util.Map; - -@JsonInclude(Include.NON_NULL) -public record OpenApiObject( - String openapi, - Info info, - List servers, - Map paths, - Components components, - List>> security) { -} diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/Http.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/Http.java index de5c14ec3a..37e837ea07 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/Http.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/Http.java @@ -9,11 +9,9 @@ package org.opendaylight.restconf.openapi.model.security; import static java.util.Objects.requireNonNull; -import com.fasterxml.jackson.annotation.JsonInclude; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -@JsonInclude(JsonInclude.Include.NON_NULL) public record Http( @NonNull String scheme, @Nullable String description, diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/SecuritySchemeObject.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/SecuritySchemeObject.java index a831a8db3f..27cb33050f 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/SecuritySchemeObject.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/security/SecuritySchemeObject.java @@ -7,12 +7,7 @@ */ package org.opendaylight.restconf.openapi.model.security; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonInclude(JsonInclude.Include.NON_NULL) public interface SecuritySchemeObject { - @JsonProperty("type") Type type(); /** diff --git a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/mountpoints/MountPointOpenApi.java b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/mountpoints/MountPointOpenApi.java index 2da154bae6..dd6d5649f6 100644 --- a/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/mountpoints/MountPointOpenApi.java +++ b/restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/mountpoints/MountPointOpenApi.java @@ -9,42 +9,22 @@ package org.opendaylight.restconf.openapi.mountpoints; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.API_VERSION; import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.BASE_PATH; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.BASIC_AUTH_NAME; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.DESCRIPTION; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.OPEN_API_BASIC_AUTH; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.OPEN_API_VERSION; import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.SECURITY; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.filterByRange; -import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.getSortedModules; -import static org.opendaylight.restconf.openapi.impl.OpenApiServiceImpl.DEFAULT_PAGESIZE; -import static org.opendaylight.restconf.openapi.model.builder.OperationBuilder.SUMMARY_TEMPLATE; -import com.google.common.collect.Range; +import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicLong; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.dom.api.DOMMountPointListener; import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.dom.api.DOMSchemaService; import org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator; -import org.opendaylight.restconf.openapi.impl.DefinitionNames; -import org.opendaylight.restconf.openapi.model.Components; -import org.opendaylight.restconf.openapi.model.Info; -import org.opendaylight.restconf.openapi.model.OpenApiObject; -import org.opendaylight.restconf.openapi.model.Operation; -import org.opendaylight.restconf.openapi.model.Path; -import org.opendaylight.restconf.openapi.model.ResponseObject; -import org.opendaylight.restconf.openapi.model.Schema; -import org.opendaylight.restconf.openapi.model.Server; +import org.opendaylight.restconf.openapi.impl.OpenApiInputStream; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -159,24 +139,25 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable { .orElse(null); } - public OpenApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final String module, - final String revision) { + public OpenApiInputStream getMountPointApi(final UriInfo uriInfo, final Long id, final String module, + final String revision) throws IOException { final YangInstanceIdentifier iid = getInstanceId(id); final EffectiveModelContext context = getSchemaContext(iid); final String urlPrefix = getYangMountUrl(iid); - final String deviceName = extractDeviceName(iid); + final String deviceName = extractDeviceName(iid); if (context == null) { return null; } if (DATASTORES_LABEL.equals(module) && DATASTORES_REVISION.equals(revision)) { - return generateDataStoreOpenApi(uriInfo, urlPrefix, deviceName); + return generateDataStoreOpenApi(context, uriInfo, urlPrefix, deviceName); } return openApiGenerator.getApiDeclaration(module, revision, uriInfo, context, urlPrefix, deviceName); } - public OpenApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final @Nullable String strPageNum) { + public OpenApiInputStream getMountPointApi(final UriInfo uriInfo, final Long id, final @Nullable String strPageNum) + throws IOException { final var iid = getInstanceId(id); final var context = getSchemaContext(iid); final var urlPrefix = getYangMountUrl(iid); @@ -185,42 +166,22 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable { if (context == null) { return null; } - final var definitionNames = new DefinitionNames(); boolean includeDataStore = true; - Range range = Range.all(); if (strPageNum != null) { final var pageNum = Integer.parseInt(strPageNum); - final var end = DEFAULT_PAGESIZE * pageNum - 1; - int start = end - DEFAULT_PAGESIZE; - if (pageNum == 1) { - start++; - } else { + if (pageNum != 1) { includeDataStore = false; } - range = Range.closed(start, end); } final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo); final var host = openApiGenerator.createHostFromUriInfo(uriInfo); final var title = deviceName + " modules of RESTCONF"; - final var info = new Info(API_VERSION, title, DESCRIPTION); - final var servers = List.of(new Server(schema + "://" + host + BASE_PATH)); - - final var modules = getSortedModules(context); - final var filteredModules = filterByRange(modules, range); - final var paths = new HashMap(); - final var schemas = new HashMap(); - for (final var module : filteredModules) { - LOG.debug("Working on [{},{}]...", module.getName(), module.getQNameModule().getRevision().orElse(null)); - schemas.putAll(openApiGenerator.getSchemas(module, context, definitionNames, false)); - paths.putAll(openApiGenerator.getPaths(module, urlPrefix, deviceName, context, definitionNames, false)); - } - final var components = new Components(schemas, Map.of(BASIC_AUTH_NAME, OPEN_API_BASIC_AUTH)); - if (includeDataStore) { - paths.putAll(getDataStoreApiPaths(urlPrefix, deviceName)); - } - return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY); + final var url = schema + "://" + host + BASE_PATH; + final var modules = context.getModules(); + return new OpenApiInputStream(context, title, url, SECURITY, deviceName, urlPrefix, false, includeDataStore, + modules); } private static String extractDeviceName(final YangInstanceIdentifier iid) { @@ -228,43 +189,14 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable { .values().getElement().toString(); } - private OpenApiObject generateDataStoreOpenApi(final UriInfo uriInfo, final String context, - final String deviceName) { - final var info = new Info(API_VERSION, context, DESCRIPTION); + private OpenApiInputStream generateDataStoreOpenApi(EffectiveModelContext modelContext, + final UriInfo uriInfo, final String urlPrefix, final String deviceName) throws IOException { final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo); final var host = openApiGenerator.createHostFromUriInfo(uriInfo); - final var servers = List.of(new Server(schema + "://" + host + BASE_PATH)); - final var components = new Components(new HashMap<>(), Map.of(BASIC_AUTH_NAME, OPEN_API_BASIC_AUTH)); - final var paths = getDataStoreApiPaths(context, deviceName); - return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY); - } - - private Map getDataStoreApiPaths(final String context, final String deviceName) { - final var dataBuilder = new Path.Builder(); - dataBuilder.get(createGetPathItem("data", - "Queries the config (startup) datastore on the mounted hosted.", deviceName)); - - final var operationsBuilder = new Path.Builder(); - operationsBuilder.get(createGetPathItem("operations", - "Queries the available operations (RPC calls) on the mounted hosted.", deviceName)); - - return Map.of(openApiGenerator.getResourcePath("data", context), dataBuilder.build(), - openApiGenerator.getResourcePath("operations", context), operationsBuilder.build()); - } - - private static Operation createGetPathItem(final String resourceType, final String description, - final String deviceName) { - final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.GET, deviceName, "datastore", resourceType); - final List tags = List.of(deviceName + " GET root"); - final ResponseObject okResponse = new ResponseObject.Builder() - .description(Response.Status.OK.getReasonPhrase()) - .build(); - return new Operation.Builder() - .tags(tags) - .responses(Map.of(String.valueOf(Response.Status.OK.getStatusCode()), okResponse)) - .description(description) - .summary(summary) - .build(); + final var url = schema + "://" + host + BASE_PATH; + final var modules = modelContext.getModules(); + return new OpenApiInputStream(modelContext, urlPrefix, url, SECURITY, deviceName, urlPrefix, true, false, + modules); } @Override diff --git a/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/AbstractDocumentTest.java b/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/AbstractDocumentTest.java index 3e64359eb4..002897c434 100644 --- a/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/AbstractDocumentTest.java +++ b/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/AbstractDocumentTest.java @@ -13,6 +13,7 @@ import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.Optional; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; @@ -68,7 +69,8 @@ public abstract class AbstractDocumentTest { final var getAllController = createMockUriInfo(URI + "single"); final var controllerDocAll = openApiService.getAllModulesDoc(getAllController).getEntity(); - return MAPPER.writeValueAsString(controllerDocAll); + return new String(((OpenApiInputStream) controllerDocAll).readAllBytes(), + StandardCharsets.UTF_8); } protected static String getDocByModule(final String moduleName, final String revision) throws Exception { @@ -79,7 +81,8 @@ public abstract class AbstractDocumentTest { final var getModuleController = createMockUriInfo(uri); final var controllerDocModule = openApiService.getDocByModule(moduleName, revision, getModuleController); - return MAPPER.writeValueAsString(controllerDocModule.getEntity()); + return new String(((OpenApiInputStream) controllerDocModule.getEntity()).readAllBytes(), + StandardCharsets.UTF_8); } @@ -88,7 +91,8 @@ public abstract class AbstractDocumentTest { when(getAllDevice.getQueryParameters()).thenReturn(ImmutableMultivaluedMap.empty()); final var deviceDocAll = openApiService.getMountDoc("1", getAllDevice); - return MAPPER.writeValueAsString(deviceDocAll.getEntity()); + return new String(((OpenApiInputStream) deviceDocAll.getEntity()).readAllBytes(), + StandardCharsets.UTF_8); } @@ -96,7 +100,8 @@ public abstract class AbstractDocumentTest { final var getDevice = createMockUriInfo(URI + "mounts/1/" + moduleName); final var deviceDoc = openApiService.getMountDocByModule("1", moduleName, revision, getDevice); - return MAPPER.writeValueAsString(deviceDoc.getEntity()); + return new String(((OpenApiInputStream) deviceDoc.getEntity()).readAllBytes(), + StandardCharsets.UTF_8); } public static UriInfo createMockUriInfo(final String urlPrefix) throws Exception { -- 2.36.6