From a7344c5df7e99910c0e0543f6172643a54d85205 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 3 Nov 2020 15:41:08 +0200 Subject: [PATCH] Allow disabling the UIs in Runtime. Signed-off-by: Phillip Kruger --- .../deployment/SmallRyeGraphQLBuildItem.java | 22 + .../deployment/SmallRyeGraphQLProcessor.java | 146 ++++--- .../deployment/SmallRyeGraphQLUIConfig.java | 6 - .../SmallRyeGraphQLNotFoundHandler.java | 17 + .../runtime/SmallRyeGraphQLRecorder.java | 31 +- .../runtime/SmallRyeGraphQLRuntimeConfig.java | 16 + .../runtime/SmallRyeGraphQLSchemaHandler.java | 5 +- .../runtime/SmallRyeGraphQLStaticHandler.java | 60 +++ .../deployment/SmallRyeHealthBuildItem.java | 22 + .../deployment/SmallRyeHealthProcessor.java | 163 ++++---- .../deployment/SmallRyeHealthUIConfig.java | 5 - .../SmallRyeHealthNotFoundHandler.java | 17 + .../runtime/SmallRyeHealthRecorder.java | 31 +- .../runtime/SmallRyeHealthRuntimeConfig.java | 15 + .../runtime/SmallRyeHealthStaticHandler.java | 60 +++ .../deployment/SmallRyeOpenApiProcessor.java | 25 +- .../openapi/runtime/OpenApiHandler.java | 14 +- .../runtime/OpenApiNotFoundHandler.java | 17 + .../openapi/runtime/OpenApiRecorder.java | 11 + .../openapi/runtime/OpenApiRuntimeConfig.java | 16 + .../deployment/SwaggerUiBuildItem.java | 22 + .../swaggerui/deployment/SwaggerUiConfig.java | 261 ++++++++++++ .../deployment/SwaggerUiProcessor.java | 386 +++--------------- .../runtime/SwaggerUiNotFoundHandler.java | 17 + .../swaggerui/runtime/SwaggerUiRecorder.java | 30 +- .../runtime/SwaggerUiRuntimeConfig.java | 17 + .../runtime/SwaggerUiStaticHandler.java | 59 +++ 27 files changed, 943 insertions(+), 548 deletions(-) create mode 100644 extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLBuildItem.java create mode 100644 extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLNotFoundHandler.java create mode 100644 extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRuntimeConfig.java create mode 100644 extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLStaticHandler.java create mode 100644 extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthBuildItem.java create mode 100644 extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthNotFoundHandler.java create mode 100644 extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRuntimeConfig.java create mode 100644 extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiNotFoundHandler.java create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRuntimeConfig.java create mode 100644 extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiBuildItem.java create mode 100644 extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java create mode 100644 extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiNotFoundHandler.java create mode 100644 extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRuntimeConfig.java create mode 100644 extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiStaticHandler.java diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLBuildItem.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLBuildItem.java new file mode 100644 index 0000000000000..11cc5e3f4e1cf --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkus.smallrye.graphql.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +final class SmallRyeGraphQLBuildItem extends SimpleBuildItem { + + private final String graphqlUiFinalDestination; + private final String graphqlUiPath; + + public SmallRyeGraphQLBuildItem(String graphqlUiFinalDestination, String graphqlUiPath) { + this.graphqlUiFinalDestination = graphqlUiFinalDestination; + this.graphqlUiPath = graphqlUiPath; + } + + public String getGraphqlUiFinalDestination() { + return graphqlUiFinalDestination; + } + + public String getGraphqlUiPath() { + return graphqlUiPath; + } +} \ No newline at end of file diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 3f6a2527395c9..27891b0f49efc 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -45,6 +45,7 @@ import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.metrics.MetricsFactory; import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLRecorder; +import io.quarkus.smallrye.graphql.runtime.SmallRyeGraphQLRuntimeConfig; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; @@ -92,8 +93,6 @@ public class SmallRyeGraphQLProcessor { private static final String LINE_TO_UPDATE = "const api = '"; private static final String LINE_FORMAT = LINE_TO_UPDATE + "%s';"; - SmallRyeGraphQLConfig quarkusConfig; - @BuildStep void feature(BuildProducer featureProducer) { featureProducer.produce(new FeatureBuildItem(Feature.SMALLRYE_GRAPHQL)); @@ -145,11 +144,12 @@ void buildExecutionService( BuildProducer reflectiveHierarchyProducer, SmallRyeGraphQLRecorder recorder, BeanContainerBuildItem beanContainer, - CombinedIndexBuildItem combinedIndex) { + CombinedIndexBuildItem combinedIndex, + SmallRyeGraphQLConfig graphQLConfig) { IndexView index = combinedIndex.getIndex(); - Schema schema = SchemaBuilder.build(index, quarkusConfig.autoNameStrategy); + Schema schema = SchemaBuilder.build(index, graphQLConfig.autoNameStrategy); recorder.createExecutionService(beanContainer.getValue(), schema); @@ -166,14 +166,27 @@ void requireBody(BuildProducer requireBodyHandlerPr requireBodyHandlerProducer.produce(new RequireBodyHandlerBuildItem()); } + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + void buildSchemaEndpoint( + BuildProducer routeProducer, + SmallRyeGraphQLRecorder recorder, + SmallRyeGraphQLConfig graphQLConfig) { + + Handler schemaHandler = recorder.schemaHandler(); + routeProducer.produce( + new RouteBuildItem(graphQLConfig.rootPath + SCHEMA_PATH, schemaHandler, HandlerType.BLOCKING)); + } + @Record(ExecutionTime.STATIC_INIT) @BuildStep - void buildEndpoints( + void buildExecutionEndpoint( BuildProducer routeProducer, BuildProducer notFoundPageDisplayableEndpointProducer, - LaunchModeBuildItem launchMode, SmallRyeGraphQLRecorder recorder, ShutdownContextBuildItem shutdownContext, + LaunchModeBuildItem launchMode, + SmallRyeGraphQLConfig graphQLConfig, BeanContainerBuildItem beanContainerBuildItem // don't remove this - makes sure beanContainer is initialized ) { @@ -192,19 +205,16 @@ void buildEndpoints( // add graphql endpoint for not found display in dev or test mode if (launchMode.getLaunchMode().isDevOrTest()) { notFoundPageDisplayableEndpointProducer - .produce(new NotFoundPageDisplayableEndpointBuildItem(quarkusConfig.rootPath)); + .produce(new NotFoundPageDisplayableEndpointBuildItem(graphQLConfig.rootPath)); notFoundPageDisplayableEndpointProducer - .produce(new NotFoundPageDisplayableEndpointBuildItem(quarkusConfig.rootPath + SCHEMA_PATH)); + .produce(new NotFoundPageDisplayableEndpointBuildItem(graphQLConfig.rootPath + SCHEMA_PATH)); } Boolean allowGet = ConfigProvider.getConfig().getOptionalValue(ConfigKey.ALLOW_GET, boolean.class).orElse(false); Handler executionHandler = recorder.executionHandler(allowGet); - routeProducer.produce(new RouteBuildItem(quarkusConfig.rootPath, executionHandler, HandlerType.BLOCKING)); + routeProducer.produce(new RouteBuildItem(graphQLConfig.rootPath, executionHandler, HandlerType.BLOCKING)); - Handler schemaHandler = recorder.schemaHandler(); - routeProducer.produce( - new RouteBuildItem(quarkusConfig.rootPath + SCHEMA_PATH, schemaHandler, HandlerType.BLOCKING)); } private String[] getSchemaJavaClasses(Schema schema) { @@ -318,11 +328,12 @@ private Set getAllReferenceClasses(Reference reference) { @BuildStep void activateMetrics(Capabilities capabilities, Optional metricsCapability, + SmallRyeGraphQLConfig graphQLConfig, BuildProducer systemProperties, BuildProducer unremovableBeans) { boolean activate = shouldActivateService(capabilities, - quarkusConfig.metricsEnabled, + graphQLConfig.metricsEnabled, metricsCapability.isPresent(), "quarkus-smallrye-metrics", "metrics", @@ -339,10 +350,11 @@ void activateMetrics(Capabilities capabilities, @BuildStep void activateTracing(Capabilities capabilities, + SmallRyeGraphQLConfig graphQLConfig, BuildProducer systemProperties) { boolean activate = shouldActivateService(capabilities, - quarkusConfig.tracingEnabled, + graphQLConfig.tracingEnabled, "quarkus-smallrye-opentracing", Capability.OPENTRACING, "quarkus.smallrye-graphql.tracing.enabled"); @@ -355,10 +367,11 @@ void activateTracing(Capabilities capabilities, @BuildStep void activateValidation(Capabilities capabilities, + SmallRyeGraphQLConfig graphQLConfig, BuildProducer systemProperties) { boolean activate = shouldActivateService(capabilities, - quarkusConfig.validationEnabled, + graphQLConfig.validationEnabled, "quarkus-hibernate-validator", Capability.HIBERNATE_VALIDATOR, "quarkus.smallrye-graphql.validation.enabled"); @@ -370,8 +383,8 @@ void activateValidation(Capabilities capabilities, } @BuildStep - void activateEventing(BuildProducer systemProperties) { - if (quarkusConfig.eventsEnabled) { + void activateEventing(SmallRyeGraphQLConfig graphQLConfig, BuildProducer systemProperties) { + if (graphQLConfig.eventsEnabled) { systemProperties.produce(new SystemPropertyBuildItem(ConfigKey.ENABLE_EVENTS, TRUE)); } else { systemProperties.produce(new SystemPropertyBuildItem(ConfigKey.ENABLE_EVENTS, FALSE)); @@ -412,63 +425,80 @@ private boolean shouldActivateService(Capabilities capabilities, // UI Related @BuildStep - @Record(ExecutionTime.STATIC_INIT) - void registerGraphQLUiServletExtension( - BuildProducer routeProducer, + void getGraphqlUiFinalDestination( BuildProducer generatedResourceProducer, BuildProducer nativeImageResourceProducer, BuildProducer notFoundPageDisplayableEndpointProducer, - SmallRyeGraphQLRecorder recorder, - LaunchModeBuildItem launchMode, + BuildProducer smallRyeGraphQLBuildProducer, HttpRootPathBuildItem httpRootPath, - CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception { + CurateOutcomeBuildItem curateOutcomeBuildItem, + LaunchModeBuildItem launchMode, + SmallRyeGraphQLConfig graphQLConfig) throws Exception { - if (!quarkusConfig.ui.enable) { - return; - } - if ("/".equals(quarkusConfig.ui.rootPath)) { - throw new ConfigurationError( - "quarkus.smallrye-graphql.root-path-ui was set to \"/\", this is not allowed as it blocks the application from serving anything else."); - } + if (shouldInclude(launchMode, graphQLConfig)) { + + if ("/".equals(graphQLConfig.ui.rootPath)) { + throw new ConfigurationError( + "quarkus.smallrye-graphql.root-path-ui was set to \"/\", this is not allowed as it blocks the application from serving anything else."); + } - String graphQLPath = httpRootPath.adjustPath(quarkusConfig.rootPath); + String graphQLPath = httpRootPath.adjustPath(graphQLConfig.rootPath); - AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, GRAPHQL_UI_WEBJAR_GROUP_ID, - GRAPHQL_UI_WEBJAR_ARTIFACT_ID); - if (launchMode.getLaunchMode().isDevOrTest()) { - Path tempPath = WebJarUtil.devOrTest(curateOutcomeBuildItem, launchMode, artifact, GRAPHQL_UI_WEBJAR_PREFIX); - WebJarUtil.updateUrl(tempPath.resolve(FILE_TO_UPDATE), graphQLPath, LINE_TO_UPDATE, LINE_FORMAT); + AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, GRAPHQL_UI_WEBJAR_GROUP_ID, + GRAPHQL_UI_WEBJAR_ARTIFACT_ID); + if (launchMode.getLaunchMode().isDevOrTest()) { + Path tempPath = WebJarUtil.devOrTest(curateOutcomeBuildItem, launchMode, artifact, GRAPHQL_UI_WEBJAR_PREFIX); + WebJarUtil.updateUrl(tempPath.resolve(FILE_TO_UPDATE), graphQLPath, LINE_TO_UPDATE, LINE_FORMAT); - Handler handler = recorder.uiHandler(tempPath.toAbsolutePath().toString(), - httpRootPath.adjustPath(quarkusConfig.ui.rootPath)); - routeProducer.produce(new RouteBuildItem(quarkusConfig.ui.rootPath, handler)); - routeProducer.produce(new RouteBuildItem(quarkusConfig.ui.rootPath + "/*", handler)); - notFoundPageDisplayableEndpointProducer - .produce(new NotFoundPageDisplayableEndpointBuildItem(quarkusConfig.ui.rootPath + "/")); + smallRyeGraphQLBuildProducer.produce(new SmallRyeGraphQLBuildItem(tempPath.toAbsolutePath().toString(), + httpRootPath.adjustPath(graphQLConfig.ui.rootPath))); + notFoundPageDisplayableEndpointProducer + .produce(new NotFoundPageDisplayableEndpointBuildItem(graphQLConfig.ui.rootPath + "/")); - } else if (quarkusConfig.ui.alwaysInclude) { + } else { + Map files = WebJarUtil.production(curateOutcomeBuildItem, artifact, GRAPHQL_UI_WEBJAR_PREFIX); - Map files = WebJarUtil.production(curateOutcomeBuildItem, artifact, GRAPHQL_UI_WEBJAR_PREFIX); + for (Map.Entry file : files.entrySet()) { - for (Map.Entry file : files.entrySet()) { + String fileName = file.getKey(); + byte[] content = file.getValue(); + if (fileName.endsWith(FILE_TO_UPDATE)) { + content = WebJarUtil + .updateUrl(new String(content, StandardCharsets.UTF_8), graphQLPath, LINE_TO_UPDATE, + LINE_FORMAT) + .getBytes(StandardCharsets.UTF_8); + } + fileName = GRAPHQL_UI_FINAL_DESTINATION + "/" + fileName; - String fileName = file.getKey(); - byte[] content = file.getValue(); - if (fileName.endsWith(FILE_TO_UPDATE)) { - content = WebJarUtil - .updateUrl(new String(content, StandardCharsets.UTF_8), graphQLPath, LINE_TO_UPDATE, LINE_FORMAT) - .getBytes(StandardCharsets.UTF_8); + generatedResourceProducer.produce(new GeneratedResourceBuildItem(fileName, content)); + nativeImageResourceProducer.produce(new NativeImageResourceBuildItem(fileName)); } - fileName = GRAPHQL_UI_FINAL_DESTINATION + "/" + fileName; - generatedResourceProducer.produce(new GeneratedResourceBuildItem(fileName, content)); - nativeImageResourceProducer.produce(new NativeImageResourceBuildItem(fileName)); + smallRyeGraphQLBuildProducer.produce(new SmallRyeGraphQLBuildItem(GRAPHQL_UI_FINAL_DESTINATION, + httpRootPath.adjustPath(graphQLConfig.ui.rootPath))); } + } + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void registerGraphQLUiHandler( + BuildProducer routeProducer, + SmallRyeGraphQLRecorder recorder, + SmallRyeGraphQLRuntimeConfig runtimeConfig, + SmallRyeGraphQLBuildItem smallRyeGraphQLBuildItem, + LaunchModeBuildItem launchMode, + SmallRyeGraphQLConfig graphQLConfig) throws Exception { - Handler handler = recorder - .uiHandler(GRAPHQL_UI_FINAL_DESTINATION, httpRootPath.adjustPath(quarkusConfig.ui.rootPath)); - routeProducer.produce(new RouteBuildItem(quarkusConfig.ui.rootPath, handler)); - routeProducer.produce(new RouteBuildItem(quarkusConfig.ui.rootPath + "/*", handler)); + if (shouldInclude(launchMode, graphQLConfig)) { + Handler handler = recorder.uiHandler(smallRyeGraphQLBuildItem.getGraphqlUiFinalDestination(), + smallRyeGraphQLBuildItem.getGraphqlUiPath(), runtimeConfig); + routeProducer.produce(new RouteBuildItem(graphQLConfig.ui.rootPath, handler)); + routeProducer.produce(new RouteBuildItem(graphQLConfig.ui.rootPath + "/*", handler)); } } + + private static boolean shouldInclude(LaunchModeBuildItem launchMode, SmallRyeGraphQLConfig graphQLConfig) { + return launchMode.getLaunchMode().isDevOrTest() || graphQLConfig.ui.alwaysInclude; + } } diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java index 7b2b8a5994226..9c426cffbecbf 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLUIConfig.java @@ -19,10 +19,4 @@ public class SmallRyeGraphQLUIConfig { */ @ConfigItem(defaultValue = "false") boolean alwaysInclude; - - /** - * If GraphQL UI should be enabled. By default, GraphQL UI is enabled. - */ - @ConfigItem(defaultValue = "true") - boolean enable; } diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLNotFoundHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLNotFoundHandler.java new file mode 100644 index 0000000000000..e7731048cae66 --- /dev/null +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLNotFoundHandler.java @@ -0,0 +1,17 @@ +package io.quarkus.smallrye.graphql.runtime; + +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Handling static when disabled + */ +public class SmallRyeGraphQLNotFoundHandler implements Handler { + + @Override + public void handle(RoutingContext event) { + event.response().setStatusCode(404); + event.response().end(); + } + +} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java index 26935ecf9effd..fb4160081b153 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRecorder.java @@ -13,9 +13,7 @@ import io.smallrye.graphql.cdi.producer.GraphQLProducer; import io.smallrye.graphql.schema.model.Schema; import io.vertx.core.Handler; -import io.vertx.core.http.HttpHeaders; import io.vertx.ext.web.RoutingContext; -import io.vertx.ext.web.handler.StaticHandler; @Recorder public class SmallRyeGraphQLRecorder { @@ -43,29 +41,14 @@ public Handler schemaHandler() { return new SmallRyeGraphQLSchemaHandler(); } - public Handler uiHandler(String graphqlUiFinalDestination, String graphqlUiPath) { + public Handler uiHandler(String graphqlUiFinalDestination, + String graphqlUiPath, SmallRyeGraphQLRuntimeConfig runtimeConfig) { - StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true) - .setWebRoot(graphqlUiFinalDestination) - .setDefaultContentEncoding("UTF-8"); - - return new Handler() { - @Override - public void handle(RoutingContext event) { - if (event.normalisedPath().length() == graphqlUiPath.length()) { - - event.response().setStatusCode(302); - event.response().headers().set(HttpHeaders.LOCATION, graphqlUiPath + "/"); - event.response().end(); - return; - } else if (event.normalisedPath().length() == graphqlUiPath.length() + 1) { - event.reroute(graphqlUiPath + "/index.html"); - return; - } - - staticHandler.handle(event); - } - }; + if (runtimeConfig.enable) { + return new SmallRyeGraphQLStaticHandler(graphqlUiFinalDestination, graphqlUiPath); + } else { + return new SmallRyeGraphQLNotFoundHandler(); + } } public void setupClDevMode(ShutdownContext shutdownContext) { diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRuntimeConfig.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRuntimeConfig.java new file mode 100644 index 0000000000000..5b5661db06737 --- /dev/null +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLRuntimeConfig.java @@ -0,0 +1,16 @@ +package io.quarkus.smallrye.graphql.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "smallrye-graphql", phase = ConfigPhase.RUN_TIME) +public class SmallRyeGraphQLRuntimeConfig { + + /** + * If GraphQL UI should be enabled. By default, GraphQL UI is enabled if it is included (see {@code always-include}). + */ + @ConfigItem(name = "ui.enable", defaultValue = "true") + boolean enable; + +} diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLSchemaHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLSchemaHandler.java index e22e7acb3a598..656150c562bd8 100644 --- a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLSchemaHandler.java +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLSchemaHandler.java @@ -21,13 +21,14 @@ public class SmallRyeGraphQLSchemaHandler implements Handler { @Override public void handle(RoutingContext event) { + HttpServerRequest request = event.request(); + HttpServerResponse response = event.response(); + GraphQLSchema graphQLSchema = CDI.current().select(GraphQLSchema.class).get(); SchemaPrinter schemaPrinter = CDI.current().select(SchemaPrinter.class).get(); String schemaString = schemaPrinter.print(graphQLSchema); - HttpServerRequest request = event.request(); - HttpServerResponse response = event.response(); if (request.method().equals(HttpMethod.OPTIONS)) { response.headers().set(HttpHeaders.ALLOW, ALLOWED_METHODS); } else if (request.method().equals(HttpMethod.GET)) { diff --git a/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLStaticHandler.java b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLStaticHandler.java new file mode 100644 index 0000000000000..8e54c389e5127 --- /dev/null +++ b/extensions/smallrye-graphql/runtime/src/main/java/io/quarkus/smallrye/graphql/runtime/SmallRyeGraphQLStaticHandler.java @@ -0,0 +1,60 @@ +package io.quarkus.smallrye.graphql.runtime; + +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.StaticHandler; + +/** + * Handling static Health UI content + */ +public class SmallRyeGraphQLStaticHandler implements Handler { + + private String graphqlUiFinalDestination; + private String graphqlUiPath; + + public SmallRyeGraphQLStaticHandler() { + } + + public SmallRyeGraphQLStaticHandler(String graphqlUiFinalDestination, String graphqlUiPath) { + this.graphqlUiFinalDestination = graphqlUiFinalDestination; + this.graphqlUiPath = graphqlUiPath; + } + + public String getGraphqlUiFinalDestination() { + return graphqlUiFinalDestination; + } + + public void setGraphqlUiFinalDestination(String graphqlUiFinalDestination) { + this.graphqlUiFinalDestination = graphqlUiFinalDestination; + } + + public String getGraphqlUiPath() { + return graphqlUiPath; + } + + public void setGraphqlUiPath(String graphqlUiPath) { + this.graphqlUiPath = graphqlUiPath; + } + + @Override + public void handle(RoutingContext event) { + StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true) + .setWebRoot(graphqlUiFinalDestination) + .setDefaultContentEncoding("UTF-8"); + + if (event.normalisedPath().length() == graphqlUiPath.length()) { + + event.response().setStatusCode(302); + event.response().headers().set(HttpHeaders.LOCATION, graphqlUiPath + "/"); + event.response().end(); + return; + } else if (event.normalisedPath().length() == graphqlUiPath.length() + 1) { + event.reroute(graphqlUiPath + "/index.html"); + return; + } + + staticHandler.handle(event); + } + +} diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthBuildItem.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthBuildItem.java new file mode 100644 index 0000000000000..8764414cadc35 --- /dev/null +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkus.smallrye.health.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +final class SmallRyeHealthBuildItem extends SimpleBuildItem { + + private final String healthUiFinalDestination; + private final String healthUiPath; + + public SmallRyeHealthBuildItem(String healthUiFinalDestination, String healthUiPath) { + this.healthUiFinalDestination = healthUiFinalDestination; + this.healthUiPath = healthUiPath; + } + + public String getHealthUiFinalDestination() { + return healthUiFinalDestination; + } + + public String getHealthUiPath() { + return healthUiPath; + } +} \ No newline at end of file diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java index 1c762c213e65e..3979cf17b53db 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java @@ -60,6 +60,7 @@ import io.quarkus.smallrye.health.runtime.SmallRyeHealthGroupHandler; import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler; import io.quarkus.smallrye.health.runtime.SmallRyeHealthRecorder; +import io.quarkus.smallrye.health.runtime.SmallRyeHealthRuntimeConfig; import io.quarkus.smallrye.health.runtime.SmallRyeIndividualHealthGroupHandler; import io.quarkus.smallrye.health.runtime.SmallRyeLivenessHandler; import io.quarkus.smallrye.health.runtime.SmallRyeReadinessHandler; @@ -103,11 +104,6 @@ public boolean getAsBoolean() { } } - /** - * The configuration for health checking. - */ - SmallRyeHealthConfig health; - HealthBuildTimeConfig config; @BuildStep @@ -133,18 +129,23 @@ void build(SmallRyeHealthRecorder recorder, BuildProducer additionalBean, BuildProducer beanDefiningAnnotation, BuildProducer displayableEndpoints, - LaunchModeBuildItem launchModeBuildItem) throws IOException, ClassNotFoundException { + LaunchModeBuildItem launchMode, + SmallRyeHealthConfig healthConfig) + throws IOException, ClassNotFoundException { feature.produce(new FeatureBuildItem(Feature.SMALLRYE_HEALTH)); // add health endpoints to not found page - if (launchModeBuildItem.getLaunchMode().isDevOrTest()) { - displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath)); - displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.livenessPath)); + if (launchMode.getLaunchMode().isDevOrTest()) { + displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(healthConfig.rootPath)); + displayableEndpoints + .produce(new NotFoundPageDisplayableEndpointBuildItem(healthConfig.rootPath + healthConfig.livenessPath)); displayableEndpoints - .produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.readinessPath)); - displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.groupPath)); - displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(health.rootPath + health.wellnessPath)); + .produce(new NotFoundPageDisplayableEndpointBuildItem(healthConfig.rootPath + healthConfig.readinessPath)); + displayableEndpoints + .produce(new NotFoundPageDisplayableEndpointBuildItem(healthConfig.rootPath + healthConfig.groupPath)); + displayableEndpoints + .produce(new NotFoundPageDisplayableEndpointBuildItem(healthConfig.rootPath + healthConfig.wellnessPath)); } // Discover the beans annotated with @Health, @Liveness, @Readiness, @HealthGroup, @@ -181,7 +182,8 @@ void build(SmallRyeHealthRecorder recorder, @BuildStep public void defineHealthRoutes(BuildProducer routes, - BeanArchiveIndexBuildItem beanArchiveIndex) { + BeanArchiveIndexBuildItem beanArchiveIndex, + SmallRyeHealthConfig healthConfig) { IndexView index = beanArchiveIndex.getIndex(); // log a warning if users try to use MP Health annotations with JAX-RS @Path @@ -191,16 +193,16 @@ public void defineHealthRoutes(BuildProducer routes, warnIfJaxRsPathUsed(index, WELLNESS); // Register the health handler - routes.produce(new RouteBuildItem(health.rootPath, new SmallRyeHealthHandler(), HandlerType.BLOCKING)); + routes.produce(new RouteBuildItem(healthConfig.rootPath, new SmallRyeHealthHandler(), HandlerType.BLOCKING)); // Register the liveness handler routes.produce( - new RouteBuildItem(health.rootPath + health.livenessPath, new SmallRyeLivenessHandler(), + new RouteBuildItem(healthConfig.rootPath + healthConfig.livenessPath, new SmallRyeLivenessHandler(), HandlerType.BLOCKING)); // Register the readiness handler routes.produce( - new RouteBuildItem(health.rootPath + health.readinessPath, new SmallRyeReadinessHandler(), + new RouteBuildItem(healthConfig.rootPath + healthConfig.readinessPath, new SmallRyeReadinessHandler(), HandlerType.BLOCKING)); // Find all health groups @@ -218,31 +220,33 @@ public void defineHealthRoutes(BuildProducer routes, // Register the health group handlers routes.produce( - new RouteBuildItem(health.rootPath + health.groupPath, new SmallRyeHealthGroupHandler(), + new RouteBuildItem(healthConfig.rootPath + healthConfig.groupPath, new SmallRyeHealthGroupHandler(), HandlerType.BLOCKING)); SmallRyeIndividualHealthGroupHandler handler = new SmallRyeIndividualHealthGroupHandler(); for (String healthGroup : healthGroups) { routes.produce( - new RouteBuildItem(health.rootPath + health.groupPath + "/" + healthGroup, + new RouteBuildItem(healthConfig.rootPath + healthConfig.groupPath + "/" + healthGroup, handler, HandlerType.BLOCKING)); } // Register the wellness handler routes.produce( - new RouteBuildItem(health.rootPath + health.wellnessPath, new SmallRyeWellnessHandler(), + new RouteBuildItem(healthConfig.rootPath + healthConfig.wellnessPath, new SmallRyeWellnessHandler(), HandlerType.BLOCKING)); } @BuildStep(onlyIf = OpenAPIIncluded.class) public void includeInOpenAPIEndpoint(BuildProducer openAPIProducer, - Capabilities capabilities) { + Capabilities capabilities, + SmallRyeHealthConfig healthConfig) { // Add to OpenAPI if OpenAPI is available if (capabilities.isPresent(Capability.SMALLRYE_OPENAPI)) { - HealthOpenAPIFilter filter = new HealthOpenAPIFilter(health.rootPath, health.rootPath + health.livenessPath, - health.rootPath + health.readinessPath); + HealthOpenAPIFilter filter = new HealthOpenAPIFilter(healthConfig.rootPath, + healthConfig.rootPath + healthConfig.livenessPath, + healthConfig.rootPath + healthConfig.readinessPath); openAPIProducer.produce(new AddToOpenAPIDefinitionBuildItem(filter)); } } @@ -272,16 +276,19 @@ private void warnIfJaxRsPathUsed(IndexView index, DotName healthAnnotation) { @BuildStep public void kubernetes(HttpBuildTimeConfig httpConfig, + SmallRyeHealthConfig healthConfig, BuildProducer livenessPathItemProducer, BuildProducer readinessPathItemProducer) { if (httpConfig.rootPath == null) { - livenessPathItemProducer.produce(new KubernetesHealthLivenessPathBuildItem(health.rootPath + health.livenessPath)); + livenessPathItemProducer + .produce(new KubernetesHealthLivenessPathBuildItem(healthConfig.rootPath + healthConfig.livenessPath)); readinessPathItemProducer - .produce(new KubernetesHealthReadinessPathBuildItem(health.rootPath + health.readinessPath)); + .produce(new KubernetesHealthReadinessPathBuildItem(healthConfig.rootPath + healthConfig.readinessPath)); } else { - String basePath = httpConfig.rootPath.replaceAll("/$", "") + health.rootPath; - livenessPathItemProducer.produce(new KubernetesHealthLivenessPathBuildItem(basePath + health.livenessPath)); - readinessPathItemProducer.produce(new KubernetesHealthReadinessPathBuildItem(basePath + health.readinessPath)); + String basePath = httpConfig.rootPath.replaceAll("/$", "") + healthConfig.rootPath; + livenessPathItemProducer.produce(new KubernetesHealthLivenessPathBuildItem(basePath + healthConfig.livenessPath)); + readinessPathItemProducer + .produce(new KubernetesHealthReadinessPathBuildItem(basePath + healthConfig.readinessPath)); } } @@ -339,61 +346,75 @@ public void transform(TransformationContext ctx) { // UI @BuildStep - @Record(ExecutionTime.STATIC_INIT) void registerUiExtension( - BuildProducer routeProducer, BuildProducer generatedResourceProducer, BuildProducer nativeImageResourceProducer, BuildProducer notFoundPageDisplayableEndpointProducer, - SmallRyeHealthRecorder recorder, - LaunchModeBuildItem launchMode, + BuildProducer smallRyeHealthBuildProducer, HttpRootPathBuildItem httpRootPath, - CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception { + SmallRyeHealthConfig healthConfig, + CurateOutcomeBuildItem curateOutcomeBuildItem, + LaunchModeBuildItem launchMode) throws Exception { - if (!health.ui.enable) { - return; - } - if ("/".equals(health.ui.rootPath)) { - throw new ConfigurationError( - "quarkus.smallrye-health.root-path-ui was set to \"/\", this is not allowed as it blocks the application from serving anything else."); - } + if (shouldInclude(launchMode, healthConfig)) { + + if ("/".equals(healthConfig.ui.rootPath)) { + throw new ConfigurationError( + "quarkus.smallrye-health.root-path-ui was set to \"/\", this is not allowed as it blocks the application from serving anything else."); + } - String healthPath = httpRootPath.adjustPath(health.rootPath); + String healthPath = httpRootPath.adjustPath(healthConfig.rootPath); - AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, HEALTH_UI_WEBJAR_GROUP_ID, - HEALTH_UI_WEBJAR_ARTIFACT_ID); + AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, HEALTH_UI_WEBJAR_GROUP_ID, + HEALTH_UI_WEBJAR_ARTIFACT_ID); - if (launchMode.getLaunchMode().isDevOrTest()) { - Path tempPath = WebJarUtil.devOrTest(curateOutcomeBuildItem, launchMode, artifact, HEALTH_UI_WEBJAR_PREFIX); - updateApiUrl(tempPath.resolve(FILE_TO_UPDATE), healthPath); - - Handler handler = recorder.uiHandler(tempPath.toAbsolutePath().toString(), - httpRootPath.adjustPath(health.ui.rootPath)); - routeProducer.produce(new RouteBuildItem(health.ui.rootPath, handler)); - routeProducer.produce(new RouteBuildItem(health.ui.rootPath + "/*", handler)); - notFoundPageDisplayableEndpointProducer - .produce(new NotFoundPageDisplayableEndpointBuildItem(health.ui.rootPath + "/")); - } else if (health.ui.alwaysInclude) { - Map files = WebJarUtil.production(curateOutcomeBuildItem, artifact, HEALTH_UI_WEBJAR_PREFIX); - - for (Map.Entry file : files.entrySet()) { - - String fileName = file.getKey(); - byte[] content = file.getValue(); - if (fileName.endsWith(FILE_TO_UPDATE)) { - content = updateApiUrl(new String(content, StandardCharsets.UTF_8), healthPath) - .getBytes(StandardCharsets.UTF_8); + if (launchMode.getLaunchMode().isDevOrTest()) { + Path tempPath = WebJarUtil.devOrTest(curateOutcomeBuildItem, launchMode, artifact, HEALTH_UI_WEBJAR_PREFIX); + updateApiUrl(tempPath.resolve(FILE_TO_UPDATE), healthPath); + + smallRyeHealthBuildProducer.produce(new SmallRyeHealthBuildItem(tempPath.toAbsolutePath().toString(), + httpRootPath.adjustPath(healthConfig.ui.rootPath))); + + notFoundPageDisplayableEndpointProducer + .produce(new NotFoundPageDisplayableEndpointBuildItem(healthConfig.ui.rootPath + "/")); + } else { + Map files = WebJarUtil.production(curateOutcomeBuildItem, artifact, HEALTH_UI_WEBJAR_PREFIX); + + for (Map.Entry file : files.entrySet()) { + + String fileName = file.getKey(); + byte[] content = file.getValue(); + if (fileName.endsWith(FILE_TO_UPDATE)) { + content = updateApiUrl(new String(content, StandardCharsets.UTF_8), healthPath) + .getBytes(StandardCharsets.UTF_8); + } + fileName = HEALTH_UI_FINAL_DESTINATION + "/" + fileName; + + generatedResourceProducer.produce(new GeneratedResourceBuildItem(fileName, content)); + nativeImageResourceProducer.produce(new NativeImageResourceBuildItem(fileName)); } - fileName = HEALTH_UI_FINAL_DESTINATION + "/" + fileName; - generatedResourceProducer.produce(new GeneratedResourceBuildItem(fileName, content)); - nativeImageResourceProducer.produce(new NativeImageResourceBuildItem(fileName)); + smallRyeHealthBuildProducer.produce(new SmallRyeHealthBuildItem(HEALTH_UI_FINAL_DESTINATION, + httpRootPath.adjustPath(healthConfig.ui.rootPath))); } + } + } - Handler handler = recorder - .uiHandler(HEALTH_UI_FINAL_DESTINATION, httpRootPath.adjustPath(health.ui.rootPath)); - routeProducer.produce(new RouteBuildItem(health.ui.rootPath, handler)); - routeProducer.produce(new RouteBuildItem(health.ui.rootPath + "/*", handler)); + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void registerGraphQLUiHandler( + BuildProducer routeProducer, + SmallRyeHealthRecorder recorder, + SmallRyeHealthRuntimeConfig runtimeConfig, + SmallRyeHealthBuildItem smallRyeHealthBuildItem, + LaunchModeBuildItem launchMode, + SmallRyeHealthConfig healthConfig) throws Exception { + + if (shouldInclude(launchMode, healthConfig)) { + Handler handler = recorder.uiHandler(smallRyeHealthBuildItem.getHealthUiFinalDestination(), + smallRyeHealthBuildItem.getHealthUiPath(), runtimeConfig); + routeProducer.produce(new RouteBuildItem(healthConfig.ui.rootPath, handler)); + routeProducer.produce(new RouteBuildItem(healthConfig.ui.rootPath + "/*", handler)); } } @@ -408,4 +429,8 @@ private void updateApiUrl(Path healthUiJs, String healthPath) throws IOException public String updateApiUrl(String original, String healthPath) { return original.replace("url = \"/health\";", "url = \"" + healthPath + "\";"); } + + private static boolean shouldInclude(LaunchModeBuildItem launchMode, SmallRyeHealthConfig healthConfig) { + return launchMode.getLaunchMode().isDevOrTest() || healthConfig.ui.alwaysInclude; + } } diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java index f933dab442b70..04a04c45ac421 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthUIConfig.java @@ -19,9 +19,4 @@ public class SmallRyeHealthUIConfig { @ConfigItem(defaultValue = "false") boolean alwaysInclude; - /** - * If Health UI should be enabled. By default, Health UI is enabled. - */ - @ConfigItem(defaultValue = "true") - boolean enable; } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthNotFoundHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthNotFoundHandler.java new file mode 100644 index 0000000000000..a2521423b276e --- /dev/null +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthNotFoundHandler.java @@ -0,0 +1,17 @@ +package io.quarkus.smallrye.health.runtime; + +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Handling static when disabled + */ +public class SmallRyeHealthNotFoundHandler implements Handler { + + @Override + public void handle(RoutingContext event) { + event.response().setStatusCode(404); + event.response().end(); + } + +} diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRecorder.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRecorder.java index ef24518be6bf0..3f5a6ebfc652f 100644 --- a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRecorder.java +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRecorder.java @@ -5,9 +5,7 @@ import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; -import io.vertx.core.http.HttpHeaders; import io.vertx.ext.web.RoutingContext; -import io.vertx.ext.web.handler.StaticHandler; @Recorder public class SmallRyeHealthRecorder { @@ -21,28 +19,13 @@ public void registerHealthCheckResponseProvider(Class uiHandler(String healthUiFinalDestination, String healthUiPath) { + public Handler uiHandler(String healthUiFinalDestination, String healthUiPath, + SmallRyeHealthRuntimeConfig runtimeConfig) { - StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true) - .setWebRoot(healthUiFinalDestination) - .setDefaultContentEncoding("UTF-8"); - - return new Handler() { - @Override - public void handle(RoutingContext event) { - if (event.normalisedPath().length() == healthUiPath.length()) { - - event.response().setStatusCode(302); - event.response().headers().set(HttpHeaders.LOCATION, healthUiPath + "/"); - event.response().end(); - return; - } else if (event.normalisedPath().length() == healthUiPath.length() + 1) { - event.reroute(healthUiPath + "/index.html"); - return; - } - - staticHandler.handle(event); - } - }; + if (runtimeConfig.enable) { + return new SmallRyeHealthStaticHandler(healthUiFinalDestination, healthUiPath); + } else { + return new SmallRyeHealthNotFoundHandler(); + } } } diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRuntimeConfig.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRuntimeConfig.java new file mode 100644 index 0000000000000..72bf241ef81c8 --- /dev/null +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthRuntimeConfig.java @@ -0,0 +1,15 @@ +package io.quarkus.smallrye.health.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "smallrye-health", phase = ConfigPhase.RUN_TIME) +public class SmallRyeHealthRuntimeConfig { + + /** + * If Health UI should be enabled. By default, Health UI is enabled if it is included (see {@code always-include}). + */ + @ConfigItem(name = "ui.enable", defaultValue = "true") + boolean enable; +} diff --git a/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java new file mode 100644 index 0000000000000..70a5a53d0e072 --- /dev/null +++ b/extensions/smallrye-health/runtime/src/main/java/io/quarkus/smallrye/health/runtime/SmallRyeHealthStaticHandler.java @@ -0,0 +1,60 @@ +package io.quarkus.smallrye.health.runtime; + +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.StaticHandler; + +/** + * Handling static Health UI content + */ +public class SmallRyeHealthStaticHandler implements Handler { + + private String healthUiFinalDestination; + private String healthUiPath; + + public SmallRyeHealthStaticHandler() { + } + + public SmallRyeHealthStaticHandler(String healthUiFinalDestination, String healthUiPath) { + this.healthUiFinalDestination = healthUiFinalDestination; + this.healthUiPath = healthUiPath; + } + + public String getHealthUiFinalDestination() { + return healthUiFinalDestination; + } + + public void setHealthUiFinalDestination(String healthUiFinalDestination) { + this.healthUiFinalDestination = healthUiFinalDestination; + } + + public String getHealthUiPath() { + return healthUiPath; + } + + public void setHealthUiPath(String healthUiPath) { + this.healthUiPath = healthUiPath; + } + + @Override + public void handle(RoutingContext event) { + StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true) + .setWebRoot(healthUiFinalDestination) + .setDefaultContentEncoding("UTF-8"); + + if (event.normalisedPath().length() == healthUiPath.length()) { + + event.response().setStatusCode(302); + event.response().headers().set(HttpHeaders.LOCATION, healthUiPath + "/"); + event.response().end(); + return; + } else if (event.normalisedPath().length() == healthUiPath.length() + 1) { + event.reroute(healthUiPath + "/index.html"); + return; + } + + staticHandler.handle(event); + } + +} diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index 90746056c7e1c..fee344e8eb850 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -64,8 +64,8 @@ import io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem; import io.quarkus.smallrye.openapi.runtime.OpenApiConstants; import io.quarkus.smallrye.openapi.runtime.OpenApiDocumentService; -import io.quarkus.smallrye.openapi.runtime.OpenApiHandler; import io.quarkus.smallrye.openapi.runtime.OpenApiRecorder; +import io.quarkus.smallrye.openapi.runtime.OpenApiRuntimeConfig; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; @@ -81,6 +81,8 @@ import io.smallrye.openapi.runtime.scanner.FilteredIndexView; import io.smallrye.openapi.runtime.scanner.OpenApiAnnotationScanner; import io.smallrye.openapi.vertx.VertxConstants; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; /** * The main OpenAPI Processor. This will scan for JAX-RS, Spring and Vert.x Annotations, and, if any, add supplied schemas. @@ -112,8 +114,6 @@ public class SmallRyeOpenApiProcessor { private static final String SPRING = "Spring"; private static final String VERT_X = "Vert.x"; - SmallRyeOpenApiConfig openApiConfig; - @BuildStep CapabilityBuildItem capability() { return new CapabilityBuildItem(Capability.SMALLRYE_OPENAPI); @@ -140,10 +140,13 @@ List configFiles() { } @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(ExecutionTime.RUNTIME_INIT) RouteBuildItem handler(LaunchModeBuildItem launch, - BuildProducer displayableEndpoints, OpenApiRecorder recorder, - ShutdownContextBuildItem shutdownContext) { + BuildProducer displayableEndpoints, + OpenApiRecorder recorder, + OpenApiRuntimeConfig openApiRuntimeConfig, + ShutdownContextBuildItem shutdownContext, + SmallRyeOpenApiConfig openApiConfig) { /* * Ugly Hack * In dev mode, we pass a classloader to load the up to date OpenAPI document. @@ -159,7 +162,9 @@ RouteBuildItem handler(LaunchModeBuildItem launch, recorder.setupClDevMode(shutdownContext); displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(openApiConfig.path)); } - return new RouteBuildItem(openApiConfig.path, new OpenApiHandler(), HandlerType.BLOCKING); + + Handler handler = recorder.handler(openApiRuntimeConfig); + return new RouteBuildItem(openApiConfig.path, handler, HandlerType.BLOCKING); } @BuildStep @@ -274,6 +279,7 @@ public void build(ApplicationArchivesBuildItem archivesBuildItem, List openAPIBuildItems, HttpRootPathBuildItem httpRootPathBuildItem, OutputTargetBuildItem out, + SmallRyeOpenApiConfig openApiConfig, Optional resteasyJaxrsConfig) throws Exception { FilteredIndexView index = openApiFilteredIndexViewBuildItem.getIndex(); @@ -296,7 +302,7 @@ public void build(ApplicationArchivesBuildItem archivesBuildItem, nativeImageResources.produce(new NativeImageResourceBuildItem(name)); if (shouldStore) { - storeGeneratedSchema(out, schemaDocument, format); + storeGeneratedSchema(openApiConfig, out, schemaDocument, format); } } } @@ -327,7 +333,8 @@ private void produceReflectiveHierarchy(BuildProducer { private volatile OpenApiDocumentService openApiDocumentService; - private static final String ALLOWED_METHODS = "GET, HEAD, OPTIONS"; - private static final String QUERY_PARAM_FORMAT = "format"; - private static final Map RESPONSE_HEADERS = new HashMap<>(); static { @@ -36,13 +33,14 @@ public class OpenApiHandler implements Handler { @Override public void handle(RoutingContext event) { - if (event.request().method().equals(HttpMethod.OPTIONS)) { - event.response().headers().setAll(RESPONSE_HEADERS); - event.response().headers().set("Allow", ALLOWED_METHODS); + HttpServerRequest req = event.request(); + HttpServerResponse resp = event.response(); + + if (req.method().equals(HttpMethod.OPTIONS)) { + resp.headers().setAll(RESPONSE_HEADERS); + resp.headers().set("Allow", ALLOWED_METHODS); event.next(); } else { - HttpServerRequest req = event.request(); - HttpServerResponse resp = event.response(); String accept = req.headers().get("Accept"); List formatParams = event.queryParam(QUERY_PARAM_FORMAT); diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiNotFoundHandler.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiNotFoundHandler.java new file mode 100644 index 0000000000000..8ab5b03fee0f3 --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiNotFoundHandler.java @@ -0,0 +1,17 @@ +package io.quarkus.smallrye.openapi.runtime; + +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Handling not found when disabled + */ +public class OpenApiNotFoundHandler implements Handler { + + @Override + public void handle(RoutingContext event) { + event.response().setStatusCode(404); + event.response().end(); + } + +} diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java index 05aa31efd77e8..9e7463ec7761b 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java @@ -2,10 +2,21 @@ import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; @Recorder public class OpenApiRecorder { + public Handler handler(OpenApiRuntimeConfig runtimeConfig) { + + if (runtimeConfig.enable) { + return new OpenApiHandler(); + } else { + return new OpenApiNotFoundHandler(); + } + } + public void setupClDevMode(ShutdownContext shutdownContext) { OpenApiConstants.classLoader = Thread.currentThread().getContextClassLoader(); shutdownContext.addShutdownTask(new Runnable() { diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRuntimeConfig.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRuntimeConfig.java new file mode 100644 index 0000000000000..340f84c96bcab --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRuntimeConfig.java @@ -0,0 +1,16 @@ +package io.quarkus.smallrye.openapi.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "smallrye-openapi", phase = ConfigPhase.RUN_TIME) +public class OpenApiRuntimeConfig { + + /** + * Enable the openapi endpoint. By default it's enabled. + */ + @ConfigItem(defaultValue = "true") + public boolean enable; + +} diff --git a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiBuildItem.java b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiBuildItem.java new file mode 100644 index 0000000000000..bfc90c824cab2 --- /dev/null +++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkus.swaggerui.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +final class SwaggerUiBuildItem extends SimpleBuildItem { + + private final String swaggerUiFinalDestination; + private final String swaggerUiPath; + + public SwaggerUiBuildItem(String swaggerUiFinalDestination, String swaggerUiPath) { + this.swaggerUiFinalDestination = swaggerUiFinalDestination; + this.swaggerUiPath = swaggerUiPath; + } + + public String getSwaggerUiFinalDestination() { + return swaggerUiFinalDestination; + } + + public String getSwaggerUiPath() { + return swaggerUiPath; + } +} \ No newline at end of file diff --git a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java new file mode 100644 index 0000000000000..75c423b5a7a8c --- /dev/null +++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiConfig.java @@ -0,0 +1,261 @@ +package io.quarkus.swaggerui.deployment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.smallrye.openapi.ui.DocExpansion; +import io.smallrye.openapi.ui.HttpMethod; +import io.smallrye.openapi.ui.ThemeHref; + +@ConfigRoot +public class SwaggerUiConfig { + + /** + * The path where Swagger UI is available. + *

+ * The value `/` is not allowed as it blocks the application from serving anything else. + */ + @ConfigItem(defaultValue = "/swagger-ui") + String path; + + /** + * If this should be included every time. By default this is only included when the application is running + * in dev mode. + */ + @ConfigItem(defaultValue = "false") + boolean alwaysInclude; + + /** + * The urls that will be included as options. By default the OpenAPI path will be used. + * Here you can override that and supply multiple urls that will appear in the TopBar plugin. + */ + @ConfigItem + Map urls; + + /** + * If urls option is used, this will be the name of the default selection. + */ + @ConfigItem + Optional urlsPrimaryName; + + /** + * The html title for the page. + */ + @ConfigItem + Optional title; + + /** + * Swagger UI theme to be used. + */ + @ConfigItem + Optional theme; + + /** + * A footer for the html page. Nothing by default. + */ + @ConfigItem + Optional footer; + + /** + * If set to true, enables deep linking for tags and operations. + */ + @ConfigItem + Optional deepLinking; + + /** + * Controls the display of operationId in operations list. The default is false. + */ + @ConfigItem + Optional displayOperationId; + + /** + * The default expansion depth for models (set to -1 completely hide the models). + */ + @ConfigItem + OptionalInt defaultModelsExpandDepth; + + /** + * The default expansion depth for the model on the model-example section. + */ + @ConfigItem + OptionalInt defaultModelExpandDepth; + + /** + * Controls how the model is shown when the API is first rendered. + */ + @ConfigItem + Optional defaultModelRendering; + + /** + * Controls the display of the request duration (in milliseconds) for "Try it out" requests. + */ + @ConfigItem + Optional displayRequestDuration; + + /** + * Controls the default expansion setting for the operations and tags. + */ + @ConfigItem + Optional docExpansion; + + /** + * If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that + * are shown. + * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the + * filter expression. + * Filtering is case sensitive matching the filter expression anywhere inside the tag. + */ + @ConfigItem + Optional filter; + + /** + * If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations. + */ + @ConfigItem + OptionalInt maxDisplayedTags; + + /** + * Apply a sort to the operation list of each API. + * It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see + * Array.prototype.sort() to know how sort function works). + * Default is the order returned by the server unchanged. + */ + @ConfigItem + Optional operationsSorter; + + /** + * Controls the display of vendor extension (x-) fields and values for Operations, Parameters, and Schema. + */ + @ConfigItem + Optional showExtensions; + + /** + * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values for + * Parameters. + */ + @ConfigItem + Optional showCommonExtensions; + + /** + * Apply a sort to the tag list of each API. + * It can be 'alpha' (sort by paths alphanumerically) or a function (see Array.prototype.sort() to learn how to write a + * sort function). + * Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger UI. + */ + @ConfigItem + Optional tagsSorter; + + /** + * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. + */ + @ConfigItem + Optional onComplete; + + /** + * Set to false to deactivate syntax highlighting of payloads and cURL command, can be otherwise an object with the + * activate and theme properties. + */ + @ConfigItem + Optional syntaxHighlight; + + /** + * OAuth redirect URL. + */ + @ConfigItem + Optional oauth2RedirectUrl; + + /** + * MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 requests. + * Accepts one argument requestInterceptor(request) and must return the modified request, or a Promise that resolves to + * the modified request. + */ + @ConfigItem + Optional requestInterceptor; + + /** + * If set, MUST be an array of command line options available to the curl command. + * This can be set on the mutated request in the requestInterceptor function. + */ + @ConfigItem + Optional> requestCurlOptions; + + /** + * MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 responses. + * Accepts one argument responseInterceptor(response) and must return the modified response, or a Promise that resolves + * to the modified response. + */ + @ConfigItem + Optional responseInterceptor; + + /** + * If set to true, uses the mutated request returned from a requestInterceptor to produce the curl command in the UI, + * otherwise the request before the requestInterceptor was applied is used. + */ + @ConfigItem + Optional showMutatedRequest; + + /** + * List of HTTP methods that have the "Try it out" feature enabled. + * An empty array disables "Try it out" for all operations. This does not filter the operations from the display. + */ + @ConfigItem + Optional> supportedSubmitMethods; + + /** + * By default, Swagger UI attempts to validate specs against swagger.io's online validator. + * You can use this parameter to set a different validator URL, for example for locally deployed validators (Validator + * Badge). + * Setting it to either none, 127.0.0.1 or localhost will disable validation. + */ + @ConfigItem + Optional validatorUrl; + + /** + * If set to true, enables passing credentials, as defined in the Fetch standard, in CORS requests that are sent by the + * browser. + */ + @ConfigItem + Optional withCredentials; + + /** + * Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property + * is immutable + */ + @ConfigItem + Optional modelPropertyMacro; + + /** + * Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). + * Operation and parameter are objects passed for context, both remain immutable + */ + @ConfigItem + Optional parameterMacro; + + /** + * If set to true, it persists authorization data and it would not be lost on browser close/refresh + */ + @ConfigItem + Optional persistAuthorization; + + /** + * The name of a component available via the plugin system to use as the top-level layout for Swagger UI. + */ + @ConfigItem + Optional layout; + + /** + * A list of plugin functions to use in Swagger UI. + */ + @ConfigItem + Optional> plugins; + + /** + * A list of presets to use in Swagger UI. + */ + @ConfigItem + Optional> presets; +} diff --git a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java index 812b7afd3a03d..75585f51fbf72 100644 --- a/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java +++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java @@ -3,14 +3,8 @@ import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.OptionalInt; -import javax.inject.Inject; - -import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.builder.Version; import io.quarkus.deployment.Feature; @@ -25,15 +19,12 @@ import io.quarkus.deployment.configuration.ConfigurationError; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.util.WebJarUtil; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig; import io.quarkus.swaggerui.runtime.SwaggerUiRecorder; +import io.quarkus.swaggerui.runtime.SwaggerUiRuntimeConfig; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; -import io.smallrye.openapi.ui.DocExpansion; -import io.smallrye.openapi.ui.HttpMethod; import io.smallrye.openapi.ui.IndexCreator; import io.smallrye.openapi.ui.Option; import io.smallrye.openapi.ui.ThemeHref; @@ -47,84 +38,89 @@ public class SwaggerUiProcessor { private static final String SWAGGER_UI_WEBJAR_PREFIX = "META-INF/resources/openapi-ui/"; private static final String SWAGGER_UI_FINAL_DESTINATION = "META-INF/swagger-ui-files"; - /** - * The configuration for Swagger UI. - */ - SwaggerUiConfig swaggerUiConfig; - - SmallRyeOpenApiConfig openapi; - - @Inject - private LaunchModeBuildItem launch; - @BuildStep - void feature(BuildProducer feature) { - if (swaggerUiConfig.enable && (launch.getLaunchMode().isDevOrTest() || swaggerUiConfig.alwaysInclude)) { + void feature(BuildProducer feature, + LaunchModeBuildItem launchMode, + SwaggerUiConfig swaggerUiConfig) { + if (shouldInclude(launchMode, swaggerUiConfig)) { feature.produce(new FeatureBuildItem(Feature.SWAGGER_UI)); } } @BuildStep - @Record(ExecutionTime.STATIC_INIT) - public void registerSwaggerUiServletExtension(SwaggerUiRecorder recorder, - BuildProducer routes, - BeanContainerBuildItem container, + public void getSwaggerUiFinalDestination( BuildProducer generatedResources, BuildProducer nativeImageResourceBuildItemBuildProducer, + BuildProducer swaggerUiBuildProducer, HttpRootPathBuildItem httpRootPathBuildItem, BuildProducer displayableEndpoints, - CurateOutcomeBuildItem curateOutcomeBuildItem) throws Exception { - - if ("/".equals(swaggerUiConfig.path)) { - throw new ConfigurationError( - "quarkus.swagger-ui.path was set to \"/\", this is not allowed as it blocks the application from serving anything else."); - } - - if (!swaggerUiConfig.enable) { - return; - } - - String openApiPath = httpRootPathBuildItem.adjustPath(openapi.path); - AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, SWAGGER_UI_WEBJAR_GROUP_ID, - SWAGGER_UI_WEBJAR_ARTIFACT_ID); - - if (launch.getLaunchMode().isDevOrTest()) { - Path tempPath = WebJarUtil.devOrTest(curateOutcomeBuildItem, launch, artifact, SWAGGER_UI_WEBJAR_PREFIX); - // Update index.html - WebJarUtil.updateFile(tempPath.resolve("index.html"), generateIndexHtml(openApiPath)); + CurateOutcomeBuildItem curateOutcomeBuildItem, + LaunchModeBuildItem launchMode, + SwaggerUiConfig swaggerUiConfig, + SmallRyeOpenApiConfig openapi) throws Exception { + + if (shouldInclude(launchMode, swaggerUiConfig)) { + if ("/".equals(swaggerUiConfig.path)) { + throw new ConfigurationError( + "quarkus.swagger-ui.path was set to \"/\", this is not allowed as it blocks the application from serving anything else."); + } - Handler handler = recorder.handler(tempPath.toAbsolutePath().toString(), - httpRootPathBuildItem.adjustPath(swaggerUiConfig.path)); - routes.produce(new RouteBuildItem(swaggerUiConfig.path, handler)); - routes.produce(new RouteBuildItem(swaggerUiConfig.path + "/*", handler)); - displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(swaggerUiConfig.path + "/")); - } else if (swaggerUiConfig.alwaysInclude) { - Map files = WebJarUtil.production(curateOutcomeBuildItem, artifact, SWAGGER_UI_WEBJAR_PREFIX); - ThemeHref theme = swaggerUiConfig.theme.orElse(ThemeHref.feeling_blue); - for (Map.Entry file : files.entrySet()) { - String fileName = file.getKey(); - // Make sure to only include the selected theme - if (fileName.equals(theme.toString()) || !fileName.startsWith("theme-")) { - byte[] content; - if (fileName.endsWith("index.html")) { - content = generateIndexHtml(openApiPath); - } else { - content = file.getValue(); + String openApiPath = httpRootPathBuildItem.adjustPath(openapi.path); + AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, SWAGGER_UI_WEBJAR_GROUP_ID, + SWAGGER_UI_WEBJAR_ARTIFACT_ID); + + if (launchMode.getLaunchMode().isDevOrTest()) { + Path tempPath = WebJarUtil.devOrTest(curateOutcomeBuildItem, launchMode, artifact, SWAGGER_UI_WEBJAR_PREFIX); + // Update index.html + WebJarUtil.updateFile(tempPath.resolve("index.html"), generateIndexHtml(openApiPath, swaggerUiConfig)); + + swaggerUiBuildProducer.produce(new SwaggerUiBuildItem(tempPath.toAbsolutePath().toString(), + httpRootPathBuildItem.adjustPath(swaggerUiConfig.path))); + displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(swaggerUiConfig.path + "/")); + } else { + Map files = WebJarUtil.production(curateOutcomeBuildItem, artifact, SWAGGER_UI_WEBJAR_PREFIX); + ThemeHref theme = swaggerUiConfig.theme.orElse(ThemeHref.feeling_blue); + for (Map.Entry file : files.entrySet()) { + String fileName = file.getKey(); + // Make sure to only include the selected theme + if (fileName.equals(theme.toString()) || !fileName.startsWith("theme-")) { + byte[] content; + if (fileName.endsWith("index.html")) { + content = generateIndexHtml(openApiPath, swaggerUiConfig); + } else { + content = file.getValue(); + } + fileName = SWAGGER_UI_FINAL_DESTINATION + "/" + fileName; + generatedResources.produce(new GeneratedResourceBuildItem(fileName, content)); + nativeImageResourceBuildItemBuildProducer.produce(new NativeImageResourceBuildItem(fileName)); } - fileName = SWAGGER_UI_FINAL_DESTINATION + "/" + fileName; - generatedResources.produce(new GeneratedResourceBuildItem(fileName, content)); - nativeImageResourceBuildItemBuildProducer.produce(new NativeImageResourceBuildItem(fileName)); } + swaggerUiBuildProducer.produce(new SwaggerUiBuildItem(SWAGGER_UI_FINAL_DESTINATION, + httpRootPathBuildItem.adjustPath(swaggerUiConfig.path))); } + } + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + public void registerSwaggerUiHandler(SwaggerUiRecorder recorder, + BuildProducer routes, + SwaggerUiBuildItem finalDestinationBuildItem, + SwaggerUiRuntimeConfig runtimeConfig, + LaunchModeBuildItem launchMode, + SwaggerUiConfig swaggerUiConfig) throws Exception { + + if (shouldInclude(launchMode, swaggerUiConfig)) { + Handler handler = recorder.handler(finalDestinationBuildItem.getSwaggerUiFinalDestination(), + finalDestinationBuildItem.getSwaggerUiPath(), + runtimeConfig); - Handler handler = recorder - .handler(SWAGGER_UI_FINAL_DESTINATION, httpRootPathBuildItem.adjustPath(swaggerUiConfig.path)); routes.produce(new RouteBuildItem(swaggerUiConfig.path, handler)); routes.produce(new RouteBuildItem(swaggerUiConfig.path + "/*", handler)); } } - private byte[] generateIndexHtml(String openApiPath) throws IOException { + private byte[] generateIndexHtml(String openApiPath, SwaggerUiConfig swaggerUiConfig) throws IOException { Map options = new HashMap<>(); Map urlsMap = null; @@ -274,257 +270,7 @@ private byte[] generateIndexHtml(String openApiPath) throws IOException { return IndexCreator.createIndexHtml(urlsMap, swaggerUiConfig.urlsPrimaryName.orElse(null), options); } - @ConfigRoot - static final class SwaggerUiConfig { - /** - * The path where Swagger UI is available. - *

- * The value `/` is not allowed as it blocks the application from serving anything else. - */ - @ConfigItem(defaultValue = "/swagger-ui") - String path; - - /** - * If this should be included every time. By default this is only included when the application is running - * in dev mode. - */ - @ConfigItem - boolean alwaysInclude; - - /** - * If Swagger UI should be enabled. By default, Swagger UI is enabled. - */ - @ConfigItem(defaultValue = "true") - boolean enable; - - /** - * The urls that will be included as options. By default the OpenAPI path will be used. - * Here you can override that and supply multiple urls that will appear in the TopBar plugin. - */ - @ConfigItem - Map urls; - - /** - * If urls option is used, this will be the name of the default selection. - */ - @ConfigItem - Optional urlsPrimaryName; - - /** - * The html title for the page. - */ - @ConfigItem - Optional title; - - /** - * Swagger UI theme to be used. - */ - @ConfigItem - Optional theme; - - /** - * A footer for the html page. Nothing by default. - */ - @ConfigItem - Optional footer; - - /** - * If set to true, enables deep linking for tags and operations. - */ - @ConfigItem - Optional deepLinking; - - /** - * Controls the display of operationId in operations list. The default is false. - */ - @ConfigItem - Optional displayOperationId; - - /** - * The default expansion depth for models (set to -1 completely hide the models). - */ - @ConfigItem - OptionalInt defaultModelsExpandDepth; - - /** - * The default expansion depth for the model on the model-example section. - */ - @ConfigItem - OptionalInt defaultModelExpandDepth; - - /** - * Controls how the model is shown when the API is first rendered. - */ - @ConfigItem - Optional defaultModelRendering; - - /** - * Controls the display of the request duration (in milliseconds) for "Try it out" requests. - */ - @ConfigItem - Optional displayRequestDuration; - - /** - * Controls the default expansion setting for the operations and tags. - */ - @ConfigItem - Optional docExpansion; - - /** - * If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that - * are shown. - * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled using that string as the - * filter expression. - * Filtering is case sensitive matching the filter expression anywhere inside the tag. - */ - @ConfigItem - Optional filter; - - /** - * If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations. - */ - @ConfigItem - OptionalInt maxDisplayedTags; - - /** - * Apply a sort to the operation list of each API. - * It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see - * Array.prototype.sort() to know how sort function works). - * Default is the order returned by the server unchanged. - */ - @ConfigItem - Optional operationsSorter; - - /** - * Controls the display of vendor extension (x-) fields and values for Operations, Parameters, and Schema. - */ - @ConfigItem - Optional showExtensions; - - /** - * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values for - * Parameters. - */ - @ConfigItem - Optional showCommonExtensions; - - /** - * Apply a sort to the tag list of each API. - * It can be 'alpha' (sort by paths alphanumerically) or a function (see Array.prototype.sort() to learn how to write a - * sort function). - * Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger UI. - */ - @ConfigItem - Optional tagsSorter; - - /** - * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. - */ - @ConfigItem - Optional onComplete; - - /** - * Set to false to deactivate syntax highlighting of payloads and cURL command, can be otherwise an object with the - * activate and theme properties. - */ - @ConfigItem - Optional syntaxHighlight; - - /** - * OAuth redirect URL. - */ - @ConfigItem - Optional oauth2RedirectUrl; - - /** - * MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 requests. - * Accepts one argument requestInterceptor(request) and must return the modified request, or a Promise that resolves to - * the modified request. - */ - @ConfigItem - Optional requestInterceptor; - - /** - * If set, MUST be an array of command line options available to the curl command. - * This can be set on the mutated request in the requestInterceptor function. - */ - @ConfigItem - Optional> requestCurlOptions; - - /** - * MUST be a function. Function to intercept remote definition, "Try it out", and OAuth 2.0 responses. - * Accepts one argument responseInterceptor(response) and must return the modified response, or a Promise that resolves - * to the modified response. - */ - @ConfigItem - Optional responseInterceptor; - - /** - * If set to true, uses the mutated request returned from a requestInterceptor to produce the curl command in the UI, - * otherwise the request before the requestInterceptor was applied is used. - */ - @ConfigItem - Optional showMutatedRequest; - - /** - * List of HTTP methods that have the "Try it out" feature enabled. - * An empty array disables "Try it out" for all operations. This does not filter the operations from the display. - */ - @ConfigItem - Optional> supportedSubmitMethods; - - /** - * By default, Swagger UI attempts to validate specs against swagger.io's online validator. - * You can use this parameter to set a different validator URL, for example for locally deployed validators (Validator - * Badge). - * Setting it to either none, 127.0.0.1 or localhost will disable validation. - */ - @ConfigItem - Optional validatorUrl; - - /** - * If set to true, enables passing credentials, as defined in the Fetch standard, in CORS requests that are sent by the - * browser. - */ - @ConfigItem - Optional withCredentials; - - /** - * Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property - * is immutable - */ - @ConfigItem - Optional modelPropertyMacro; - - /** - * Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). - * Operation and parameter are objects passed for context, both remain immutable - */ - @ConfigItem - Optional parameterMacro; - - /** - * If set to true, it persists authorization data and it would not be lost on browser close/refresh - */ - @ConfigItem - Optional persistAuthorization; - - /** - * The name of a component available via the plugin system to use as the top-level layout for Swagger UI. - */ - @ConfigItem - Optional layout; - - /** - * A list of plugin functions to use in Swagger UI. - */ - @ConfigItem - Optional> plugins; - - /** - * A list of presets to use in Swagger UI. - */ - @ConfigItem - Optional> presets; + private static boolean shouldInclude(LaunchModeBuildItem launchMode, SwaggerUiConfig swaggerUiConfig) { + return launchMode.getLaunchMode().isDevOrTest() || swaggerUiConfig.alwaysInclude; } } diff --git a/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiNotFoundHandler.java b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiNotFoundHandler.java new file mode 100644 index 0000000000000..157b73e71442b --- /dev/null +++ b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiNotFoundHandler.java @@ -0,0 +1,17 @@ +package io.quarkus.swaggerui.runtime; + +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Handling static when disabled + */ +public class SwaggerUiNotFoundHandler implements Handler { + + @Override + public void handle(RoutingContext event) { + event.response().setStatusCode(404); + event.response().end(); + } + +} diff --git a/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRecorder.java b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRecorder.java index 745e8122f276b..697bf8f559a75 100644 --- a/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRecorder.java +++ b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRecorder.java @@ -2,34 +2,18 @@ import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; -import io.vertx.core.http.HttpHeaders; import io.vertx.ext.web.RoutingContext; -import io.vertx.ext.web.handler.StaticHandler; @Recorder public class SwaggerUiRecorder { - public Handler handler(String swaggerUiFinalDestination, String swaggerUiPath) { - StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true) - .setWebRoot(swaggerUiFinalDestination) - .setDefaultContentEncoding("UTF-8"); + public Handler handler(String swaggerUiFinalDestination, String swaggerUiPath, + SwaggerUiRuntimeConfig runtimeConfig) { - return new Handler() { - @Override - public void handle(RoutingContext event) { - if (event.normalisedPath().length() == swaggerUiPath.length()) { - - event.response().setStatusCode(302); - event.response().headers().set(HttpHeaders.LOCATION, swaggerUiPath + "/"); - event.response().end(); - return; - } else if (event.normalisedPath().length() == swaggerUiPath.length() + 1) { - event.reroute(swaggerUiPath + "/index.html"); - return; - } - - staticHandler.handle(event); - } - }; + if (runtimeConfig.enable) { + return new SwaggerUiStaticHandler(swaggerUiFinalDestination, swaggerUiPath); + } else { + return new SwaggerUiNotFoundHandler(); + } } } diff --git a/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRuntimeConfig.java b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRuntimeConfig.java new file mode 100644 index 0000000000000..41f1822224d39 --- /dev/null +++ b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiRuntimeConfig.java @@ -0,0 +1,17 @@ +package io.quarkus.swaggerui.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public class SwaggerUiRuntimeConfig { + + /** + * If Swagger UI is included, it should be enabled/disabled. By default, Swagger UI is enabled if it is included (see + * {@code always-include}). + */ + @ConfigItem(defaultValue = "true") + boolean enable; + +} diff --git a/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiStaticHandler.java b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiStaticHandler.java new file mode 100644 index 0000000000000..ef45f6a73ff06 --- /dev/null +++ b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiStaticHandler.java @@ -0,0 +1,59 @@ +package io.quarkus.swaggerui.runtime; + +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.StaticHandler; + +/** + * Handling static Swagger UI content + */ +public class SwaggerUiStaticHandler implements Handler { + + private String swaggerUiFinalDestination; + private String swaggerUiPath; + + public SwaggerUiStaticHandler() { + } + + public SwaggerUiStaticHandler(String swaggerUiFinalDestination, String swaggerUiPath) { + this.swaggerUiFinalDestination = swaggerUiFinalDestination; + this.swaggerUiPath = swaggerUiPath; + } + + public String getSwaggerUiFinalDestination() { + return swaggerUiFinalDestination; + } + + public void setSwaggerUiFinalDestination(String swaggerUiFinalDestination) { + this.swaggerUiFinalDestination = swaggerUiFinalDestination; + } + + public String getSwaggerUiPath() { + return swaggerUiPath; + } + + public void setSwaggerUiPath(String swaggerUiPath) { + this.swaggerUiPath = swaggerUiPath; + } + + @Override + public void handle(RoutingContext event) { + StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true) + .setWebRoot(swaggerUiFinalDestination) + .setDefaultContentEncoding("UTF-8"); + + if (event.normalisedPath().length() == swaggerUiPath.length()) { + event.response().setStatusCode(302); + event.response().headers().set(HttpHeaders.LOCATION, swaggerUiPath + "/"); + event.response().end(); + return; + } else if (event.normalisedPath().length() == swaggerUiPath.length() + 1) { + event.reroute(swaggerUiPath + "/index.html"); + return; + } + + staticHandler.handle(event); + } + +}