From 29dfb967c4104b4e8caca72bd4286d72ff5d2413 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Sun, 2 Aug 2020 13:21:57 +0200 Subject: [PATCH] Migrate to OpenAPI 3 annotations (#1576) * Upgrade to Swagger 2.1.0 * Add missing root and events resources * Fix wrong and add missing response implementations Signed-off-by: Wouter Born --- bom/compile/pom.xml | 11 +- bom/runtime/pom.xml | 14 +- .../rest/internal/ModuleTypeResource.java | 38 +-- .../rest/internal/RuleResource.java | 182 ++++++++------- .../rest/internal/TemplateResource.java | 34 +-- .../core/id/internal/UUIDResource.java | 21 +- .../io/rest/audio/internal/AudioResource.java | 43 ++-- .../io/rest/auth/internal/TokenResource.java | 24 +- .../core/internal/addons/AddonResource.java | 65 +++--- .../internal/binding/BindingResource.java | 49 ++-- .../internal/channel/ChannelTypeResource.java | 51 ++-- .../config/ConfigDescriptionResource.java | 42 ++-- .../internal/discovery/DiscoveryResource.java | 31 +-- .../internal/discovery/InboxResource.java | 57 ++--- .../rest/core/internal/item/ItemResource.java | 217 +++++++++--------- .../link/ItemChannelLinkResource.java | 69 +++--- .../persistence/PersistenceResource.java | 90 ++++---- .../internal/profile/ProfileTypeResource.java | 30 +-- .../service/ConfigurableServiceResource.java | 69 +++--- .../core/internal/thing/ThingResource.java | 189 ++++++++------- .../internal/thing/ThingTypeResource.java | 34 +-- .../core/io/rest/log/internal/LogHandler.java | 35 +-- .../sitemap/internal/SitemapResource.java | 66 +++--- .../openhab/core/io/rest/sse/SseResource.java | 34 +-- .../impl/AuthSwaggerReaderListener.java | 55 ----- .../impl/InfoSwaggerReaderListener.java | 52 ----- .../io/rest/swagger/impl/OpenApiResource.java | 168 ++++++++++++++ .../io/rest/swagger/impl/SwaggerResource.java | 127 ---------- .../core/io/rest/ui/internal/UIResource.java | 55 +++-- .../io/rest/voice/internal/VoiceResource.java | 77 ++++--- .../rest/internal/resources/RootResource.java | 30 ++- .../ui/icon/internal/IconSetResource.java | 18 +- .../openhab-tp/src/main/feature/feature.xml | 17 +- pom.xml | 2 +- 34 files changed, 1033 insertions(+), 1063 deletions(-) delete mode 100644 bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/AuthSwaggerReaderListener.java delete mode 100644 bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/InfoSwaggerReaderListener.java create mode 100644 bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/OpenApiResource.java delete mode 100644 bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/SwaggerResource.java diff --git a/bom/compile/pom.xml b/bom/compile/pom.xml index 99194d926..52fef4764 100644 --- a/bom/compile/pom.xml +++ b/bom/compile/pom.xml @@ -18,6 +18,7 @@ 9.3.25.v20180904 + 2.1.0 @@ -224,15 +225,15 @@ - io.swagger + io.swagger.core.v3 swagger-annotations - 1.5.5 + ${swagger.version} compile - io.swagger - swagger-jaxrs - 1.5.5 + io.swagger.core.v3 + swagger-jaxrs2 + ${swagger.version} compile diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 6eb6a7e86..f4af63f9c 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -19,7 +19,7 @@ 2.10.3 9.4.20.v20190813 7.2.11 - 1.6.1 + 2.1.0 @@ -943,7 +943,7 @@ com.fasterxml.jackson.datatype - jackson-datatype-joda + jackson-datatype-jsr310 ${jackson.version} compile @@ -968,25 +968,25 @@ - io.swagger + io.swagger.core.v3 swagger-annotations ${swagger.version} compile - io.swagger + io.swagger.core.v3 swagger-core ${swagger.version} compile - io.swagger - swagger-jaxrs + io.swagger.core.v3 + swagger-jaxrs2 ${swagger.version} compile - io.swagger + io.swagger.core.v3 swagger-models ${swagger.version} compile diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java index d873ada15..3a8750843 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/ModuleTypeResource.java @@ -52,11 +52,13 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for module types and is registered with the Jersey servlet. @@ -65,6 +67,7 @@ import io.swagger.annotations.ApiResponses; * @author Markus Rathgeb - Use DTOs * @author Ana Dimova - extends Module type DTOs with composites * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -72,7 +75,7 @@ import io.swagger.annotations.ApiResponses; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(ModuleTypeResource.PATH_MODULE_TYPES) -@Api(ModuleTypeResource.PATH_MODULE_TYPES) +@Tag(name = ModuleTypeResource.PATH_MODULE_TYPES) @NonNullByDefault public class ModuleTypeResource implements RESTResource { @@ -92,12 +95,12 @@ public class ModuleTypeResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all available module types.", response = ModuleTypeDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = ModuleTypeDTO.class, responseContainer = "List") }) - public Response getAll(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language, - @QueryParam("tags") @ApiParam(value = "tags for filtering") @Nullable String tagList, - @QueryParam("type") @ApiParam(value = "filtering by action, condition or trigger") @Nullable String type) { + @Operation(summary = "Get all available module types.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ModuleTypeDTO.class)))) }) + public Response getAll( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, + @QueryParam("tags") @Parameter(description = "tags for filtering") @Nullable String tagList, + @QueryParam("type") @Parameter(description = "filtering by action, condition or trigger") @Nullable String type) { final Locale locale = localeService.getLocale(language); final String[] tags = tagList != null ? tagList.split(",") : new String[0]; final List modules = new ArrayList<>(); @@ -117,11 +120,12 @@ public class ModuleTypeResource implements RESTResource { @GET @Path("/{moduleTypeUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets a module type corresponding to the given UID.", response = ModuleTypeDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ModuleTypeDTO.class), - @ApiResponse(code = 404, message = "Module Type corresponding to the given UID does not found.") }) - public Response getByUID(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language, - @PathParam("moduleTypeUID") @ApiParam(value = "moduleTypeUID") String moduleTypeUID) { + @Operation(summary = "Gets a module type corresponding to the given UID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ModuleTypeDTO.class))), + @ApiResponse(responseCode = "404", description = "Module Type corresponding to the given UID does not found.") }) + public Response getByUID( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, + @PathParam("moduleTypeUID") @Parameter(description = "moduleTypeUID") String moduleTypeUID) { Locale locale = localeService.getLocale(language); final ModuleType moduleType = moduleTypeRegistry.get(moduleTypeUID, locale); if (moduleType != null) { diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 074656f74..151c3c330 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -79,14 +79,15 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; -import io.swagger.annotations.ResponseHeader; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for rules. @@ -94,6 +95,7 @@ import io.swagger.annotations.ResponseHeader; * @author Kai Kreuzer - Initial contribution * @author Markus Rathgeb - Use DTOs * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -101,9 +103,9 @@ import io.swagger.annotations.ResponseHeader; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(RuleResource.PATH_RULES) -@Api(value = RuleResource.PATH_RULES, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) @RolesAllowed({ Role.ADMIN }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = RuleResource.PATH_RULES) @NonNullByDefault public class RuleResource implements RESTResource { @@ -130,9 +132,8 @@ public class RuleResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get available rules, optionally filtered by tags and/or prefix.", response = EnrichedRuleDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = EnrichedRuleDTO.class, responseContainer = "List") }) + @Operation(summary = "Get available rules, optionally filtered by tags and/or prefix.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedRuleDTO.class)))) }) public Response get(@QueryParam("prefix") final @Nullable String prefix, @QueryParam("tags") final @Nullable List tags) { // match all @@ -156,13 +157,12 @@ public class RuleResource implements RESTResource { @POST @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Creates a rule.") @Produces(MediaType.APPLICATION_JSON) - @ApiResponses(value = { - @ApiResponse(code = 201, message = "Created", responseHeaders = @ResponseHeader(name = "Location", description = "Newly created Rule", response = String.class)), - @ApiResponse(code = 409, message = "Creation of the rule is refused. Rule with the same UID already exists."), - @ApiResponse(code = 400, message = "Creation of the rule is refused. Missing required parameter.") }) - public Response create(@ApiParam(value = "rule data", required = true) RuleDTO rule) throws IOException { + @Operation(summary = "Creates a rule.", responses = { + @ApiResponse(responseCode = "201", description = "Created", headers = @Header(name = "Location", description = "Newly created Rule")), + @ApiResponse(responseCode = "409", description = "Creation of the rule is refused. Rule with the same UID already exists."), + @ApiResponse(responseCode = "400", description = "Creation of the rule is refused. Missing required parameter.") }) + public Response create(@Parameter(description = "rule data", required = true) RuleDTO rule) throws IOException { try { final Rule newRule = ruleRegistry.add(RuleDTOMapper.map(rule)); return Response.status(Status.CREATED) @@ -181,10 +181,10 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the rule corresponding to the given UID.", response = EnrichedRuleDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedRuleDTO.class), - @ApiResponse(code = 404, message = "Rule not found") }) - public Response getByUID(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) { + @Operation(summary = "Gets the rule corresponding to the given UID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = EnrichedRuleDTO.class))), + @ApiResponse(responseCode = "404", description = "Rule not found") }) + public Response getByUID(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { return Response.ok(EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)).build(); @@ -196,10 +196,10 @@ public class RuleResource implements RESTResource { @DELETE @Path("/{ruleUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Removes an existing rule corresponding to the given UID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response remove(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) { + @Operation(summary = "Removes an existing rule corresponding to the given UID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response remove(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) { Rule removedRule = ruleRegistry.remove(ruleUID); if (removedRule == null) { logger.info("Received HTTP DELETE request at '{}' for the unknown rule '{}'.", uriInfo.getPath(), ruleUID); @@ -212,11 +212,11 @@ public class RuleResource implements RESTResource { @PUT @Path("/{ruleUID}") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Updates an existing rule corresponding to the given UID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response update(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @ApiParam(value = "rule data", required = true) RuleDTO rule) throws IOException { + @Operation(summary = "Updates an existing rule corresponding to the given UID.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response update(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @Parameter(description = "rule data", required = true) RuleDTO rule) throws IOException { rule.uid = ruleUID; final Rule oldRule = ruleRegistry.update(RuleDTOMapper.map(rule)); if (oldRule == null) { @@ -231,10 +231,10 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/config") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the rule configuration values.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response getConfiguration(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) + @Operation(summary = "Gets the rule configuration values.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response getConfiguration(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) throws IOException { Rule rule = ruleRegistry.get(ruleUID); if (rule == null) { @@ -249,11 +249,11 @@ public class RuleResource implements RESTResource { @PUT @Path("/{ruleUID}/config") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Sets the rule configuration values.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response updateConfiguration(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @ApiParam(value = "config") Map configurationParameters) throws IOException { + @Operation(summary = "Sets the rule configuration values.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response updateConfiguration(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @Parameter(description = "config") Map configurationParameters) throws IOException { Map config = ConfigUtil.normalizeTypes(configurationParameters); Rule rule = ruleRegistry.get(ruleUID); if (rule == null) { @@ -270,11 +270,11 @@ public class RuleResource implements RESTResource { @POST @Path("/{ruleUID}/enable") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Sets the rule enabled status.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response enableRule(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @ApiParam(value = "enable", required = true) String enabled) throws IOException { + @Operation(summary = "Sets the rule enabled status.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response enableRule(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @Parameter(description = "enable", required = true) String enabled) throws IOException { Rule rule = ruleRegistry.get(ruleUID); if (rule == null) { logger.info("Received HTTP PUT request for set enabled at '{}' for the unknown rule '{}'.", @@ -290,10 +290,11 @@ public class RuleResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{ruleUID}/runnow") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Executes actions of the rule.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response runNow(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) throws IOException { + @Operation(summary = "Executes actions of the rule.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response runNow(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) + throws IOException { Rule rule = ruleRegistry.get(ruleUID); if (rule == null) { logger.info("Received HTTP PUT request for run now at '{}' for the unknown rule '{}'.", uriInfo.getPath(), @@ -308,11 +309,10 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/triggers") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the rule triggers.", response = TriggerDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = TriggerDTO.class, responseContainer = "List"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response getTriggers(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) { + @Operation(summary = "Gets the rule triggers.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TriggerDTO.class)))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response getTriggers(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { return Response.ok(TriggerDTOMapper.map(rule.getTriggers())).build(); @@ -324,11 +324,10 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/conditions") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the rule conditions.", response = ConditionDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = ConditionDTO.class, responseContainer = "List"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response getConditions(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) { + @Operation(summary = "Gets the rule conditions.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConditionDTO.class)))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response getConditions(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { return Response.ok(ConditionDTOMapper.map(rule.getConditions())).build(); @@ -340,11 +339,10 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/actions") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the rule actions.", response = ActionDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = ActionDTO.class, responseContainer = "List"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found.") }) - public Response getActions(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID) { + @Operation(summary = "Gets the rule actions.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ActionDTO.class)))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found.") }) + public Response getActions(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { return Response.ok(ActionDTOMapper.map(rule.getActions())).build(); @@ -356,12 +354,12 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/{moduleCategory}/{id}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the rule's module corresponding to the given Category and ID.", response = ModuleDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ModuleDTO.class), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) - public Response getModuleById(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @PathParam("moduleCategory") @ApiParam(value = "moduleCategory") String moduleCategory, - @PathParam("id") @ApiParam(value = "id") String id) { + @Operation(summary = "Gets the rule's module corresponding to the given Category and ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ModuleDTO.class))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) + public Response getModuleById(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @PathParam("moduleCategory") @Parameter(description = "moduleCategory") String moduleCategory, + @PathParam("id") @Parameter(description = "id") String id) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { final ModuleDTO dto = getModuleDTO(rule, moduleCategory, id); @@ -375,12 +373,12 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/{moduleCategory}/{id}/config") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the module's configuration.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) - public Response getModuleConfig(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @PathParam("moduleCategory") @ApiParam(value = "moduleCategory") String moduleCategory, - @PathParam("id") @ApiParam(value = "id") String id) { + @Operation(summary = "Gets the module's configuration.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) + public Response getModuleConfig(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @PathParam("moduleCategory") @Parameter(description = "moduleCategory") String moduleCategory, + @PathParam("id") @Parameter(description = "id") String id) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { Module module = getModule(rule, moduleCategory, id); @@ -394,13 +392,13 @@ public class RuleResource implements RESTResource { @GET @Path("/{ruleUID}/{moduleCategory}/{id}/config/{param}") @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Gets the module's configuration parameter.", response = String.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) - public Response getModuleConfigParam(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @PathParam("moduleCategory") @ApiParam(value = "moduleCategory") String moduleCategory, - @PathParam("id") @ApiParam(value = "id") String id, - @PathParam("param") @ApiParam(value = "param") String param) { + @Operation(summary = "Gets the module's configuration parameter.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) + public Response getModuleConfigParam(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @PathParam("moduleCategory") @Parameter(description = "moduleCategory") String moduleCategory, + @PathParam("id") @Parameter(description = "id") String id, + @PathParam("param") @Parameter(description = "param") String param) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { Module module = getModule(rule, moduleCategory, id); @@ -413,15 +411,15 @@ public class RuleResource implements RESTResource { @PUT @Path("/{ruleUID}/{moduleCategory}/{id}/config/{param}") - @ApiOperation(value = "Sets the module's configuration parameter value.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) + @Operation(summary = "Sets the module's configuration parameter value.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Rule corresponding to the given UID does not found or does not have a module with such Category and ID.") }) @Consumes(MediaType.TEXT_PLAIN) - public Response setModuleConfigParam(@PathParam("ruleUID") @ApiParam(value = "ruleUID") String ruleUID, - @PathParam("moduleCategory") @ApiParam(value = "moduleCategory") String moduleCategory, - @PathParam("id") @ApiParam(value = "id") String id, - @PathParam("param") @ApiParam(value = "param") String param, - @ApiParam(value = "value", required = true) String value) { + public Response setModuleConfigParam(@PathParam("ruleUID") @Parameter(description = "ruleUID") String ruleUID, + @PathParam("moduleCategory") @Parameter(description = "moduleCategory") String moduleCategory, + @PathParam("id") @Parameter(description = "id") String id, + @PathParam("param") @Parameter(description = "param") String param, + @Parameter(description = "value", required = true) String value) { Rule rule = ruleRegistry.get(ruleUID); if (rule != null) { Module module = getModule(rule, moduleCategory, id); diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/TemplateResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/TemplateResource.java index 6963db988..8c793c3d2 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/TemplateResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/TemplateResource.java @@ -45,17 +45,20 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for templates and is registered with the Jersey servlet. * * @author Kai Kreuzer - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -63,7 +66,7 @@ import io.swagger.annotations.ApiResponses; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(TemplateResource.PATH_TEMPLATES) -@Api(TemplateResource.PATH_TEMPLATES) +@Tag(name = TemplateResource.PATH_TEMPLATES) @NonNullByDefault public class TemplateResource implements RESTResource { @@ -83,10 +86,10 @@ public class TemplateResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all available templates.", response = Template.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = Template.class, responseContainer = "List") }) - public Response getAll(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language) { + @Operation(summary = "Get all available templates.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Template.class)))) }) + public Response getAll( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { Locale locale = localeService.getLocale(language); Collection result = templateRegistry.getAll(locale).stream() .map(template -> RuleTemplateDTOMapper.map(template)).collect(Collectors.toList()); @@ -96,11 +99,12 @@ public class TemplateResource implements RESTResource { @GET @Path("/{templateUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets a template corresponding to the given UID.", response = Template.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Template.class), - @ApiResponse(code = 404, message = "Template corresponding to the given UID does not found.") }) - public Response getByUID(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language, - @PathParam("templateUID") @ApiParam(value = "templateUID") String templateUID) { + @Operation(summary = "Gets a template corresponding to the given UID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Template.class))), + @ApiResponse(responseCode = "404", description = "Template corresponding to the given UID does not found.") }) + public Response getByUID( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, + @PathParam("templateUID") @Parameter(description = "templateUID") String templateUID) { Locale locale = localeService.getLocale(language); RuleTemplate template = templateRegistry.get(templateUID, locale); if (template != null) { diff --git a/bundles/org.openhab.core.id/src/main/java/org/openhab/core/id/internal/UUIDResource.java b/bundles/org.openhab.core.id/src/main/java/org/openhab/core/id/internal/UUIDResource.java index 23a70a252..2234014a9 100644 --- a/bundles/org.openhab.core.id/src/main/java/org/openhab/core/id/internal/UUIDResource.java +++ b/bundles/org.openhab.core.id/src/main/java/org/openhab/core/id/internal/UUIDResource.java @@ -30,27 +30,28 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for accessing the UUID of the instance * * @author Kai Kreuzer - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @JaxrsName(UUIDResource.PATH_UUID) @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @Path(UUIDResource.PATH_UUID) -@Api(value = UUIDResource.PATH_UUID, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) @RolesAllowed({ Role.ADMIN }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = UUIDResource.PATH_UUID) @NonNullByDefault public class UUIDResource implements RESTResource { @@ -58,8 +59,8 @@ public class UUIDResource implements RESTResource { @GET @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "A unified unique id.", response = String.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class) }) + @Operation(summary = "A unified unique id.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))) }) public Response getInstanceUUID() { return Response.ok(InstanceUUID.get()).build(); } diff --git a/bundles/org.openhab.core.io.rest.audio/src/main/java/org/openhab/core/io/rest/audio/internal/AudioResource.java b/bundles/org.openhab.core.io.rest.audio/src/main/java/org/openhab/core/io/rest/audio/internal/AudioResource.java index 34906e10d..ff6f4df85 100644 --- a/bundles/org.openhab.core.io.rest.audio/src/main/java/org/openhab/core/io/rest/audio/internal/AudioResource.java +++ b/bundles/org.openhab.core.io.rest.audio/src/main/java/org/openhab/core/io/rest/audio/internal/AudioResource.java @@ -46,17 +46,20 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for audio features. * * @author Laurent Garnier - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -65,7 +68,7 @@ import io.swagger.annotations.ApiResponses; @JSONRequired @Path(AudioResource.PATH_AUDIO) @RolesAllowed({ Role.USER, Role.ADMIN }) -@Api(AudioResource.PATH_AUDIO) +@Tag(name = AudioResource.PATH_AUDIO) @NonNullByDefault public class AudioResource implements RESTResource { @@ -86,10 +89,10 @@ public class AudioResource implements RESTResource { @GET @Path("/sources") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get the list of all sources.", response = AudioSourceDTO.class, responseContainer = "List") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get the list of all sources.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = AudioSourceDTO.class)))) }) public Response getSources( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); Collection sources = audioManager.getAllSources(); List dtos = new ArrayList<>(sources.size()); @@ -102,11 +105,11 @@ public class AudioResource implements RESTResource { @GET @Path("/defaultsource") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get the default source if defined or the first available source.", response = AudioSourceDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Source not found") }) + @Operation(summary = "Get the default source if defined or the first available source.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AudioSourceDTO.class))), + @ApiResponse(responseCode = "404", description = "Source not found") }) public Response getDefaultSource( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); AudioSource source = audioManager.getSource(); if (source != null) { @@ -119,10 +122,10 @@ public class AudioResource implements RESTResource { @GET @Path("/sinks") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get the list of all sinks.", response = AudioSinkDTO.class, responseContainer = "List") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get the list of all sinks.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = AudioSinkDTO.class)))) }) public Response getSinks( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); Collection sinks = audioManager.getAllSinks(); List dtos = new ArrayList<>(sinks.size()); @@ -135,11 +138,11 @@ public class AudioResource implements RESTResource { @GET @Path("/defaultsink") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get the default sink if defined or the first available sink.", response = AudioSinkDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Sink not found") }) + @Operation(summary = "Get the default sink if defined or the first available sink.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AudioSinkDTO.class))), + @ApiResponse(responseCode = "404", description = "Sink not found") }) public Response getDefaultSink( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); AudioSink sink = audioManager.getSink(); if (sink != null) { diff --git a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java index 134c8bdf7..71d583ce1 100644 --- a/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java +++ b/bundles/org.openhab.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java @@ -61,16 +61,18 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class is used to issue JWT tokens to clients. * * @author Yannick Schaus - Initial contribution * @author Wouter Born - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = { RESTResource.class, TokenResource.class }) @JaxrsResource @@ -78,7 +80,7 @@ import io.swagger.annotations.ApiResponses; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(TokenResource.PATH_AUTH) -@Api(TokenResource.PATH_AUTH) +@Tag(name = TokenResource.PATH_AUTH) @NonNullByDefault public class TokenResource implements RESTResource { private final Logger logger = LoggerFactory.getLogger(TokenResource.class); @@ -107,8 +109,8 @@ public class TokenResource implements RESTResource { @Path("/token") @Produces({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_FORM_URLENCODED }) - @ApiOperation(value = "Get access and refresh tokens.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get access and refresh tokens.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) public Response getToken(@FormParam("grant_type") String grantType, @FormParam("code") String code, @FormParam("redirect_uri") String redirectUri, @FormParam("client_id") String clientId, @FormParam("refresh_token") String refreshToken, @FormParam("code_verifier") String codeVerifier, @@ -135,8 +137,8 @@ public class TokenResource implements RESTResource { @GET @Path("/sessions") - @ApiOperation(value = "List the sessions associated to the authenticated user.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = UserSessionDTO.class) }) + @Operation(summary = "List the sessions associated to the authenticated user.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UserSessionDTO.class))) }) @Produces({ MediaType.APPLICATION_JSON }) public Response getSessions(@Context SecurityContext securityContext) { if (securityContext.getUserPrincipal() == null) { @@ -155,8 +157,8 @@ public class TokenResource implements RESTResource { @POST @Path("/logout") @Consumes({ MediaType.APPLICATION_FORM_URLENCODED }) - @ApiOperation(value = "Delete the session associated with a refresh token.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Delete the session associated with a refresh token.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) public Response deleteSession(@FormParam("refresh_token") String refreshToken, @FormParam("id") String id, @Context SecurityContext securityContext) { if (securityContext.getUserPrincipal() == null) { diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java index 801ad8c4e..aecc1c47c 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/addons/AddonResource.java @@ -63,13 +63,13 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for add-ons and provides methods to install and uninstall them. @@ -77,6 +77,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Kai Kreuzer - Initial contribution * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -85,8 +86,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(AddonResource.PATH_ADDONS) @RolesAllowed({ Role.ADMIN }) -@Api(value = AddonResource.PATH_ADDONS, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = AddonResource.PATH_ADDONS) @NonNullByDefault public class AddonResource implements RESTResource { @@ -118,10 +119,10 @@ public class AddonResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all add-ons.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class) }) + @Operation(summary = "Get all add-ons.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))) }) public Response getExtensions( - @HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language) { + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); return Response.ok(new Stream2JSONInputStream(getAllExtensions(locale))).build(); @@ -130,9 +131,10 @@ public class AddonResource implements RESTResource { @GET @Path("/types") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all add-on types.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class) }) - public Response getTypes(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language) { + @Operation(summary = "Get all add-on types.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))) }) + public Response getTypes( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath()); Locale locale = localeService.getLocale(language); Stream addonTypeStream = getAllExtensionTypes(locale).stream().distinct(); @@ -142,11 +144,12 @@ public class AddonResource implements RESTResource { @GET @Path("/{addonId: [a-zA-Z_0-9-:]+}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get add-on with given ID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Not found") }) - public Response getById(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language, - @PathParam("addonId") @ApiParam(value = "addon ID") String addonId) { + @Operation(summary = "Get add-on with given ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Not found") }) + public Response getById( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, + @PathParam("addonId") @Parameter(description = "addon ID") String addonId) { logger.debug("Received HTTP GET request at '{}'.", uriInfo.getPath()); Locale locale = localeService.getLocale(language); AddonService addonService = getAddonService(addonId); @@ -160,9 +163,9 @@ public class AddonResource implements RESTResource { @POST @Path("/{addonId: [a-zA-Z_0-9-:]+}/install") - @ApiOperation(value = "Installs the add-on with the given ID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Response installAddon(final @PathParam("addonId") @ApiParam(value = "addon ID") String addonId) { + @Operation(summary = "Installs the add-on with the given ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) + public Response installAddon(final @PathParam("addonId") @Parameter(description = "addon ID") String addonId) { ThreadPoolManager.getPool(THREAD_POOL_NAME).submit(() -> { try { AddonService addonService = getAddonService(addonId); @@ -177,10 +180,11 @@ public class AddonResource implements RESTResource { @POST @Path("/url/{url}/install") - @ApiOperation(value = "Installs the add-on from the given URL.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "The given URL is malformed or not valid.") }) - public Response installExtensionByURL(final @PathParam("url") @ApiParam(value = "addon install URL") String url) { + @Operation(summary = "Installs the add-on from the given URL.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "The given URL is malformed or not valid.") }) + public Response installExtensionByURL( + final @PathParam("url") @Parameter(description = "addon install URL") String url) { try { URI addonURI = new URI(url); String addonId = getAddonId(addonURI); @@ -195,9 +199,10 @@ public class AddonResource implements RESTResource { @POST @Path("/{addonId: [a-zA-Z_0-9-:]+}/uninstall") - @ApiOperation(value = "Uninstalls the add-on with the given ID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Response uninstallExtension(final @PathParam("addonId") @ApiParam(value = "addon ID") String addonId) { + @Operation(summary = "Uninstalls the add-on with the given ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) + public Response uninstallExtension( + final @PathParam("addonId") @Parameter(description = "addon ID") String addonId) { ThreadPoolManager.getPool(THREAD_POOL_NAME).submit(() -> { try { AddonService addonService = getAddonService(addonId); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/binding/BindingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/binding/BindingResource.java index eba7c270b..2bfcf3fb5 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/binding/BindingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/binding/BindingResource.java @@ -58,13 +58,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for bindings and is registered with the @@ -75,6 +76,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Yordan Zhelev - Added Swagger annotations * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -83,8 +85,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(BindingResource.PATH_BINDINGS) @RolesAllowed({ Role.ADMIN }) -@Api(value = BindingResource.PATH_BINDINGS, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = BindingResource.PATH_BINDINGS) @NonNullByDefault public class BindingResource implements RESTResource { @@ -112,11 +114,10 @@ public class BindingResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(nickname = "getAllBindings", value = "Get all bindings.", response = BindingInfoDTO.class, responseContainer = "Set") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = BindingInfoDTO.class, responseContainer = "Set") }) + @Operation(operationId = "getAllBindings", summary = "Get all bindings.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = BindingInfoDTO.class), uniqueItems = true))) }) public Response getAll( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); Set bindingInfos = bindingInfoRegistry.getBindingInfos(locale); @@ -126,11 +127,11 @@ public class BindingResource implements RESTResource { @GET @Path("/{bindingId}/config") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(nickname = "getBindingConfiguration", value = "Get binding configuration for given binding ID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Binding does not exist"), - @ApiResponse(code = 500, message = "Configuration can not be read due to internal error") }) - public Response getConfiguration(@PathParam("bindingId") @ApiParam(value = "service ID") String bindingId) { + @Operation(operationId = "getBindingConfiguration", summary = "Get binding configuration for given binding ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Binding does not exist"), + @ApiResponse(responseCode = "500", description = "Configuration can not be read due to internal error") }) + public Response getConfiguration(@PathParam("bindingId") @Parameter(description = "service ID") String bindingId) { try { String configId = getConfigId(bindingId); if (configId == null) { @@ -151,12 +152,12 @@ public class BindingResource implements RESTResource { @Path("/{bindingId}/config") @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Updates a binding configuration for given binding ID and returns the old configuration.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 204, message = "No old configuration"), - @ApiResponse(code = 404, message = "Binding does not exist"), - @ApiResponse(code = 500, message = "Configuration can not be updated due to internal error") }) - public Response updateConfiguration(@PathParam("bindingId") @ApiParam(value = "service ID") String bindingId, + @Operation(summary = "Updates a binding configuration for given binding ID and returns the old configuration.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "204", description = "No old configuration"), + @ApiResponse(responseCode = "404", description = "Binding does not exist"), + @ApiResponse(responseCode = "500", description = "Configuration can not be updated due to internal error") }) + public Response updateConfiguration(@PathParam("bindingId") @Parameter(description = "service ID") String bindingId, @Nullable Map configuration) { try { String configId = getConfigId(bindingId); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java index dac8476d1..e3b7dd90e 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/channel/ChannelTypeResource.java @@ -62,13 +62,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * Provides access to ChannelType via REST. @@ -77,6 +78,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Yannick Schaus - Added filter to getAll * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -85,8 +87,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(ChannelTypeResource.PATH_CHANNEL_TYPES) @RolesAllowed({ Role.ADMIN }) -@Api(value = ChannelTypeResource.PATH_CHANNEL_TYPES, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = ChannelTypeResource.PATH_CHANNEL_TYPES) @NonNullByDefault public class ChannelTypeResource implements RESTResource { @@ -112,11 +114,11 @@ public class ChannelTypeResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets all available channel types.", response = ChannelTypeDTO.class, responseContainer = "Set") - @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = ChannelTypeDTO.class, responseContainer = "Set")) + @Operation(summary = "Gets all available channel types.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ChannelTypeDTO.class), uniqueItems = true))) }) public Response getAll( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @QueryParam("prefixes") @ApiParam(value = "filter UIDs by prefix (multiple comma-separated prefixes allowed, for example: 'system,mqtt')") @Nullable String prefixes) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @QueryParam("prefixes") @Parameter(description = "filter UIDs by prefix (multiple comma-separated prefixes allowed, for example: 'system,mqtt')") @Nullable String prefixes) { Locale locale = localeService.getLocale(language); Stream channelStream = channelTypeRegistry.getChannelTypes(locale).stream() @@ -136,12 +138,12 @@ public class ChannelTypeResource implements RESTResource { @GET @Path("/{channelTypeUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets channel type by UID.", response = ChannelTypeDTO.class) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "Channel type with provided channelTypeUID does not exist.", response = ChannelTypeDTO.class), - @ApiResponse(code = 404, message = "No content") }) - public Response getByUID(@PathParam("channelTypeUID") @ApiParam(value = "channelTypeUID") String channelTypeUID, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @Operation(summary = "Gets channel type by UID.", responses = { + @ApiResponse(responseCode = "200", description = "Channel type with provided channelTypeUID does not exist.", content = @Content(schema = @Schema(implementation = ChannelTypeDTO.class))), + @ApiResponse(responseCode = "404", description = "No content") }) + public Response getByUID( + @PathParam("channelTypeUID") @Parameter(description = "channelTypeUID") String channelTypeUID, + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { Locale locale = localeService.getLocale(language); ChannelType channelType = channelTypeRegistry.getChannelType(new ChannelTypeUID(channelTypeUID), locale); if (channelType != null) { @@ -154,13 +156,12 @@ public class ChannelTypeResource implements RESTResource { @GET @Path("/{channelTypeUID}/linkableItemTypes") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the item types the given trigger channel type UID can be linked to.", response = String.class, responseContainer = "Set") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = String.class, responseContainer = "Set"), - @ApiResponse(code = 204, message = "No content: channel type has no linkable items or is no trigger channel."), - @ApiResponse(code = 404, message = "Given channel type UID not found.") }) + @Operation(summary = "Gets the item types the given trigger channel type UID can be linked to.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class), uniqueItems = true))), + @ApiResponse(responseCode = "204", description = "No content: channel type has no linkable items or is no trigger channel."), + @ApiResponse(responseCode = "404", description = "Given channel type UID not found.") }) public Response getLinkableItemTypes( - @PathParam("channelTypeUID") @ApiParam(value = "channelTypeUID") String channelTypeUID) { + @PathParam("channelTypeUID") @Parameter(description = "channelTypeUID") String channelTypeUID) { ChannelTypeUID ctUID = new ChannelTypeUID(channelTypeUID); ChannelType channelType = channelTypeRegistry.getChannelType(ctUID); if (channelType == null) { diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java index 1c22069a5..dc5f8f6e1 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/config/ConfigDescriptionResource.java @@ -49,13 +49,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * {@link ConfigDescriptionResource} provides access to {@link ConfigDescription}s via REST. @@ -64,6 +65,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Chris Jackson - Modify response to use JSONResponse * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -72,9 +74,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS) @RolesAllowed({ Role.ADMIN }) -@Api(value = ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS, authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = ConfigDescriptionResource.PATH_CONFIG_DESCRIPTIONS) @NonNullByDefault public class ConfigDescriptionResource implements RESTResource { @@ -94,10 +95,11 @@ public class ConfigDescriptionResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(nickname = "getAllConfigDescriptions", value = "Gets all available config descriptions.", response = ConfigDescriptionDTO.class, responseContainer = "List") - @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = ConfigDescriptionDTO.class, responseContainer = "List")) - public Response getAll(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language, // - @QueryParam("scheme") @ApiParam(value = "scheme filter") @Nullable String scheme) { + @Operation(operationId = "getAllConfigDescriptions", summary = "Gets all available config descriptions.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConfigDescriptionDTO.class)))) }) + public Response getAll( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, // + @QueryParam("scheme") @Parameter(description = "scheme filter") @Nullable String scheme) { Locale locale = localeService.getLocale(language); Collection configDescriptions = configDescriptionRegistry.getConfigDescriptions(locale); return Response.ok(new Stream2JSONInputStream(configDescriptions.stream().filter(configDescription -> { @@ -108,11 +110,13 @@ public class ConfigDescriptionResource implements RESTResource { @GET @Path("/{uri}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets a config description by URI.", response = ConfigDescriptionDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ConfigDescriptionDTO.class), - @ApiResponse(code = 400, message = "Invalid URI syntax"), @ApiResponse(code = 404, message = "Not found") }) - public Response getByURI(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language, - @PathParam("uri") @ApiParam(value = "uri") String uri) { + @Operation(summary = "Gets a config description by URI.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ConfigDescriptionDTO.class))), + @ApiResponse(responseCode = "400", description = "Invalid URI syntax"), + @ApiResponse(responseCode = "404", description = "Not found") }) + public Response getByURI( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language, + @PathParam("uri") @Parameter(description = "uri") String uri) { Locale locale = localeService.getLocale(language); URI uriObject = UriBuilder.fromPath(uri).build(); ConfigDescription configDescription = configDescriptionRegistry.getConfigDescription(uriObject, locale); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/DiscoveryResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/DiscoveryResource.java index 34cdf4975..67a381c51 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/DiscoveryResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/DiscoveryResource.java @@ -42,13 +42,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for discovery and is registered with the @@ -60,6 +61,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Ivaylo Ivanov - Added payload to the response of scan * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = { RESTResource.class, DiscoveryResource.class }) @JaxrsResource @@ -68,8 +70,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(DiscoveryResource.PATH_DISCOVERY) @RolesAllowed({ Role.ADMIN }) -@Api(value = DiscoveryResource.PATH_DISCOVERY, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = DiscoveryResource.PATH_DISCOVERY) @NonNullByDefault public class DiscoveryResource implements RESTResource { @@ -87,9 +89,8 @@ public class DiscoveryResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets all bindings that support discovery.", response = String.class, responseContainer = "Set") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = String.class, responseContainer = "Set") }) + @Operation(summary = "Gets all bindings that support discovery.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class), uniqueItems = true))) }) public Response getDiscoveryServices() { Collection supportedBindings = discoveryServiceRegistry.getSupportedBindings(); return Response.ok(new LinkedHashSet<>(supportedBindings)).build(); @@ -98,9 +99,9 @@ public class DiscoveryResource implements RESTResource { @POST @Path("/bindings/{bindingId}/scan") @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Starts asynchronous discovery process for a binding and returns the timeout in seconds of the discovery operation.", response = Integer.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Integer.class) }) - public Response scan(@PathParam("bindingId") @ApiParam(value = "bindingId") final String bindingId) { + @Operation(summary = "Starts asynchronous discovery process for a binding and returns the timeout in seconds of the discovery operation.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = Integer.class))) }) + public Response scan(@PathParam("bindingId") @Parameter(description = "bindingId") final String bindingId) { discoveryServiceRegistry.startScan(bindingId, new ScanListener() { @Override public void onErrorOccurred(@Nullable Exception exception) { diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/InboxResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/InboxResource.java index ac5fd2e01..d7958de4f 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/InboxResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/discovery/InboxResource.java @@ -52,13 +52,13 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for the inbox and is registered with the @@ -70,6 +70,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Chris Jackson - Updated to use JSONResponse. Fixed null response from approve. Improved error reporting. * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = { RESTResource.class, InboxResource.class }) @JaxrsResource @@ -78,8 +79,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(InboxResource.PATH_INBOX) @RolesAllowed({ Role.ADMIN }) -@Api(value = InboxResource.PATH_INBOX, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = InboxResource.PATH_INBOX) @NonNullByDefault public class InboxResource implements RESTResource { private final Logger logger = LoggerFactory.getLogger(InboxResource.class); @@ -97,14 +98,14 @@ public class InboxResource implements RESTResource { @POST @Path("/{thingUID}/approve") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Approves the discovery result by adding the thing to the registry.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Thing unable to be approved."), - @ApiResponse(code = 409, message = "No binding found that supports this thing.") }) + @Operation(summary = "Approves the discovery result by adding the thing to the registry.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Thing unable to be approved."), + @ApiResponse(responseCode = "409", description = "No binding found that supports this thing.") }) public Response approve( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, - @ApiParam(value = "thing label") @Nullable String label) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thingUID") String thingUID, + @Parameter(description = "thing label") @Nullable String label) { ThingUID thingUIDObject = new ThingUID(thingUID); String notEmptyLabel = label != null && !label.isEmpty() ? label : null; Thing thing = null; @@ -125,10 +126,10 @@ public class InboxResource implements RESTResource { @DELETE @Path("/{thingUID}") - @ApiOperation(value = "Removes the discovery result from the inbox.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Discovery result not found in the inbox.") }) - public Response delete(@PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID) { + @Operation(summary = "Removes the discovery result from the inbox.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Discovery result not found in the inbox.") }) + public Response delete(@PathParam("thingUID") @Parameter(description = "thingUID") String thingUID) { if (inbox.remove(new ThingUID(thingUID))) { return Response.ok(null, MediaType.TEXT_PLAIN).build(); } else { @@ -138,8 +139,8 @@ public class InboxResource implements RESTResource { @GET @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get all discovered things.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = DiscoveryResultDTO.class) }) + @Operation(summary = "Get all discovered things.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DiscoveryResultDTO.class))) }) public Response getAll() { Stream discoveryStream = inbox.getAll().stream().map(DiscoveryResultDTOMapper::map); return Response.ok(new Stream2JSONInputStream(discoveryStream)).build(); @@ -147,18 +148,18 @@ public class InboxResource implements RESTResource { @POST @Path("/{thingUID}/ignore") - @ApiOperation(value = "Flags a discovery result as ignored for further processing.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Response ignore(@PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID) { + @Operation(summary = "Flags a discovery result as ignored for further processing.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) + public Response ignore(@PathParam("thingUID") @Parameter(description = "thingUID") String thingUID) { inbox.setFlag(new ThingUID(thingUID), DiscoveryResultFlag.IGNORED); return Response.ok(null, MediaType.TEXT_PLAIN).build(); } @POST @Path("/{thingUID}/unignore") - @ApiOperation(value = "Removes ignore flag from a discovery result.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Response unignore(@PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID) { + @Operation(summary = "Removes ignore flag from a discovery result.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) + public Response unignore(@PathParam("thingUID") @Parameter(description = "thingUID") String thingUID) { inbox.setFlag(new ThingUID(thingUID), DiscoveryResultFlag.NEW); return Response.ok(null, MediaType.TEXT_PLAIN).build(); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index c76991da1..07a40e5ed 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -92,13 +92,14 @@ import org.slf4j.LoggerFactory; import com.google.gson.JsonObject; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** *

@@ -121,6 +122,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Stefan Triller - Added bulk item add method * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -128,7 +130,7 @@ import io.swagger.annotations.AuthorizationScope; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(ItemResource.PATH_ITEMS) -@Api(ItemResource.PATH_ITEMS) +@Tag(name = ItemResource.PATH_ITEMS) @NonNullByDefault public class ItemResource implements RESTResource { @@ -193,16 +195,15 @@ public class ItemResource implements RESTResource { @GET @RolesAllowed({ Role.USER, Role.ADMIN }) @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all available items.", response = EnrichedItemDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class, responseContainer = "List") }) + @Operation(summary = "Get all available items.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedItemDTO.class)))) }) public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @QueryParam("type") @ApiParam(value = "item type filter") @Nullable String type, - @QueryParam("tags") @ApiParam(value = "item tag filter") @Nullable String tags, - @QueryParam("metadata") @ApiParam(value = "metadata selector") @Nullable String namespaceSelector, - @DefaultValue("false") @QueryParam("recursive") @ApiParam(value = "get member items recursively") boolean recursive, - @QueryParam("fields") @ApiParam(value = "limit output to the given fields (comma separated)") @Nullable String fields) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @QueryParam("type") @Parameter(description = "item type filter") @Nullable String type, + @QueryParam("tags") @Parameter(description = "item tag filter") @Nullable String tags, + @QueryParam("metadata") @Parameter(description = "metadata selector") @Nullable String namespaceSelector, + @DefaultValue("false") @QueryParam("recursive") @Parameter(description = "get member items recursively") boolean recursive, + @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields) { final Locale locale = localeService.getLocale(language); final Set namespaces = splitAndFilterNamespaces(namespaceSelector, locale); @@ -220,13 +221,13 @@ public class ItemResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Gets a single item.", response = EnrichedItemDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = EnrichedItemDTO.class), - @ApiResponse(code = 404, message = "Item not found") }) + @Operation(summary = "Gets a single item.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = EnrichedItemDTO.class))), + @ApiResponse(responseCode = "404", description = "Item not found") }) public Response getItemData(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @QueryParam("metadata") @ApiParam(value = "metadata selector") @Nullable String namespaceSelector, - @PathParam("itemname") @ApiParam(value = "item name") String itemname) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @QueryParam("metadata") @Parameter(description = "metadata selector") @Nullable String namespaceSelector, + @PathParam("itemname") @Parameter(description = "item name") String itemname) { final Locale locale = localeService.getLocale(language); final Set namespaces = splitAndFilterNamespaces(namespaceSelector, locale); @@ -257,10 +258,10 @@ public class ItemResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}/state") @Produces(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Gets the state of an item.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Item not found") }) - public Response getPlainItemState(@PathParam("itemname") @ApiParam(value = "item name") String itemname) { + @Operation(summary = "Gets the state of an item.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Item not found") }) + public Response getPlainItemState(@PathParam("itemname") @Parameter(description = "item name") String itemname) { // get item Item item = getItem(itemname); @@ -278,14 +279,14 @@ public class ItemResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}/state") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Updates the state of an item.") - @ApiResponses(value = { @ApiResponse(code = 202, message = "Accepted"), - @ApiResponse(code = 404, message = "Item not found"), - @ApiResponse(code = 400, message = "Item state null") }) + @Operation(summary = "Updates the state of an item.", responses = { + @ApiResponse(responseCode = "202", description = "Accepted"), + @ApiResponse(responseCode = "404", description = "Item not found"), + @ApiResponse(responseCode = "400", description = "Item state null") }) public Response putItemState( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("itemname") @ApiParam(value = "item name") String itemname, - @ApiParam(value = "valid item state (e.g. ON, OFF)", required = true) String value) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("itemname") @Parameter(description = "item name") String itemname, + @Parameter(description = "valid item state (e.g. ON, OFF)", required = true) String value) { final Locale locale = localeService.getLocale(language); // get Item @@ -314,12 +315,12 @@ public class ItemResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Sends a command to an item.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item not found"), - @ApiResponse(code = 400, message = "Item command null") }) - public Response postItemCommand(@PathParam("itemname") @ApiParam(value = "item name") String itemname, - @ApiParam(value = "valid item command (e.g. ON, OFF, UP, DOWN, REFRESH)", required = true) String value) { + @Operation(summary = "Sends a command to an item.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item not found"), + @ApiResponse(responseCode = "400", description = "Item command null") }) + public Response postItemCommand(@PathParam("itemname") @Parameter(description = "item name") String itemname, + @Parameter(description = "valid item command (e.g. ON, OFF, UP, DOWN, REFRESH)", required = true) String value) { Item item = getItem(itemname); Command command = null; if (item != null) { @@ -355,14 +356,13 @@ public class ItemResource implements RESTResource { @PUT @RolesAllowed({ Role.ADMIN }) @Path("/{itemName: [a-zA-Z_0-9]+}/members/{memberItemName: [a-zA-Z_0-9]+}") - @ApiOperation(value = "Adds a new member to a group item.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item or member item not found or item is not of type group item."), - @ApiResponse(code = 405, message = "Member item is not editable.") }) - public Response addMember(@PathParam("itemName") @ApiParam(value = "item name") String itemName, - @PathParam("memberItemName") @ApiParam(value = "member item name") String memberItemName) { + @Operation(summary = "Adds a new member to a group item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item or member item not found or item is not of type group item."), + @ApiResponse(responseCode = "405", description = "Member item is not editable.") }) + public Response addMember(@PathParam("itemName") @Parameter(description = "item name") String itemName, + @PathParam("memberItemName") @Parameter(description = "member item name") String memberItemName) { try { Item item = itemRegistry.getItem(itemName); @@ -395,14 +395,13 @@ public class ItemResource implements RESTResource { @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{itemName: [a-zA-Z_0-9]+}/members/{memberItemName: [a-zA-Z_0-9]+}") - @ApiOperation(value = "Removes an existing member from a group item.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item or member item not found or item is not of type group item."), - @ApiResponse(code = 405, message = "Member item is not editable.") }) - public Response removeMember(@PathParam("itemName") @ApiParam(value = "item name") String itemName, - @PathParam("memberItemName") @ApiParam(value = "member item name") String memberItemName) { + @Operation(summary = "Removes an existing member from a group item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item or member item not found or item is not of type group item."), + @ApiResponse(responseCode = "405", description = "Member item is not editable.") }) + public Response removeMember(@PathParam("itemName") @Parameter(description = "item name") String itemName, + @PathParam("memberItemName") @Parameter(description = "member item name") String memberItemName) { try { Item item = itemRegistry.getItem(itemName); @@ -435,12 +434,11 @@ public class ItemResource implements RESTResource { @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}") - @ApiOperation(value = "Removes an item from the registry.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item not found or item is not editable.") }) - public Response removeItem(@PathParam("itemname") @ApiParam(value = "item name") String itemname) { + @Operation(summary = "Removes an item from the registry.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item not found or item is not editable.") }) + public Response removeItem(@PathParam("itemname") @Parameter(description = "item name") String itemname) { if (managedItemProvider.remove(itemname) == null) { return Response.status(Status.NOT_FOUND).build(); } @@ -450,13 +448,13 @@ public class ItemResource implements RESTResource { @PUT @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}/tags/{tag}") - @ApiOperation(value = "Adds a tag to an item.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item not found."), - @ApiResponse(code = 405, message = "Item not editable.") }) - public Response addTag(@PathParam("itemname") @ApiParam(value = "item name") String itemname, - @PathParam("tag") @ApiParam(value = "tag") String tag) { + @Operation(summary = "Adds a tag to an item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item not found."), + @ApiResponse(responseCode = "405", description = "Item not editable.") }) + public Response addTag(@PathParam("itemname") @Parameter(description = "item name") String itemname, + @PathParam("tag") @Parameter(description = "tag") String tag) { Item item = getItem(itemname); if (item == null) { @@ -476,13 +474,13 @@ public class ItemResource implements RESTResource { @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}/tags/{tag}") - @ApiOperation(value = "Removes a tag from an item.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item not found."), - @ApiResponse(code = 405, message = "Item not editable.") }) - public Response removeTag(@PathParam("itemname") @ApiParam(value = "item name") String itemname, - @PathParam("tag") @ApiParam(value = "tag") String tag) { + @Operation(summary = "Removes a tag from an item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item not found."), + @ApiResponse(responseCode = "405", description = "Item not editable.") }) + public Response removeTag(@PathParam("itemname") @Parameter(description = "item name") String itemname, + @PathParam("tag") @Parameter(description = "tag") String tag) { Item item = getItem(itemname); if (item == null) { @@ -503,17 +501,16 @@ public class ItemResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}/metadata/{namespace}") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Adds metadata to an item.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { // - @ApiResponse(code = 200, message = "OK"), // - @ApiResponse(code = 201, message = "Created"), // - @ApiResponse(code = 400, message = "Metadata value empty."), // - @ApiResponse(code = 404, message = "Item not found."), // - @ApiResponse(code = 405, message = "Metadata not editable.") }) - public Response addMetadata(@PathParam("itemname") @ApiParam(value = "item name") String itemname, - @PathParam("namespace") @ApiParam(value = "namespace") String namespace, - @ApiParam(value = "metadata", required = true) MetadataDTO metadata) { + @Operation(summary = "Adds metadata to an item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { // + @ApiResponse(responseCode = "200", description = "OK"), // + @ApiResponse(responseCode = "201", description = "Created"), // + @ApiResponse(responseCode = "400", description = "Metadata value empty."), // + @ApiResponse(responseCode = "404", description = "Item not found."), // + @ApiResponse(responseCode = "405", description = "Metadata not editable.") }) + public Response addMetadata(@PathParam("itemname") @Parameter(description = "item name") String itemname, + @PathParam("namespace") @Parameter(description = "namespace") String namespace, + @Parameter(description = "metadata", required = true) MetadataDTO metadata) { Item item = getItem(itemname); if (item == null) { @@ -539,14 +536,13 @@ public class ItemResource implements RESTResource { @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}/metadata/{namespace}") - @ApiOperation(value = "Removes metadata from an item.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Item not found."), - @ApiResponse(code = 405, message = "Meta data not editable.") }) - public Response removeMetadata(@PathParam("itemname") @ApiParam(value = "item name") String itemname, - @PathParam("namespace") @ApiParam(value = "namespace") String namespace) { + @Operation(summary = "Removes metadata from an item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Item not found."), + @ApiResponse(responseCode = "405", description = "Meta data not editable.") }) + public Response removeMetadata(@PathParam("itemname") @Parameter(description = "item name") String itemname, + @PathParam("namespace") @Parameter(description = "namespace") String namespace) { Item item = getItem(itemname); if (item == null) { @@ -576,17 +572,17 @@ public class ItemResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{itemname: [a-zA-Z_0-9]+}") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Adds a new item to the registry or updates the existing item.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 201, message = "Item created."), @ApiResponse(code = 400, message = "Item null."), - @ApiResponse(code = 404, message = "Item not found."), - @ApiResponse(code = 405, message = "Item not editable.") }) + @Operation(summary = "Adds a new item to the registry or updates the existing item.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "201", description = "Item created."), + @ApiResponse(responseCode = "400", description = "Item null."), + @ApiResponse(responseCode = "404", description = "Item not found."), + @ApiResponse(responseCode = "405", description = "Item not editable.") }) public Response createOrUpdateItem(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("itemname") @ApiParam(value = "item name") String itemname, - @ApiParam(value = "item data", required = true) @Nullable GroupItemDTO item) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("itemname") @Parameter(description = "item name") String itemname, + @Parameter(description = "item data", required = true) @Nullable GroupItemDTO item) { final Locale locale = localeService.getLocale(language); // If we didn't get an item bean, then return! @@ -629,13 +625,12 @@ public class ItemResource implements RESTResource { @PUT @RolesAllowed({ Role.ADMIN }) @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Adds a list of items to the registry or updates the existing items.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 400, message = "Item list is null.") }) + @Operation(summary = "Adds a list of items to the registry or updates the existing items.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "Item list is null.") }) public Response createOrUpdateItems( - @ApiParam(value = "array of item data", required = true) GroupItemDTO @Nullable [] items) { + @Parameter(description = "array of item data", required = true) GroupItemDTO @Nullable [] items) { // If we didn't get an item list bean, then return! if (items == null) { return Response.status(Status.BAD_REQUEST).build(); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/link/ItemChannelLinkResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/link/ItemChannelLinkResource.java index d23e5bf58..6d8ab6317 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/link/ItemChannelLinkResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/link/ItemChannelLinkResource.java @@ -50,13 +50,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for links. @@ -67,6 +68,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Yannick Schaus - Added filters to getAll * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = { RESTResource.class, ItemChannelLinkResource.class }) @JaxrsResource @@ -75,8 +77,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(ItemChannelLinkResource.PATH_LINKS) @RolesAllowed({ Role.ADMIN }) -@Api(value = ItemChannelLinkResource.PATH_LINKS, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = ItemChannelLinkResource.PATH_LINKS) @NonNullByDefault public class ItemChannelLinkResource implements RESTResource { @@ -92,12 +94,11 @@ public class ItemChannelLinkResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets all available links.", response = ItemChannelLinkDTO.class, responseContainer = "List") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = ItemChannelLinkDTO.class, responseContainer = "List") }) + @Operation(summary = "Gets all available links.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ItemChannelLinkDTO.class)))) }) public Response getAll( - @QueryParam("channelUID") @ApiParam(value = "filter by channel UID") @Nullable String channelUID, - @QueryParam("itemName") @ApiParam(value = "filter by item name") @Nullable String itemName) { + @QueryParam("channelUID") @Parameter(description = "filter by channel UID") @Nullable String channelUID, + @QueryParam("itemName") @Parameter(description = "filter by item name") @Nullable String itemName) { Stream linkStream = itemChannelLinkRegistry.getAll().stream().map(this::toBeans); if (channelUID != null) { @@ -112,11 +113,11 @@ public class ItemChannelLinkResource implements RESTResource { @GET @Path("/{itemName}/{channelUID}") - @ApiOperation(value = "Retrieves an individual link.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Content does not match the path") }) - public Response getLink(@PathParam("itemName") @ApiParam(value = "itemName") String itemName, - @PathParam("channelUID") @ApiParam(value = "channelUID") String channelUid) { + @Operation(summary = "Retrieves an individual link.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Content does not match the path") }) + public Response getLink(@PathParam("itemName") @Parameter(description = "itemName") String itemName, + @PathParam("channelUID") @Parameter(description = "channelUID") String channelUid) { List links = itemChannelLinkRegistry.getAll().stream() .filter(link -> channelUid.equals(link.getLinkedUID().getAsString())) .filter(link -> itemName.equals(link.getItemName())).map(this::toBeans).collect(Collectors.toList()); @@ -132,14 +133,14 @@ public class ItemChannelLinkResource implements RESTResource { @PUT @RolesAllowed({ Role.ADMIN }) @Path("/{itemName}/{channelUID}") - @ApiOperation(value = "Links item to a channel.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Content does not match the path"), - @ApiResponse(code = 405, message = "Link is not editable") }) - public Response link(@PathParam("itemName") @ApiParam(value = "itemName") String itemName, - @PathParam("channelUID") @ApiParam(value = "channelUID") String channelUid, - @ApiParam(value = "link data") @Nullable ItemChannelLinkDTO bean) { + @Operation(summary = "Links item to a channel.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Content does not match the path"), + @ApiResponse(responseCode = "405", description = "Link is not editable") }) + public Response link(@PathParam("itemName") @Parameter(description = "itemName") String itemName, + @PathParam("channelUID") @Parameter(description = "channelUID") String channelUid, + @Parameter(description = "link data") @Nullable ItemChannelLinkDTO bean) { ItemChannelLink link; if (bean == null) { link = new ItemChannelLink(itemName, new ChannelUID(channelUid), new Configuration()); @@ -166,13 +167,13 @@ public class ItemChannelLinkResource implements RESTResource { @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{itemName}/{channelUID}") - @ApiOperation(value = "Unlinks item from a channel.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Link not found."), - @ApiResponse(code = 405, message = "Link not editable.") }) - public Response unlink(@PathParam("itemName") @ApiParam(value = "itemName") String itemName, - @PathParam("channelUID") @ApiParam(value = "channelUID") String channelUid) { + @Operation(summary = "Unlinks item from a channel.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Link not found."), + @ApiResponse(responseCode = "405", description = "Link not editable.") }) + public Response unlink(@PathParam("itemName") @Parameter(description = "itemName") String itemName, + @PathParam("channelUID") @Parameter(description = "channelUID") String channelUid) { String linkId = AbstractLink.getIDFor(itemName, new ChannelUID(channelUid)); if (itemChannelLinkRegistry.get(linkId) == null) { String message = "Link " + linkId + " does not exist!"; diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java index 3571d2481..823a07649 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java @@ -71,13 +71,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for history data and provides different methods to interact with the persistence @@ -89,6 +90,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Erdoan Hadzhiyusein - Adapted the convertTime() method to work with the new DateTimeType * @author Lyubomir Papazov - Change java.util.Date references to be of type java.time.ZonedDateTime * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -96,7 +98,7 @@ import io.swagger.annotations.AuthorizationScope; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(PersistenceResource.PATH_PERSISTENCE) -@Api(PersistenceResource.PATH_PERSISTENCE) +@Tag(name = PersistenceResource.PATH_PERSISTENCE) @NonNullByDefault public class PersistenceResource implements RESTResource { @@ -129,12 +131,11 @@ public class PersistenceResource implements RESTResource { @GET @RolesAllowed({ Role.ADMIN }) @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Gets a list of persistence services.", response = String.class, responseContainer = "List", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = String.class, responseContainer = "List")) + @Operation(summary = "Gets a list of persistence services.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))) }) public Response httpGetPersistenceServices(@Context HttpHeaders headers, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { Locale locale = localeService.getLocale(language); Object responseObject = getPersistenceServiceList(locale); @@ -145,12 +146,11 @@ public class PersistenceResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/items") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Gets a list of items available via a specific persistence service.", response = String.class, responseContainer = "List", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = String.class, responseContainer = "List")) + @Operation(summary = "Gets a list of items available via a specific persistence service.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))) }) public Response httpGetPersistenceServiceItems(@Context HttpHeaders headers, - @ApiParam(value = "Id of the persistence service. If not provided the default service will be used") @QueryParam("serviceId") @Nullable String serviceId) { + @Parameter(description = "Id of the persistence service. If not provided the default service will be used") @QueryParam("serviceId") @Nullable String serviceId) { return getServiceItemList(serviceId); } @@ -158,20 +158,20 @@ public class PersistenceResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/items/{itemname: [a-zA-Z_0-9]+}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Gets item persistence data from the persistence service.", response = ItemHistoryDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ItemHistoryDTO.class), - @ApiResponse(code = 404, message = "Unknown Item or persistence service") }) + @Operation(summary = "Gets item persistence data from the persistence service.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ItemHistoryDTO.class))), + @ApiResponse(responseCode = "404", description = "Unknown Item or persistence service") }) public Response httpGetPersistenceItemData(@Context HttpHeaders headers, - @ApiParam(value = "Id of the persistence service. If not provided the default service will be used") @QueryParam("serviceId") @Nullable String serviceId, - @ApiParam(value = "The item name") @PathParam("itemname") String itemName, - @ApiParam(value = "Start time of the data to return. Will default to 1 day before endtime. [" + @Parameter(description = "Id of the persistence service. If not provided the default service will be used") @QueryParam("serviceId") @Nullable String serviceId, + @Parameter(description = "The item name") @PathParam("itemname") String itemName, + @Parameter(description = "Start time of the data to return. Will default to 1 day before endtime. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]") @QueryParam("starttime") @Nullable String startTime, - @ApiParam(value = "End time of the data to return. Will default to current time. [" + @Parameter(description = "End time of the data to return. Will default to current time. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]") @QueryParam("endtime") @Nullable String endTime, - @ApiParam(value = "Page number of data to return. This parameter will enable paging.") @QueryParam("page") int pageNumber, - @ApiParam(value = "The length of each page.") @QueryParam("pagelength") int pageLength, - @ApiParam(value = "Gets one value before and after the requested period.") @QueryParam("boundary") boolean boundary) { + @Parameter(description = "Page number of data to return. This parameter will enable paging.") @QueryParam("page") int pageNumber, + @Parameter(description = "The length of each page.") @QueryParam("pagelength") int pageLength, + @Parameter(description = "Gets one value before and after the requested period.") @QueryParam("boundary") boolean boundary) { return getItemHistoryDTO(serviceId, itemName, startTime, endTime, pageNumber, pageLength, boundary); } @@ -179,19 +179,17 @@ public class PersistenceResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/items/{itemname: [a-zA-Z_0-9]+}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Delete item data from a specific persistence service.", response = String.class, responseContainer = "List", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = String.class, responseContainer = "List"), - @ApiResponse(code = 400, message = "Invalid filter parameters"), - @ApiResponse(code = 404, message = "Unknown persistence service") }) + @Operation(summary = "Delete item data from a specific persistence service.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))), + @ApiResponse(responseCode = "400", description = "Invalid filter parameters"), + @ApiResponse(responseCode = "404", description = "Unknown persistence service") }) public Response httpDeletePersistenceServiceItem(@Context HttpHeaders headers, - @ApiParam(value = "Id of the persistence service.", required = true) @QueryParam("serviceId") String serviceId, - @ApiParam(value = "The item name.") @PathParam("itemname") String itemName, - @ApiParam(value = "Start time of the data to return. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + @Parameter(description = "Id of the persistence service.", required = true) @QueryParam("serviceId") String serviceId, + @Parameter(description = "The item name.") @PathParam("itemname") String itemName, + @Parameter(description = "Start time of the data to return. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]", required = true) @QueryParam("starttime") String startTime, - @ApiParam(value = "End time of the data to return. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + @Parameter(description = "End time of the data to return. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]", required = true) @QueryParam("endtime") String endTime) { return deletePersistenceItemData(serviceId, itemName, startTime, endTime); } @@ -200,15 +198,15 @@ public class PersistenceResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/items/{itemname: [a-zA-Z_0-9]+}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Stores item persistence data into the persistence service.", response = ItemHistoryDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ItemHistoryDTO.class), - @ApiResponse(code = 404, message = "Unknown Item or persistence service") }) + @Operation(summary = "Stores item persistence data into the persistence service.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ItemHistoryDTO.class))), + @ApiResponse(responseCode = "404", description = "Unknown Item or persistence service") }) public Response httpPutPersistenceItemData(@Context HttpHeaders headers, - @ApiParam(value = "Id of the persistence service. If not provided the default service will be used") @QueryParam("serviceId") @Nullable String serviceId, - @ApiParam(value = "The item name.") @PathParam("itemname") String itemName, - @ApiParam(value = "Time of the data to be stored. Will default to current time. [" + @Parameter(description = "Id of the persistence service. If not provided the default service will be used") @QueryParam("serviceId") @Nullable String serviceId, + @Parameter(description = "The item name.") @PathParam("itemname") String itemName, + @Parameter(description = "Time of the data to be stored. Will default to current time. [" + DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]", required = true) @QueryParam("time") String time, - @ApiParam(value = "The state to store.", required = true) @QueryParam("state") String value) { + @Parameter(description = "The state to store.", required = true) @QueryParam("state") String value) { return putItemState(serviceId, itemName, value, time); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java index 7c699b69b..30ff0911e 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/profile/ProfileTypeResource.java @@ -53,19 +53,21 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * REST resource to obtain profile-types * * @author Stefan Triller - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -74,8 +76,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(ProfileTypeResource.PATH_PROFILE_TYPES) @RolesAllowed({ Role.ADMIN }) -@Api(value = ProfileTypeResource.PATH_PROFILE_TYPES, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = ProfileTypeResource.PATH_PROFILE_TYPES) @NonNullByDefault public class ProfileTypeResource implements RESTResource { @@ -99,12 +101,12 @@ public class ProfileTypeResource implements RESTResource { @GET @RolesAllowed({ Role.USER }) @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets all available profile types.", response = ProfileTypeDTO.class, responseContainer = "Set") - @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = ProfileTypeDTO.class, responseContainer = "Set")) + @Operation(summary = "Gets all available profile types.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ProfileTypeDTO.class), uniqueItems = true))) }) public Response getAll( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @QueryParam("channelTypeUID") @ApiParam(value = "channel type filter") @Nullable String channelTypeUID, - @QueryParam("itemType") @ApiParam(value = "item type filter") @Nullable String itemType) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @QueryParam("channelTypeUID") @Parameter(description = "channel type filter") @Nullable String channelTypeUID, + @QueryParam("itemType") @Parameter(description = "item type filter") @Nullable String itemType) { Locale locale = localeService.getLocale(language); return Response.ok(new Stream2JSONInputStream(getProfileTypes(locale, channelTypeUID, itemType))).build(); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java index 165c271ea..ca208913b 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/service/ConfigurableServiceResource.java @@ -65,13 +65,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * {@link ConfigurableServiceResource} provides access to configurable services. It lists the available services and @@ -80,6 +81,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Dennis Nobel - Initial contribution * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = { RESTResource.class, ConfigurableServiceResource.class }) @JaxrsResource @@ -88,8 +90,8 @@ import io.swagger.annotations.AuthorizationScope; @JSONRequired @Path(ConfigurableServiceResource.PATH_SERVICES) @RolesAllowed({ Role.ADMIN }) -@Api(value = ConfigurableServiceResource.PATH_SERVICES, authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) +@SecurityRequirement(name = "oauth2", scopes = { "admin" }) +@Tag(name = ConfigurableServiceResource.PATH_SERVICES) @NonNullByDefault public class ConfigurableServiceResource implements RESTResource { @@ -123,9 +125,8 @@ public class ConfigurableServiceResource implements RESTResource { @GET @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get all configurable services.") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = ConfigurableServiceDTO.class, responseContainer = "List") }) + @Operation(summary = "Get all configurable services.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConfigurableServiceDTO.class)))) }) public List getAll() { List services = getConfigurableServices(); return services; @@ -134,10 +135,10 @@ public class ConfigurableServiceResource implements RESTResource { @GET @Path("/{serviceId}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get configurable service for given service ID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ConfigurableServiceDTO.class), - @ApiResponse(code = 404, message = "Not found") }) - public Response getById(@PathParam("serviceId") @ApiParam(value = "service ID") String serviceId) { + @Operation(summary = "Get configurable service for given service ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ConfigurableServiceDTO.class))), + @ApiResponse(responseCode = "404", description = "Not found") }) + public Response getById(@PathParam("serviceId") @Parameter(description = "service ID") String serviceId) { ConfigurableServiceDTO configurableService = getServiceById(serviceId); if (configurableService != null) { return Response.ok(configurableService).build(); @@ -174,11 +175,10 @@ public class ConfigurableServiceResource implements RESTResource { @GET @Path("/{serviceId}/contexts") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get existing multiple context service configurations for the given factory PID.") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = ConfigurableServiceDTO.class, responseContainer = "List") }) + @Operation(summary = "Get existing multiple context service configurations for the given factory PID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ConfigurableServiceDTO.class)))) }) public List getMultiConfigServicesByFactoryPid( - @PathParam("serviceId") @ApiParam(value = "service ID") String serviceId) { + @PathParam("serviceId") @Parameter(description = "service ID") String serviceId) { List services = collectServicesById(serviceId); return services; } @@ -191,10 +191,10 @@ public class ConfigurableServiceResource implements RESTResource { @GET @Path("/{serviceId}/config") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get service configuration for given service ID.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 500, message = "Configuration can not be read due to internal error") }) - public Response getConfiguration(@PathParam("serviceId") @ApiParam(value = "service ID") String serviceId) { + @Operation(summary = "Get service configuration for given service ID.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "500", description = "Configuration can not be read due to internal error") }) + public Response getConfiguration(@PathParam("serviceId") @Parameter(description = "service ID") String serviceId) { try { Configuration configuration = configurationService.get(serviceId); return configuration != null ? Response.ok(configuration.getProperties()).build() @@ -209,11 +209,11 @@ public class ConfigurableServiceResource implements RESTResource { @Path("/{serviceId}/config") @Consumes(MediaType.APPLICATION_JSON) @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Updates a service configuration for given service ID and returns the old configuration.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 204, message = "No old configuration"), - @ApiResponse(code = 500, message = "Configuration can not be updated due to internal error") }) - public Response updateConfiguration(@PathParam("serviceId") @ApiParam(value = "service ID") String serviceId, + @Operation(summary = "Updates a service configuration for given service ID and returns the old configuration.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "204", description = "No old configuration"), + @ApiResponse(responseCode = "500", description = "Configuration can not be updated due to internal error") }) + public Response updateConfiguration(@PathParam("serviceId") @Parameter(description = "service ID") String serviceId, @Nullable Map configuration) { try { Configuration oldConfiguration = configurationService.get(serviceId); @@ -256,11 +256,12 @@ public class ConfigurableServiceResource implements RESTResource { @DELETE @Path("/{serviceId}/config") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Deletes a service configuration for given service ID and returns the old configuration.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 204, message = "No old configuration"), - @ApiResponse(code = 500, message = "Configuration can not be deleted due to internal error") }) - public Response deleteConfiguration(@PathParam("serviceId") @ApiParam(value = "service ID") String serviceId) { + @Operation(summary = "Deletes a service configuration for given service ID and returns the old configuration.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "204", description = "No old configuration"), + @ApiResponse(responseCode = "500", description = "Configuration can not be deleted due to internal error") }) + public Response deleteConfiguration( + @PathParam("serviceId") @Parameter(description = "service ID") String serviceId) { try { Configuration oldConfiguration = configurationService.get(serviceId); configurationService.delete(serviceId); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java index fd28d3ff4..4b364a59f 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java @@ -78,7 +78,6 @@ import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.firmware.Firmware; import org.openhab.core.thing.dto.ChannelDTO; import org.openhab.core.thing.dto.ChannelDTOMapper; -import org.openhab.core.thing.dto.StrippedThingTypeDTO; import org.openhab.core.thing.dto.ThingDTO; import org.openhab.core.thing.dto.ThingDTOMapper; import org.openhab.core.thing.firmware.FirmwareRegistry; @@ -107,13 +106,14 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for things and is registered with the @@ -131,6 +131,7 @@ import io.swagger.annotations.AuthorizationScope; * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Dimitar Ivanov - replaced Firmware UID with thing UID and firmware version * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -138,7 +139,7 @@ import io.swagger.annotations.AuthorizationScope; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(ThingResource.PATH_THINGS) -@Api(ThingResource.PATH_THINGS) +@Tag(name = ThingResource.PATH_THINGS) @NonNullByDefault public class ThingResource implements RESTResource { @@ -211,15 +212,14 @@ public class ThingResource implements RESTResource { @POST @RolesAllowed({ Role.ADMIN }) @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Creates a new thing and adds it to the registry.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 201, message = "Created", response = String.class), - @ApiResponse(code = 400, message = "A uid must be provided, if no binding can create a thing of this type."), - @ApiResponse(code = 409, message = "A thing with the same uid already exists.") }) + @Operation(summary = "Creates a new thing and adds it to the registry.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "201", description = "Created", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "A uid must be provided, if no binding can create a thing of this type."), + @ApiResponse(responseCode = "409", description = "A thing with the same uid already exists.") }) public Response create( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @ApiParam(value = "thing data", required = true) ThingDTO thingBean) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @Parameter(description = "thing data", required = true) ThingDTO thingBean) { final Locale locale = localeService.getLocale(language); ThingUID thingUID = thingBean.UID == null ? null : new ThingUID(thingBean.UID); @@ -285,11 +285,10 @@ public class ThingResource implements RESTResource { @GET @RolesAllowed({ Role.USER, Role.ADMIN }) @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all available things.", response = EnrichedThingDTO.class, responseContainer = "Set") - @ApiResponses(value = { - @ApiResponse(code = 200, message = "OK", response = EnrichedThingDTO.class, responseContainer = "Set") }) + @Operation(summary = "Get all available things.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedThingDTO.class), uniqueItems = true))) }) public Response getAll( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); Stream thingStream = thingRegistry.stream().map(t -> convertToEnrichedThingDTO(t, locale)) @@ -301,13 +300,13 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets thing by UID.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ThingDTO.class), - @ApiResponse(code = 404, message = "Thing not found.") }) + @Operation(summary = "Gets thing by UID.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ThingDTO.class))), + @ApiResponse(responseCode = "404", description = "Thing not found.") }) public Response getByUID( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thingUID") String thingUID) { final Locale locale = localeService.getLocale(language); Thing thing = thingRegistry.get((new ThingUID(thingUID))); @@ -332,17 +331,16 @@ public class ThingResource implements RESTResource { @DELETE @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}") - @ApiOperation(value = "Removes a thing from the registry. Set \'force\' to __true__ if you want the thing te be removed immediately.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK, was deleted."), - @ApiResponse(code = 202, message = "ACCEPTED for asynchronous deletion."), - @ApiResponse(code = 404, message = "Thing not found."), - @ApiResponse(code = 409, message = "Thing could not be deleted because it's not editable.") }) + @Operation(summary = "Removes a thing from the registry. Set \'force\' to __true__ if you want the thing te be removed immediately.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK, was deleted."), + @ApiResponse(responseCode = "202", description = "ACCEPTED for asynchronous deletion."), + @ApiResponse(responseCode = "404", description = "Thing not found."), + @ApiResponse(responseCode = "409", description = "Thing could not be deleted because it's not editable.") }) public Response remove( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, - @DefaultValue("false") @QueryParam("force") @ApiParam(value = "force") boolean force) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thingUID") String thingUID, + @DefaultValue("false") @QueryParam("force") @Parameter(description = "force") boolean force) { final Locale locale = localeService.getLocale(language); ThingUID thingUIDObject = new ThingUID(thingUID); @@ -392,15 +390,15 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Updates a thing.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ThingDTO.class), - @ApiResponse(code = 404, message = "Thing not found."), - @ApiResponse(code = 409, message = "Thing could not be updated as it is not editable.") }) + @Operation(summary = "Updates a thing.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ThingDTO.class))), + @ApiResponse(responseCode = "404", description = "Thing not found."), + @ApiResponse(responseCode = "409", description = "Thing could not be updated as it is not editable.") }) public Response update( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, - @ApiParam(value = "thing", required = true) ThingDTO thingBean) throws IOException { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thingUID") String thingUID, + @Parameter(description = "thing", required = true) ThingDTO thingBean) throws IOException { final Locale locale = localeService.getLocale(language); ThingUID thingUIDObject = new ThingUID(thingUID); @@ -452,17 +450,16 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/config") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Updates thing's configuration.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = ThingDTO.class), - @ApiResponse(code = 400, message = "Configuration of the thing is not valid."), - @ApiResponse(code = 404, message = "Thing not found"), - @ApiResponse(code = 409, message = "Thing could not be updated as it is not editable.") }) + @Operation(summary = "Updates thing's configuration.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ThingDTO.class))), + @ApiResponse(responseCode = "400", description = "Configuration of the thing is not valid."), + @ApiResponse(responseCode = "404", description = "Thing not found"), + @ApiResponse(responseCode = "409", description = "Thing could not be updated as it is not editable.") }) public Response updateConfiguration( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thing") String thingUID, - @ApiParam(value = "configuration parameters") @Nullable Map configurationParameters) + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thing") String thingUID, + @Parameter(description = "configuration parameters") @Nullable Map configurationParameters) throws IOException { final Locale locale = localeService.getLocale(language); @@ -510,13 +507,13 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.USER, Role.ADMIN }) @Path("/{thingUID}/status") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets thing status.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Thing not found.") }) + @Operation(summary = "Gets thing status.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Thing not found.") }) public Response getStatus( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thing") String thingUID) throws IOException { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thing") String thingUID) throws IOException { ThingUID thingUIDObject = new ThingUID(thingUID); // Check if the Thing exists, 404 if not @@ -535,15 +532,14 @@ public class ThingResource implements RESTResource { @PUT @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/enable") - @ApiOperation(value = "Sets the thing enabled status.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Thing not found.") }) + @Operation(summary = "Sets the thing enabled status.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Thing not found.") }) public Response setEnabled( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thing") String thingUID, - @ApiParam(value = "enabled") String enabled) throws IOException { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thing") String thingUID, + @Parameter(description = "enabled") String enabled) throws IOException { final Locale locale = localeService.getLocale(language); ThingUID thingUIDObject = new ThingUID(thingUID); @@ -566,13 +562,13 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/config/status") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets thing config status.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class), - @ApiResponse(code = 404, message = "Thing not found.") }) + @Operation(summary = "Gets thing config status.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "404", description = "Thing not found.") }) public Response getConfigStatus( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") String language, - @PathParam("thingUID") @ApiParam(value = "thing") String thingUID) throws IOException { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") String language, + @PathParam("thingUID") @Parameter(description = "thing") String thingUID) throws IOException { ThingUID thingUIDObject = new ThingUID(thingUID); // Check if the Thing exists, 404 if not @@ -594,15 +590,16 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/firmware/{firmwareVersion}") @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Update thing firmware.", authorizations = { @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Firmware update preconditions not satisfied."), - @ApiResponse(code = 404, message = "Thing not found.") }) + @Operation(summary = "Update thing firmware.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Firmware update preconditions not satisfied."), + @ApiResponse(responseCode = "404", description = "Thing not found.") }) public Response updateFirmware( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thing") String thingUID, - @PathParam("firmwareVersion") @ApiParam(value = "version") String firmwareVersion) throws IOException { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thing") String thingUID, + @PathParam("firmwareVersion") @Parameter(description = "version") String firmwareVersion) + throws IOException { Thing thing = thingRegistry.get(new ThingUID(thingUID)); if (thing == null) { logger.info("Received HTTP PUT request for firmware update at '{}' for the unknown thing '{}'.", @@ -630,14 +627,13 @@ public class ThingResource implements RESTResource { @GET @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/firmware/status") - @ApiOperation(value = "Gets thing's firmware status.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 204, message = "No firmware status provided by this Thing.") }) + @Operation(summary = "Gets thing's firmware status.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "204", description = "No firmware status provided by this Thing.") }) public Response getFirmwareStatus( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("thingUID") @ApiParam(value = "thing") String thingUID) throws IOException { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("thingUID") @Parameter(description = "thing") String thingUID) throws IOException { ThingUID thingUIDObject = new ThingUID(thingUID); FirmwareStatusDTO firmwareStatusDto = getThingFirmwareStatusInfo(thingUIDObject); if (firmwareStatusDto == null) { @@ -651,13 +647,12 @@ public class ThingResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/{thingUID}/firmwares") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all available firmwares for provided thing UID", response = StrippedThingTypeDTO.class, responseContainer = "Set", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 204, message = "No firmwares found.") }) - public Response getFirmwares(@PathParam("thingUID") @ApiParam(value = "thingUID") String thingUID, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @Operation(summary = "Get all available firmwares for provided thing UID", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = FirmwareDTO.class), uniqueItems = true))), + @ApiResponse(responseCode = "204", description = "No firmwares found.") }) + public Response getFirmwares(@PathParam("thingUID") @Parameter(description = "thingUID") String thingUID, + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { ThingUID aThingUID = new ThingUID(thingUID); Thing thing = thingRegistry.get(aThingUID); if (thing == null) { diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingTypeResource.java index 64ae7cd3c..85c114793 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingTypeResource.java @@ -68,11 +68,13 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * ThingTypeResource provides access to ThingType via REST. @@ -87,6 +89,7 @@ import io.swagger.annotations.ApiResponses; * @author Franck Dechavanne - Added DTOs to ApiResponses * @author Yannick Schaus - Added filter to getAll * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -94,7 +97,7 @@ import io.swagger.annotations.ApiResponses; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(ThingTypeResource.PATH_THING_TYPES) -@Api(ThingTypeResource.PATH_THING_TYPES) +@Tag(name = ThingTypeResource.PATH_THING_TYPES) @NonNullByDefault public class ThingTypeResource implements RESTResource { @@ -126,11 +129,11 @@ public class ThingTypeResource implements RESTResource { @GET @RolesAllowed({ Role.USER }) @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets all available thing types without config description, channels and properties.", response = StrippedThingTypeDTO.class, responseContainer = "Set") - @ApiResponses(value = @ApiResponse(code = 200, message = "OK", response = StrippedThingTypeDTO.class, responseContainer = "Set")) + @Operation(summary = "Gets all available thing types without config description, channels and properties.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = StrippedThingTypeDTO.class), uniqueItems = true))) }) public Response getAll( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @QueryParam("bindingId") @ApiParam(value = "filter by binding Id") @Nullable String bindingId) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @QueryParam("bindingId") @Parameter(description = "filter by binding Id") @Nullable String bindingId) { Locale locale = localeService.getLocale(language); Stream typeStream = thingTypeRegistry.getThingTypes(locale).stream() .map(thingType -> StrippedThingTypeDTOMapper.map(thingType, locale)); @@ -146,12 +149,11 @@ public class ThingTypeResource implements RESTResource { @RolesAllowed({ Role.USER }) @Path("/{thingTypeUID}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets thing type by UID.", response = ThingTypeDTO.class) - @ApiResponses(value = { - @ApiResponse(code = 200, message = "Thing type with provided thingTypeUID does not exist.", response = ThingTypeDTO.class), - @ApiResponse(code = 404, message = "No content") }) - public Response getByUID(@PathParam("thingTypeUID") @ApiParam(value = "thingTypeUID") String thingTypeUID, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @Operation(summary = "Gets thing type by UID.", responses = { + @ApiResponse(responseCode = "200", description = "Thing type with provided thingTypeUID does not exist.", content = @Content(schema = @Schema(implementation = ThingTypeDTO.class))), + @ApiResponse(responseCode = "404", description = "No content") }) + public Response getByUID(@PathParam("thingTypeUID") @Parameter(description = "thingTypeUID") String thingTypeUID, + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { Locale locale = localeService.getLocale(language); ThingType thingType = thingTypeRegistry.getThingType(new ThingTypeUID(thingTypeUID), locale); if (thingType != null) { diff --git a/bundles/org.openhab.core.io.rest.log/src/main/java/org/openhab/core/io/rest/log/internal/LogHandler.java b/bundles/org.openhab.core.io.rest.log/src/main/java/org/openhab/core/io/rest/log/internal/LogHandler.java index e18ac3441..2714f26c3 100644 --- a/bundles/org.openhab.core.io.rest.log/src/main/java/org/openhab/core/io/rest/log/internal/LogHandler.java +++ b/bundles/org.openhab.core.io.rest.log/src/main/java/org/openhab/core/io/rest/log/internal/LogHandler.java @@ -45,16 +45,17 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * * @author Sebastian Janzen - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -62,8 +63,8 @@ import io.swagger.annotations.ApiResponses; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(LogHandler.PATH_LOG) -@Api(LogHandler.PATH_LOG) @Produces(MediaType.APPLICATION_JSON) +@Tag(name = LogHandler.PATH_LOG) @NonNullByDefault public class LogHandler implements RESTResource { @@ -90,17 +91,19 @@ public class LogHandler implements RESTResource { @GET @Path("/levels") - @ApiOperation(value = "Get log severities, which are logged by the current logger settings.", code = 200, notes = "This depends on the current log settings at the backend.") + @Operation(summary = "Get log severities, which are logged by the current logger settings.", responses = { + @ApiResponse(responseCode = "200", description = "This depends on the current log settings at the backend.") }) public Response getLogLevels() { return Response.ok(createLogLevelsMap()).build(); } @GET - @ApiOperation(value = "Returns the last logged frontend messages. The amount is limited to the " + @Operation(summary = "Returns the last logged frontend messages. The amount is limited to the " + LogConstants.LOG_BUFFER_LIMIT + " last entries.") - @ApiParam(name = "limit", allowableValues = "range[1, " + LogConstants.LOG_BUFFER_LIMIT + "]") - public Response getLastLogs( - @DefaultValue(LogConstants.LOG_BUFFER_LIMIT + "") @QueryParam("limit") @Nullable Integer limit) { + + public Response getLastLogs(@DefaultValue(LogConstants.LOG_BUFFER_LIMIT + + "") @QueryParam("limit") @Parameter(name = "limit", schema = @Schema(implementation = Integer.class, minimum = "1", maximum = "" + + LogConstants.LOG_BUFFER_LIMIT)) @Nullable Integer limit) { if (logBuffer.isEmpty()) { return Response.ok("[]").build(); } @@ -127,11 +130,11 @@ public class LogHandler implements RESTResource { @POST @Consumes(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Log a frontend log message to the backend.") - @ApiParam(name = "logMessage", value = "Severity is required and can be one of error, warn, info or debug, depending on activated severities which you can GET at /logLevels.", example = "{\"severity\": \"error\", \"url\": \"http://example.org\", \"message\": \"Error message\"}") - @ApiResponses({ @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 403, message = LogConstants.LOG_SEVERITY_IS_NOT_SUPPORTED) }) - public Response log(final @Nullable LogMessage logMessage) { + @Operation(summary = "Log a frontend log message to the backend.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "403", description = LogConstants.LOG_SEVERITY_IS_NOT_SUPPORTED) }) + public Response log( + final @Parameter(name = "logMessage", description = "Severity is required and can be one of error, warn, info or debug, depending on activated severities which you can GET at /logLevels.", example = "{\"severity\": \"error\", \"url\": \"http://example.org\", \"message\": \"Error message\"}") @Nullable LogMessage logMessage) { if (logMessage == null) { logger.debug("Received null log message model!"); return Response.status(500) diff --git a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java index 187650e1b..11e4903b1 100644 --- a/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java +++ b/bundles/org.openhab.core.io.rest.sitemap/src/main/java/org/openhab/core/io/rest/sitemap/internal/SitemapResource.java @@ -105,11 +105,13 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.MapMaker; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** *

@@ -120,6 +122,7 @@ import io.swagger.annotations.ApiResponses; * @author Chris Jackson - Initial contribution * @author Yordan Zhelev - Added Swagger annotations * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = RESTResource.class) @JaxrsResource @@ -128,7 +131,7 @@ import io.swagger.annotations.ApiResponses; @JSONRequired @Path(SitemapResource.PATH_SITEMAPS) @RolesAllowed({ Role.USER, Role.ADMIN }) -@Api(SitemapResource.PATH_SITEMAPS) +@Tag(name = SitemapResource.PATH_SITEMAPS) @NonNullByDefault public class SitemapResource implements RESTResource, SitemapSubscriptionCallback, SseBroadcaster.Listener { @@ -217,8 +220,8 @@ public class SitemapResource @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get all available sitemaps.", response = SitemapDTO.class, responseContainer = "Collection") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get all available sitemaps.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = SitemapDTO.class)))) }) public Response getSitemaps() { logger.debug("Received HTTP GET request from IP {} at '{}'", request.getRemoteAddr(), uriInfo.getPath()); Object responseObject = getSitemapBeans(uriInfo.getAbsolutePathBuilder().build()); @@ -228,13 +231,13 @@ public class SitemapResource @GET @Path("/{sitemapname: [a-zA-Z_0-9]+}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get sitemap by name.", response = SitemapDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get sitemap by name.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SitemapDTO.class))) }) public Response getSitemapData(@Context HttpHeaders headers, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("sitemapname") @ApiParam(value = "sitemap name") String sitemapname, + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("sitemapname") @Parameter(description = "sitemap name") String sitemapname, @QueryParam("type") String type, @QueryParam("jsoncallback") @DefaultValue("callback") String callback, - @QueryParam("includeHidden") @ApiParam(value = "include hidden widgets") boolean includeHiddenWidgets) { + @QueryParam("includeHidden") @Parameter(description = "include hidden widgets") boolean includeHiddenWidgets) { final Locale locale = localeService.getLocale(language); logger.debug("Received HTTP GET request from IP {} at '{}' for media type '{}'.", request.getRemoteAddr(), uriInfo.getPath(), type); @@ -246,16 +249,16 @@ public class SitemapResource @GET @Path("/{sitemapname: [a-zA-Z_0-9]+}/{pageid: [a-zA-Z_0-9]+}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Polls the data for a sitemap.", response = PageDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Sitemap with requested name does not exist or page does not exist, or page refers to a non-linkable widget"), - @ApiResponse(code = 400, message = "Invalid subscription id has been provided.") }) + @Operation(summary = "Polls the data for a sitemap.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = PageDTO.class))), + @ApiResponse(responseCode = "404", description = "Sitemap with requested name does not exist or page does not exist, or page refers to a non-linkable widget"), + @ApiResponse(responseCode = "400", description = "Invalid subscription id has been provided.") }) public Response getPageData(@Context HttpHeaders headers, - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("sitemapname") @ApiParam(value = "sitemap name") String sitemapname, - @PathParam("pageid") @ApiParam(value = "page id") String pageId, - @QueryParam("subscriptionid") @ApiParam(value = "subscriptionid") @Nullable String subscriptionId, - @QueryParam("includeHidden") @ApiParam(value = "include hidden widgets") boolean includeHiddenWidgets) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("sitemapname") @Parameter(description = "sitemap name") String sitemapname, + @PathParam("pageid") @Parameter(description = "page id") String pageId, + @QueryParam("subscriptionid") @Parameter(description = "subscriptionid") @Nullable String subscriptionId, + @QueryParam("includeHidden") @Parameter(description = "include hidden widgets") boolean includeHiddenWidgets) { final Locale locale = localeService.getLocale(language); logger.debug("Received HTTP GET request from IP {} at '{}'", request.getRemoteAddr(), uriInfo.getPath()); @@ -289,9 +292,9 @@ public class SitemapResource @POST @Path(SEGMENT_EVENTS + "/subscribe") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Creates a sitemap event subscription.") - @ApiResponses(value = { @ApiResponse(code = 201, message = "Subscription created."), - @ApiResponse(code = 503, message = "Subscriptions limit reached.") }) + @Operation(summary = "Creates a sitemap event subscription.", responses = { + @ApiResponse(responseCode = "201", description = "Subscription created."), + @ApiResponse(responseCode = "503", description = "Subscriptions limit reached.") }) public Object createEventSubscription() { String subscriptionId = subscriptions.createSubscription(this); if (subscriptionId == null) { @@ -318,14 +321,13 @@ public class SitemapResource @GET @Path(SEGMENT_EVENTS + "/{subscriptionid: [a-zA-Z_0-9-]+}") @Produces(MediaType.SERVER_SENT_EVENTS) - @ApiOperation(value = "Get sitemap events.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Page not linked to the subscription."), - @ApiResponse(code = 404, message = "Subscription not found.") }) + @Operation(summary = "Get sitemap events.", responses = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Page not linked to the subscription."), + @ApiResponse(responseCode = "404", description = "Subscription not found.") }) public void getSitemapEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response, - @PathParam("subscriptionid") @ApiParam(value = "subscription id") String subscriptionId, - @QueryParam("sitemap") @ApiParam(value = "sitemap name") @Nullable String sitemapname, - @QueryParam("pageid") @ApiParam(value = "page id") @Nullable String pageId) { + @PathParam("subscriptionid") @Parameter(description = "subscription id") String subscriptionId, + @QueryParam("sitemap") @Parameter(description = "sitemap name") @Nullable String sitemapname, + @QueryParam("pageid") @Parameter(description = "page id") @Nullable String pageId) { final SseSinkInfo sinkInfo = knownSubscriptions.get(subscriptionId); if (sinkInfo == null) { logger.debug("Subscription id {} does not exist.", subscriptionId); diff --git a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java index ee8994efb..6ac0f243f 100644 --- a/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java +++ b/bundles/org.openhab.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java @@ -43,6 +43,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.auth.Role; import org.openhab.core.events.Event; import org.openhab.core.io.rest.RESTConstants; +import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.SseBroadcaster; import org.openhab.core.io.rest.sse.internal.SseItemStatesEventBuilder; import org.openhab.core.io.rest.sse.internal.SsePublisher; @@ -63,10 +64,10 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * SSE Resource for pushing events to currently listening clients. @@ -76,17 +77,19 @@ import io.swagger.annotations.ApiResponses; * @author Yannick Schaus - Add endpoints to track item state updates * @author Markus Rathgeb - Drop Glassfish dependency and use API only * @author Wouter Born - Rework SSE item state sinks for dropping Glassfish + * @author Wouter Born - Migrated to OpenAPI annotations */ -@Component(service = SsePublisher.class) +@Component(service = { RESTResource.class, SsePublisher.class }) @JaxrsResource @JaxrsName(SseResource.PATH_EVENTS) @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(SseResource.PATH_EVENTS) @RolesAllowed({ Role.USER }) +@Tag(name = SseResource.PATH_EVENTS) @Singleton @NonNullByDefault -public class SseResource implements SsePublisher { +public class SseResource implements RESTResource, SsePublisher { // The URI path to this resource public static final String PATH_EVENTS = "events"; @@ -149,11 +152,10 @@ public class SseResource implements SsePublisher { @GET @Produces(MediaType.SERVER_SENT_EVENTS) - @ApiOperation(value = "Get all events.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 400, message = "Topic is empty or contains invalid characters") }) + @Operation(summary = "Get all events.", responses = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Topic is empty or contains invalid characters") }) public void listen(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response, - @QueryParam("topics") @ApiParam(value = "topics") String eventFilter) { + @QueryParam("topics") @Parameter(description = "topics") String eventFilter) { if (!SseUtil.isValidTopicFilter(eventFilter)) { response.setStatus(Status.BAD_REQUEST.getStatusCode()); return; @@ -180,8 +182,8 @@ public class SseResource implements SsePublisher { @GET @Path("/states") @Produces(MediaType.SERVER_SENT_EVENTS) - @ApiOperation(value = "Initiates a new item state tracker connection") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Initiates a new item state tracker connection", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) public void getStateEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response) { final SseSinkItemInfo sinkItemInfo = new SseSinkItemInfo(); itemStatesBroadcaster.add(sseEventSink, sinkItemInfo); @@ -201,11 +203,11 @@ public class SseResource implements SsePublisher { */ @POST @Path("/states/{connectionId}") - @ApiOperation(value = "Changes the list of items a SSE connection will receive state updates to.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Unknown connectionId") }) + @Operation(summary = "Changes the list of items a SSE connection will receive state updates to.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Unknown connectionId") }) public Object updateTrackedItems(@PathParam("connectionId") String connectionId, - @ApiParam("items") Set itemNames) { + @Parameter(description = "items") Set itemNames) { Optional itemStateInfo = itemStatesBroadcaster.getInfoIf(hasConnectionId(connectionId)) .findFirst(); if (!itemStateInfo.isPresent()) { diff --git a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/AuthSwaggerReaderListener.java b/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/AuthSwaggerReaderListener.java deleted file mode 100644 index 3819847b2..000000000 --- a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/AuthSwaggerReaderListener.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.io.rest.swagger.impl; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.swagger.jaxrs.Reader; -import io.swagger.jaxrs.config.ReaderListener; -import io.swagger.models.Swagger; -import io.swagger.models.auth.OAuth2Definition; -import io.swagger.models.auth.SecuritySchemeDefinition; - -/** - * This class adds a security definition to the Swagger object for the OAuth2 flow. - * - * @author Yannick Schaus - initial contribution - */ -@Component -@NonNullByDefault -public class AuthSwaggerReaderListener implements ReaderListener { - public static final String OAUTH_AUTHORIZE_ENDPOINT = "/auth/authorize"; - public static final String OAUTH_TOKEN_ENDPOINT = "/rest/auth/token"; - - private final Logger logger = LoggerFactory.getLogger(AuthSwaggerReaderListener.class); - - @Override - public void beforeScan(@NonNullByDefault({}) Reader reader, @NonNullByDefault({}) Swagger swagger) { - logger.debug("Adding securityDefinition to Swagger"); - SecuritySchemeDefinition oauth2Definition = new OAuth2Definition() - .accessCode(OAUTH_AUTHORIZE_ENDPOINT, OAUTH_TOKEN_ENDPOINT).scope("admin", "Administration operations"); - Map securityDefinitions = new HashMap<>(); - securityDefinitions.put("oauth2", oauth2Definition); - swagger.setSecurityDefinitions(securityDefinitions); - } - - @Override - public void afterScan(@NonNullByDefault({}) Reader reader, @NonNullByDefault({}) Swagger swagger) { - } -} diff --git a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/InfoSwaggerReaderListener.java b/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/InfoSwaggerReaderListener.java deleted file mode 100644 index dba064209..000000000 --- a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/InfoSwaggerReaderListener.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.io.rest.swagger.impl; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.io.rest.RESTConstants; -import org.osgi.service.component.annotations.Component; - -import io.swagger.jaxrs.Reader; -import io.swagger.jaxrs.config.ReaderListener; -import io.swagger.models.Contact; -import io.swagger.models.Info; -import io.swagger.models.Swagger; - -/** - * This class adds information about the REST API to the Swagger object. - * - * @author Yannick Schaus - initial contribution - */ -@Component -@NonNullByDefault -public class InfoSwaggerReaderListener implements ReaderListener { - public static final String API_TITLE = "openHAB REST API"; - public static final String CONTACT_NAME = "openHAB"; - public static final String CONTACT_URL = "https://www.openhab.org/docs/"; - - @Override - public void beforeScan(@NonNullByDefault({}) Reader reader, @NonNullByDefault({}) Swagger swagger) { - Info info = new Info(); - info.setTitle(API_TITLE); - info.setVersion(RESTConstants.API_VERSION); - Contact contact = new Contact(); - contact.setName(CONTACT_NAME); - contact.setUrl(CONTACT_URL); - info.setContact(contact); - swagger.setInfo(info); - } - - @Override - public void afterScan(@NonNullByDefault({}) Reader reader, @NonNullByDefault({}) Swagger swagger) { - } -} diff --git a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/OpenApiResource.java b/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/OpenApiResource.java new file mode 100644 index 000000000..ecdf5be5c --- /dev/null +++ b/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/OpenApiResource.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.swagger.impl; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.io.rest.RESTConstants; +import org.openhab.core.io.rest.RESTResource; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; + +import io.swagger.v3.core.util.Json; +import io.swagger.v3.jaxrs2.Reader; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.OAuthFlow; +import io.swagger.v3.oas.models.security.OAuthFlows; +import io.swagger.v3.oas.models.security.Scopes; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; + +/** + * An endpoint to generate and provide a OpenAPI description. + * + * @author Markus Rathgeb - Initial contribution + * @author Kai Kreuzer - made it a RESTResource to register in the root bean + * @author Yannick Schaus - add support for ReaderListeners, remove dependency + * @author Wouter Born - Migrated to OpenAPI + */ +@Component(service = OpenApiResource.class) +@JaxrsResource +@JaxrsName("spec") +@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") +@JSONRequired +@Path("/spec") +@NonNullByDefault +public class OpenApiResource implements RESTResource { + + public static final String API_TITLE = "openHAB REST API"; + public static final String CONTACT_NAME = "openHAB"; + public static final String CONTACT_URL = "https://www.openhab.org/docs/"; + + public static final String OAUTH_AUTHORIZE_ENDPOINT = "/auth/authorize"; + public static final String OAUTH_TOKEN_ENDPOINT = "/rest/auth/token"; + + private final Logger logger = LoggerFactory.getLogger(OpenApiResource.class); + private final BundleContext bundleContext; + + /** + * Creates a new instance. + */ + @Activate + public OpenApiResource(final BundleContext bc, final @Reference Application application) { + this.bundleContext = bc; + } + + /** + * Gets the current JAX-RS Whiteboard provided endpoint information by OpenAPI. + * + * @return a OpenAPI description of the endpoints + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public Object getOpenAPI() { + try { + Reader reader = new Reader(); + + OpenAPI openAPI = reader.read(getReaderClasses()); + openAPI.setInfo(createInfo()); + openAPI.setServers(List.of(createServer())); + openAPI.schemaRequirement("oauth2", createOAuth2SecurityScheme()); + + String json = Json.mapper().writeValueAsString(openAPI); + return Response.status(Response.Status.OK) + .entity(Json.mapper().readValue(json, new TypeReference>() { + })).build(); + } catch (JsonProcessingException e) { + logger.error("Error while serializing the OpenAPI object to JSON"); + return Response.serverError().build(); + } catch (InvalidSyntaxException e) { + logger.error("Error while enumerating services for OpenAPI generation"); + return Response.serverError().build(); + } + } + + private Set> getReaderClasses() throws InvalidSyntaxException { + return bundleContext.getServiceReferences(RESTResource.class, null).stream() + .map(reference -> bundleContext.getService(reference).getClass()).collect(Collectors.toSet()); + } + + private Server createServer() { + ServiceReference applicationReference = bundleContext.getServiceReference(Application.class); + + Server server = new Server(); + server.setUrl("/" + applicationReference.getProperty(JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE)); + + return server; + } + + private Info createInfo() { + Contact contact = new Contact(); + contact.setName(CONTACT_NAME); + contact.setUrl(CONTACT_URL); + + Info info = new Info(); + info.setContact(contact); + info.setTitle(API_TITLE); + info.setVersion(RESTConstants.API_VERSION); + + return info; + } + + private SecurityScheme createOAuth2SecurityScheme() { + Scopes scopes = new Scopes(); + scopes.addString("admin", "Administration operations"); + + OAuthFlow authorizationCode = new OAuthFlow(); + authorizationCode.setAuthorizationUrl(OAUTH_AUTHORIZE_ENDPOINT); + authorizationCode.setTokenUrl(OAUTH_TOKEN_ENDPOINT); + authorizationCode.setScopes(scopes); + + OAuthFlows flows = new OAuthFlows(); + flows.setAuthorizationCode(authorizationCode); + + SecurityScheme securityScheme = new SecurityScheme(); + securityScheme.setName("oauth2"); + securityScheme.setType(SecurityScheme.Type.OAUTH2); + securityScheme.setFlows(flows); + + return securityScheme; + } +} diff --git a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/SwaggerResource.java b/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/SwaggerResource.java deleted file mode 100644 index eb75f709d..000000000 --- a/bundles/org.openhab.core.io.rest.swagger/src/main/java/org/openhab/core/io/rest/swagger/impl/SwaggerResource.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.core.io.rest.swagger.impl; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.io.rest.RESTConstants; -import org.openhab.core.io.rest.RESTResource; -import org.osgi.framework.BundleContext; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; -import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; - -import io.swagger.jaxrs.Reader; -import io.swagger.jaxrs.config.ReaderListener; -import io.swagger.models.Swagger; -import io.swagger.util.Json; - -/** - * An endpoint to generate and provide a Swagger description. - * - * @author Markus Rathgeb - Initial contribution - * @author Kai Kreuzer - made it a RESTResource to register in the root bean - * @author Yannick Schaus - add support for ReaderListeners, remove dependency - */ -@Component(service = SwaggerResource.class) -@JaxrsResource -@JaxrsName("spec") -@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") -@JSONRequired -@Path("/spec") -@NonNullByDefault -public class SwaggerResource implements RESTResource { - - private final Logger logger = LoggerFactory.getLogger(SwaggerResource.class); - private final BundleContext bundleContext; - - /** - * Creates a new instance. - */ - @Activate - public SwaggerResource(final BundleContext bc, final @Reference Application application) { - this.bundleContext = bc; - } - - /** - * Gets the current JAX-RS Whiteboard provided endpoint information by Swagger. - * - * @return a Swagger description of the endpoints - */ - @GET - @Produces(MediaType.APPLICATION_JSON) - public Object getSwagger() { - Swagger swagger = new Swagger(); - Set> classes = new HashSet>(); - Reader swaggerReader = new Reader(swagger); - - try { - ServiceReference applicationReference = bundleContext.getServiceReference(Application.class); - swagger.setBasePath( - "/" + applicationReference.getProperty(JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE)); - - Collection> resourcesReferences = bundleContext - .getServiceReferences(RESTResource.class, null); - resourcesReferences.forEach(sr -> { - Object service = bundleContext.getService(sr); - classes.add(service.getClass()); - }); - - Collection> readerListenersReferences = bundleContext - .getServiceReferences(ReaderListener.class, null); - readerListenersReferences.forEach(sr -> { - Object service = bundleContext.getService(sr); - classes.add(service.getClass()); - }); - - swaggerReader.read(classes); - String json = Json.mapper().writeValueAsString(swagger); - return Response.status(Response.Status.OK) - .entity(Json.mapper().readValue(json, new TypeReference>() { - })).build(); - } catch (JsonProcessingException e) { - logger.error("Error while serializing the Swagger object to JSON"); - return Response.serverError().build(); - } catch (IOException e) { - logger.error("Error while deserializing the Swagger JSON output"); - return Response.serverError().build(); - } catch (InvalidSyntaxException e) { - logger.error("Error while enumerating services for Swagger generation"); - return Response.serverError().build(); - } - } -} diff --git a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java index 6ffee32b1..e6930113e 100644 --- a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java +++ b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java @@ -47,18 +47,20 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; -import io.swagger.annotations.Authorization; -import io.swagger.annotations.AuthorizationScope; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for the UI resources and is registered with the * Jersey servlet. * * @author Yannick Schaus - Initial contribution + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component(service = { RESTResource.class, UIResource.class }) @JaxrsResource @@ -66,7 +68,7 @@ import io.swagger.annotations.AuthorizationScope; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(UIResource.PATH_UI) -@Api(UIResource.PATH_UI) +@Tag(name = UIResource.PATH_UI) @NonNullByDefault public class UIResource implements RESTResource { @@ -87,8 +89,8 @@ public class UIResource implements RESTResource { @GET @Path("/tiles") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get all registered UI tiles.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class) }) + @Operation(summary = "Get all registered UI tiles.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = TileDTO.class)))) }) public Response getAll() { Stream tiles = tileProvider.getTiles().map(this::toTileDTO); return Response.ok(new Stream2JSONInputStream(tiles)).build(); @@ -97,8 +99,8 @@ public class UIResource implements RESTResource { @GET @Path("/components/{namespace}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get all registered UI components in the specified namespace.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class) }) + @Operation(summary = "Get all registered UI components in the specified namespace.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RootUIComponent.class)))) }) public Response getAllComponents(@PathParam("namespace") String namespace) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); Stream components = registry.getAll().stream(); @@ -108,9 +110,9 @@ public class UIResource implements RESTResource { @GET @Path("/components/{namespace}/{componentUID}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Get a specific UI component in the specified namespace.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class), - @ApiResponse(code = 404, message = "Component not found", response = Tile.class) }) + @Operation(summary = "Get a specific UI component in the specified namespace.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RootUIComponent.class))), + @ApiResponse(responseCode = "404", description = "Component not found") }) public Response getComponentByUID(@PathParam("namespace") String namespace, @PathParam("componentUID") String componentUID) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); @@ -125,10 +127,9 @@ public class UIResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/components/{namespace}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Add an UI component in the specified namespace.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class) }) + @Operation(summary = "Add an UI component in the specified namespace.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RootUIComponent.class))) }) public Response addComponent(@PathParam("namespace") String namespace, RootUIComponent component) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); component.updateTimestamp(); @@ -140,11 +141,10 @@ public class UIResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/components/{namespace}/{componentUID}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Update a specific UI component in the specified namespace.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class), - @ApiResponse(code = 404, message = "Component not found", response = Tile.class) }) + @Operation(summary = "Update a specific UI component in the specified namespace.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RootUIComponent.class))), + @ApiResponse(responseCode = "404", description = "Component not found") }) public Response updateComponent(@PathParam("namespace") String namespace, @PathParam("componentUID") String componentUID, RootUIComponent component) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); @@ -165,11 +165,10 @@ public class UIResource implements RESTResource { @RolesAllowed({ Role.ADMIN }) @Path("/components/{namespace}/{componentUID}") @Produces({ MediaType.APPLICATION_JSON }) - @ApiOperation(value = "Remove a specific UI component in the specified namespace.", authorizations = { - @Authorization(value = "oauth2", scopes = { - @AuthorizationScope(scope = "admin", description = "Admin operations") }) }) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Tile.class), - @ApiResponse(code = 404, message = "Component not found", response = Tile.class) }) + @Operation(summary = "Remove a specific UI component in the specified namespace.", security = { + @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Component not found") }) public Response deleteComponent(@PathParam("namespace") String namespace, @PathParam("componentUID") String componentUID) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); diff --git a/bundles/org.openhab.core.io.rest.voice/src/main/java/org/openhab/core/io/rest/voice/internal/VoiceResource.java b/bundles/org.openhab.core.io.rest.voice/src/main/java/org/openhab/core/io/rest/voice/internal/VoiceResource.java index 644164345..be2a5ed5d 100644 --- a/bundles/org.openhab.core.io.rest.voice/src/main/java/org/openhab/core/io/rest/voice/internal/VoiceResource.java +++ b/bundles/org.openhab.core.io.rest.voice/src/main/java/org/openhab/core/io/rest/voice/internal/VoiceResource.java @@ -50,11 +50,13 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This class acts as a REST resource for voice features. @@ -62,6 +64,7 @@ import io.swagger.annotations.ApiResponses; * @author Kai Kreuzer - Initial contribution * @author Laurent Garnier - add TTS feature to the REST API * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -70,7 +73,7 @@ import io.swagger.annotations.ApiResponses; @JSONRequired @Path(VoiceResource.PATH_VOICE) @RolesAllowed({ Role.USER, Role.ADMIN }) -@Api(VoiceResource.PATH_VOICE) +@Tag(name = VoiceResource.PATH_VOICE) @NonNullByDefault public class VoiceResource implements RESTResource { @@ -91,10 +94,10 @@ public class VoiceResource implements RESTResource { @GET @Path("/interpreters") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get the list of all interpreters.", response = HumanLanguageInterpreterDTO.class, responseContainer = "List") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get the list of all interpreters.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = HumanLanguageInterpreterDTO.class)))) }) public Response getInterpreters( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) { final Locale locale = localeService.getLocale(language); List dtos = voiceManager.getHLIs().stream().map(hli -> HLIMapper.map(hli, locale)) .collect(Collectors.toList()); @@ -104,12 +107,12 @@ public class VoiceResource implements RESTResource { @GET @Path("/interpreters/{id: [a-zA-Z_0-9]+}") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets a single interpreter.", response = HumanLanguageInterpreterDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "Interpreter not found") }) + @Operation(summary = "Gets a single interpreter.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = HumanLanguageInterpreterDTO.class)))), + @ApiResponse(responseCode = "404", description = "Interpreter not found") }) public Response getInterpreter( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @PathParam("id") @ApiParam(value = "interpreter id") String id) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @PathParam("id") @Parameter(description = "interpreter id") String id) { final Locale locale = localeService.getLocale(language); HumanLanguageInterpreter hli = voiceManager.getHLI(id); if (hli == null) { @@ -123,14 +126,14 @@ public class VoiceResource implements RESTResource { @POST @Path("/interpreters/{id: [a-zA-Z_0-9]+}") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Sends a text to a given human language interpreter.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "No human language interpreter was found."), - @ApiResponse(code = 400, message = "interpretation exception occurs") }) + @Operation(summary = "Sends a text to a given human language interpreter.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "No human language interpreter was found."), + @ApiResponse(responseCode = "400", description = "interpretation exception occurs") }) public Response interpret( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @ApiParam(value = "text to interpret", required = true) String text, - @PathParam("id") @ApiParam(value = "interpreter id") String id) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @Parameter(description = "text to interpret", required = true) String text, + @PathParam("id") @Parameter(description = "interpreter id") String id) { final Locale locale = localeService.getLocale(language); HumanLanguageInterpreter hli = voiceManager.getHLI(id); if (hli == null) { @@ -148,13 +151,13 @@ public class VoiceResource implements RESTResource { @POST @Path("/interpreters") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Sends a text to the default human language interpreter.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "No human language interpreter was found."), - @ApiResponse(code = 400, message = "interpretation exception occurs") }) + @Operation(summary = "Sends a text to the default human language interpreter.", responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "No human language interpreter was found."), + @ApiResponse(responseCode = "400", description = "interpretation exception occurs") }) public Response interpret( - @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = "language") @Nullable String language, - @ApiParam(value = "text to interpret", required = true) String text) { + @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, + @Parameter(description = "text to interpret", required = true) String text) { final Locale locale = localeService.getLocale(language); HumanLanguageInterpreter hli = voiceManager.getHLI(); if (hli == null) { @@ -172,8 +175,8 @@ public class VoiceResource implements RESTResource { @GET @Path("/voices") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Get the list of all voices.", response = VoiceDTO.class, responseContainer = "List") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) + @Operation(summary = "Get the list of all voices.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = VoiceDTO.class)))) }) public Response getVoices() { List dtos = voiceManager.getAllVoices().stream().map(VoiceMapper::map).collect(Collectors.toList()); return Response.ok(dtos).build(); @@ -182,9 +185,9 @@ public class VoiceResource implements RESTResource { @GET @Path("/defaultvoice") @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets the default voice.", response = VoiceDTO.class) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 404, message = "No default voice was found.") }) + @Operation(summary = "Gets the default voice.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = VoiceDTO.class))), + @ApiResponse(responseCode = "404", description = "No default voice was found.") }) public Response getDefaultVoice() { Voice voice = voiceManager.getDefaultVoice(); if (voice == null) { @@ -198,11 +201,11 @@ public class VoiceResource implements RESTResource { @POST @Path("/say") @Consumes(MediaType.TEXT_PLAIN) - @ApiOperation(value = "Speaks a given text with a given voice through the given audio sink.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Response say(@ApiParam(value = "text to speak", required = true) String text, - @QueryParam("voiceid") @ApiParam(value = "voice id") @Nullable String voiceId, - @QueryParam("sinkid") @ApiParam(value = "audio sink id") @Nullable String sinkId) { + @Operation(summary = "Speaks a given text with a given voice through the given audio sink.", responses = { + @ApiResponse(responseCode = "200", description = "OK") }) + public Response say(@Parameter(description = "text to speak", required = true) String text, + @QueryParam("voiceid") @Parameter(description = "voice id") @Nullable String voiceId, + @QueryParam("sinkid") @Parameter(description = "audio sink id") @Nullable String sinkId) { voiceManager.say(text, voiceId, sinkId); return Response.ok(null, MediaType.TEXT_PLAIN).build(); } diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/RootResource.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/RootResource.java index b01e6ff7b..9cad5f06e 100644 --- a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/RootResource.java +++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/internal/resources/RootResource.java @@ -21,10 +21,12 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.io.rest.RESTConstants; +import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.internal.resources.beans.RootBean; import org.openhab.core.io.rest.internal.resources.beans.RootBean.Links; import org.osgi.service.component.annotations.Activate; @@ -42,10 +44,11 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** *

@@ -59,17 +62,18 @@ import io.swagger.annotations.ApiResponses; * * @author Kai Kreuzer - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ -@Component(service = RootResource.class, configurationPid = "org.openhab.restroot") +@Component(configurationPid = "org.openhab.restroot") @JaxrsResource @JaxrsName(RootResource.RESOURCE_NAME) @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Produces(MediaType.APPLICATION_JSON) -@NonNullByDefault @Path("/") -@Api(RootResource.RESOURCE_NAME) -public class RootResource { +@Tag(name = RootResource.RESOURCE_NAME) +@NonNullByDefault +public class RootResource implements RESTResource { public static final String RESOURCE_NAME = "root"; @@ -82,9 +86,11 @@ public class RootResource { } @GET - @ApiOperation(value = "Gets information about the runtime, the API version and links to resources.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Object getRoot(@Context UriInfo uriInfo) { + @Path("/") + @Produces(MediaType.APPLICATION_JSON) + @Operation(summary = "Gets information about the runtime, the API version and links to resources.", responses = { + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RootBean.class))) }) + public Response getRoot(@Context UriInfo uriInfo) { // key: path, value: name (this way we could ensure that ever path is added only once). final Map collectedLinks = new HashMap<>(); @@ -113,6 +119,6 @@ public class RootResource { collectedLinks.forEach((path, name) -> { bean.links.add(new Links(name, path)); }); - return bean; + return Response.ok(bean).build(); } } diff --git a/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconSetResource.java b/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconSetResource.java index 84dd866c8..14f645ee6 100644 --- a/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconSetResource.java +++ b/bundles/org.openhab.core.ui.icon/src/main/java/org/openhab/core/ui/icon/internal/IconSetResource.java @@ -41,17 +41,17 @@ import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; /** * This is a REST resource that provides information about available icon sets. * * @author Kai Kreuzer - Initial contribution * @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification + * @author Wouter Born - Migrated to OpenAPI annotations */ @Component @JaxrsResource @@ -59,7 +59,7 @@ import io.swagger.annotations.ApiResponses; @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") @JSONRequired @Path(IconSetResource.PATH_ICONSETS) -@Api(IconSetResource.PATH_ICONSETS) +@Tag(name = IconSetResource.PATH_ICONSETS) @NonNullByDefault public class IconSetResource implements RESTResource { @@ -86,9 +86,9 @@ public class IconSetResource implements RESTResource { @GET @Produces(MediaType.APPLICATION_JSON) - @ApiOperation(value = "Gets all icon sets.") - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK") }) - public Response getAll(@HeaderParam("Accept-Language") @ApiParam(value = "language") @Nullable String language) { + @Operation(summary = "Gets all icon sets.", responses = { @ApiResponse(responseCode = "200", description = "OK") }) + public Response getAll( + @HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) { Locale locale = localeService.getLocale(language); List iconSets = new ArrayList<>(iconProviders.size()); diff --git a/features/karaf/openhab-tp/src/main/feature/feature.xml b/features/karaf/openhab-tp/src/main/feature/feature.xml index e5f279b19..4233afdc5 100644 --- a/features/karaf/openhab-tp/src/main/feature/feature.xml +++ b/features/karaf/openhab-tp/src/main/feature/feature.xml @@ -84,7 +84,7 @@ mvn:com.fasterxml.jackson.core/jackson-databind/2.10.3 mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.10.3 mvn:com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.10.3 - mvn:com.fasterxml.jackson.datatype/jackson-datatype-joda/2.10.3 + mvn:com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.10.3 mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.10.3 mvn:com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.10.3 mvn:com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.10.3 @@ -244,19 +244,20 @@ - openhab.tp;feature=swagger-jaxrs;version=1.6.1 + openhab.tp;feature=swagger-jaxrs;version=2.1.0 openhab.tp-jax-rs-whiteboard openhab.tp-jackson - mvn:io.swagger/swagger-jaxrs/1.6.1 - mvn:io.swagger/swagger-models/1.6.1 - mvn:io.swagger/swagger-annotations/1.6.1 - mvn:io.swagger/swagger-core/1.6.1 + mvn:io.swagger.core.v3/swagger-annotations/2.1.0 + mvn:io.swagger.core.v3/swagger-core/2.1.0 + mvn:io.swagger.core.v3/swagger-integration/2.1.0 + mvn:io.swagger.core.v3/swagger-jaxrs2/2.1.0 + mvn:io.swagger.core.v3/swagger-models/2.1.0 mvn:com.google.guava/guava/27.1-jre mvn:com.google.guava/failureaccess/1.0.1 mvn:javax.validation/validation-api/1.1.0.Final - mvn:org.apache.commons/commons-lang3/3.2.1 - mvn:org.reflections/reflections/0.9.12 + mvn:org.apache.commons/commons-lang3/3.11 mvn:org.javassist/javassist/3.26.0-GA + mvn:org.reflections/reflections/0.9.12 diff --git a/pom.xml b/pom.xml index a685cdcd2..f0b2857cf 100644 --- a/pom.xml +++ b/pom.xml @@ -126,7 +126,7 @@