From 26a3134c5ceef86264b7c5f4d6f5b5ae1a9be533 Mon Sep 17 00:00:00 2001 From: "p.zahnen" Date: Tue, 6 Jan 2026 14:58:50 +0100 Subject: [PATCH] refactor OpenAPI resource error handling and logging --- .../openapi/app/DynamicOpenApi.java | 6 +- .../openapi/app/DynamicOpenApiImpl.java | 102 ++++++++++-------- .../openapi/app/DynamicOpenApiResource.java | 24 +++-- .../openapi/app/MustacheResolverOpenApi.java | 4 +- .../openapi/app/OpenApiSwaggerUiResource.java | 7 +- 5 files changed, 81 insertions(+), 62 deletions(-) diff --git a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApi.java b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApi.java index 50809d54..51096c45 100644 --- a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApi.java +++ b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApi.java @@ -17,10 +17,8 @@ public interface DynamicOpenApi { Response getOpenApi( - HttpHeaders headers, UriInfo uriInfo, String type, OpenAPISpecFilter specFilter) - throws Exception; + HttpHeaders headers, UriInfo uriInfo, String type, OpenAPISpecFilter specFilter); Response getOpenApi( - HttpHeaders headers, ServletConfig config, Application app, UriInfo uriInfo, String yaml) - throws Exception; + HttpHeaders headers, ServletConfig config, Application app, UriInfo uriInfo, String yaml); } diff --git a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiImpl.java b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiImpl.java index 7e2427cc..b99a0cea 100644 --- a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiImpl.java +++ b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiImpl.java @@ -48,61 +48,65 @@ public class DynamicOpenApiImpl extends BaseOpenApiResource implements DynamicOpenApi, JaxRsConsumer { - private static Logger LOGGER = LoggerFactory.getLogger(DynamicOpenApiImpl.class); + private static Logger logger = LoggerFactory.getLogger(DynamicOpenApiImpl.class); public static final MediaType YAML_TYPE = new MediaType("application", "yaml"); public static final String YAML = "application/yaml"; private OpenAPI openApiSpec; @Inject - public DynamicOpenApiImpl() {} + public DynamicOpenApiImpl() { + super(); + } @Override public Consumer> getConsumer() { return this::scan; } - private synchronized void scan(Set resources) { - Set> resourceClasses = - resources.stream() - .flatMap( - resource -> { - if (resource instanceof DropwizardResourceConfig.SpecificBinder) { - return ((DropwizardResourceConfig.SpecificBinder) resource) - .getBindings().stream() - .filter(binding -> binding instanceof InstanceBinding) - .map(binding -> ((InstanceBinding) binding).getService().getClass()); - } - return Stream.of(resource.getClass()); - }) - .collect(Collectors.toSet()); - Reader reader = new Reader(new OpenAPI()); - this.openApiSpec = reader.read(resourceClasses); - openApiSpec.addServersItem(new Server().url("/rest")); - if (Objects.nonNull(openApiSpec.getComponents())) { - openApiSpec - .getComponents() - .addSecuritySchemes( - "JWT", - new SecurityScheme() - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT")); + private void scan(Set resources) { + synchronized (this) { + Set> resourceClasses = + resources.stream() + .flatMap( + resource -> { + if (resource instanceof DropwizardResourceConfig.SpecificBinder) { + return ((DropwizardResourceConfig.SpecificBinder) resource) + .getBindings().stream() + .filter(binding -> binding instanceof InstanceBinding) + .map( + binding -> + ((InstanceBinding) binding).getService().getClass()); + } + return Stream.of(resource.getClass()); + }) + .collect(Collectors.toSet()); + Reader reader = new Reader(new OpenAPI()); + this.openApiSpec = reader.read(resourceClasses); + openApiSpec.addServersItem(new Server().url("/rest")); + if (Objects.nonNull(openApiSpec.getComponents())) { + openApiSpec + .getComponents() + .addSecuritySchemes( + "JWT", + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT")); + } + openApiSpec.addSecurityItem(new SecurityRequirement().addList("JWT")); } - openApiSpec.addSecurityItem(new SecurityRequirement().addList("JWT")); } @Override public Response getOpenApi( - HttpHeaders headers, ServletConfig config, Application app, UriInfo uriInfo, String type) - throws Exception { + HttpHeaders headers, ServletConfig config, Application app, UriInfo uriInfo, String type) { return getOpenApi(headers, uriInfo, type, null); } @Override public Response getOpenApi( - HttpHeaders headers, UriInfo uriInfo, String type, OpenAPISpecFilter specFilter) - throws Exception { + HttpHeaders headers, UriInfo uriInfo, String type, OpenAPISpecFilter specFilter) { if (openApiSpec == null) { return Response.status(404).build(); } @@ -121,21 +125,29 @@ public Response getOpenApi( getHeaders(headers)); } - if (StringUtils.isNotBlank(type) && type.trim().equalsIgnoreCase("yaml")) { - return Response.status(Response.Status.OK) - .entity(pretty ? Yaml.pretty(oas) : Yaml.mapper().writeValueAsString(oas)) - .type("application/yaml") - .build(); - } else { - return Response.status(Response.Status.OK) - .entity(pretty ? Json.pretty(oas) : Json.mapper().writeValueAsString(oas)) - .type(MediaType.APPLICATION_JSON_TYPE) + try { + if (StringUtils.isNotBlank(type) && "yaml".equalsIgnoreCase(type.trim())) { + return Response.status(Response.Status.OK) + .entity(pretty ? Yaml.pretty(oas) : Yaml.mapper().writeValueAsString(oas)) + .type("application/yaml") + .build(); + } else { + return Response.status(Response.Status.OK) + .entity(pretty ? Json.pretty(oas) : Json.mapper().writeValueAsString(oas)) + .type(MediaType.APPLICATION_JSON_TYPE) + .build(); + } + } catch (com.fasterxml.jackson.core.JsonProcessingException e) { + logger.error("Error serializing OpenAPI spec", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity("Error serializing OpenAPI spec: " + e.getMessage()) + .type(MediaType.TEXT_PLAIN) .build(); } } private static Map> getQueryParams(MultivaluedMap params) { - Map> output = new HashMap>(); + Map> output = new HashMap<>(); if (params != null) { for (String key : params.keySet()) { List values = params.get(key); @@ -146,7 +158,7 @@ private static Map> getQueryParams(MultivaluedMap getCookies(HttpHeaders headers) { - Map output = new HashMap(); + Map output = new HashMap<>(); if (headers != null) { for (String key : headers.getCookies().keySet()) { Cookie cookie = headers.getCookies().get(key); @@ -157,7 +169,7 @@ private static Map getCookies(HttpHeaders headers) { } private static Map> getHeaders(HttpHeaders headers) { - Map> output = new HashMap>(); + Map> output = new HashMap<>(); if (headers != null) { for (String key : headers.getRequestHeaders().keySet()) { List values = headers.getRequestHeaders().get(key); diff --git a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiResource.java b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiResource.java index 3165f64a..a9b9fd7f 100644 --- a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiResource.java +++ b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/DynamicOpenApiResource.java @@ -84,10 +84,10 @@ public DynamicOpenApiResource( @Content(mediaType = "text/html", schema = @Schema(type = "string")) }) }) - public Response getApiDescription(@Context HttpHeaders headers, @Context UriInfo uriInfo) - throws Exception { - if (LOGGER.isTraceEnabled()) + public Response getApiDescription(@Context HttpHeaders headers, @Context UriInfo uriInfo) { + if (LOGGER.isTraceEnabled()) { LOGGER.trace("MIME {} {}", "HTML", headers.getHeaderString("Accept")); + } return openApiViewerResource.getFile("index.html"); } @@ -99,9 +99,10 @@ public Response getApiDescriptionJson( @Context HttpHeaders headers, @Context UriInfo uriInfo, @Context ServletConfig config, - @Context Application app) - throws Exception { - if (LOGGER.isTraceEnabled()) LOGGER.trace("MIME {})", "JSON"); + @Context Application app) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("MIME: JSON"); + } return openApi.getOpenApi(headers, config, app, uriInfo, "json"); } @@ -113,9 +114,10 @@ public Response getApiDescriptionYaml( @Context HttpHeaders headers, @Context UriInfo uriInfo, @Context ServletConfig config, - @Context Application app) - throws Exception { - if (LOGGER.isTraceEnabled()) LOGGER.trace("MIME {})", "YAML"); + @Context Application app) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("MIME: YAML"); + } return openApi.getOpenApi(headers, config, app, uriInfo, "yaml"); } @@ -123,7 +125,9 @@ public Response getApiDescriptionYaml( @Path("/{file}") @CacheControl(maxAge = 3600) public Response getFile(@PathParam("file") String file) { - if (LOGGER.isTraceEnabled()) LOGGER.trace("FILE {})", file); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("FILE: {}", file); + } if (openApiViewerResource == null) { throw new NotFoundException(); diff --git a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/MustacheResolverOpenApi.java b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/MustacheResolverOpenApi.java index 8302af61..5dab5113 100644 --- a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/MustacheResolverOpenApi.java +++ b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/MustacheResolverOpenApi.java @@ -21,7 +21,9 @@ public class MustacheResolverOpenApi extends PerClassMustacheResolver implements PartialMustacheResolver { @Inject - MustacheResolverOpenApi() {} + MustacheResolverOpenApi() { + super(); + } @Override public int getSortPriority() { diff --git a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/OpenApiSwaggerUiResource.java b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/OpenApiSwaggerUiResource.java index 1899e2fa..b9ec4988 100644 --- a/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/OpenApiSwaggerUiResource.java +++ b/xtraplatform-openapi/src/main/java/de/ii/xtraplatform/openapi/app/OpenApiSwaggerUiResource.java @@ -28,7 +28,7 @@ @AutoBind public class OpenApiSwaggerUiResource implements OpenApiViewerResource { - private static Logger LOGGER = LoggerFactory.getLogger(OpenApiSwaggerUiResource.class); + private static Logger logger = LoggerFactory.getLogger(OpenApiSwaggerUiResource.class); @Inject public OpenApiSwaggerUiResource() {} @@ -46,7 +46,10 @@ public Response getFile(String file) { .type(getMimeType(file)) .build(); } catch (Throwable e) { - throw new NotFoundException(); + if (logger.isWarnEnabled()) { + logger.warn("Error serving file '{}': {}", file, e.toString(), e); + } + throw new NotFoundException(e); } }