diff --git a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java index b87f5c19a4029..c8882b07d9f1d 100644 --- a/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java +++ b/extensions/info/deployment/src/main/java/io/quarkus/info/deployment/InfoProcessor.java @@ -46,7 +46,7 @@ import io.quarkus.info.runtime.spi.InfoContributor; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; -import io.quarkus.vertx.http.deployment.RouteBuildItem; +import io.quarkus.vertx.http.deployment.spi.RouteBuildItem; public class InfoProcessor { @@ -283,14 +283,12 @@ RouteBuildItem defineRoute(InfoBuildTimeConfig buildTimeConfig, List infoContributors = contributors.stream() .map(InfoBuildTimeContributorBuildItem::getInfoContributor) .collect(Collectors.toList()); + unremovableBeanBuildItemBuildProducer.produce(UnremovableBeanBuildItem.beanTypes(InfoContributor.class)); - return nonApplicationRootPathBuildItem.routeBuilder() - .management() - .route(buildTimeConfig.path()) - .routeConfigKey("quarkus.info.path") - .handler(recorder.handler(buildTimeInfo, infoContributors)) - .displayOnNotFoundPage() - .blockingRoute() + + return RouteBuildItem.newManagementRoute(buildTimeConfig.path()) + .withRoutePathConfigKey("quarkus.info.path") + .withRequestHandler(recorder.handler(buildTimeInfo, infoContributors)) .build(); } } 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 c9a12f3a1264a..b5308159a27d3 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 @@ -107,9 +107,9 @@ import io.quarkus.vertx.http.deployment.FilterBuildItem; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; -import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.deployment.SecurityInformationBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; +import io.quarkus.vertx.http.deployment.spi.RouteBuildItem; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig; import io.quarkus.vertx.http.runtime.management.ManagementInterfaceConfiguration; import io.smallrye.openapi.api.OpenApiConfig; @@ -305,32 +305,31 @@ void handler(LaunchModeBuildItem launch, } } - routes.produce(nonApplicationRootPathBuildItem.routeBuilder() - .management("quarkus.smallrye-openapi.management.enabled") - .routeFunction(openApiConfig.path, corsFilter) - .routeConfigKey("quarkus.smallrye-openapi.path") - .handler(handler) + routes.produce(RouteBuildItem.newManagementRoute(openApiConfig.path, "quarkus.smallrye-openapi.management.enabled") + .withRouteCustomizer(corsFilter) + .withRoutePathConfigKey("quarkus.smallrye-openapi.path") + .withRequestHandler(handler) .displayOnNotFoundPage("Open API Schema document") - .blockingRoute() + .asBlockingRoute() .build()); - routes.produce(nonApplicationRootPathBuildItem.routeBuilder() - .management("quarkus.smallrye-openapi.management.enabled") - .routeFunction(openApiConfig.path + ".json", corsFilter) - .handler(handler) - .build()); - - routes.produce(nonApplicationRootPathBuildItem.routeBuilder() - .management("quarkus.smallrye-openapi.management.enabled") - .routeFunction(openApiConfig.path + ".yaml", corsFilter) - .handler(handler) - .build()); - - routes.produce(nonApplicationRootPathBuildItem.routeBuilder() - .management("quarkus.smallrye-openapi.management.enabled") - .routeFunction(openApiConfig.path + ".yml", corsFilter) - .handler(handler) - .build()); + routes.produce( + RouteBuildItem.newManagementRoute(openApiConfig.path + ".json", "quarkus.smallrye-openapi.management.enabled") + .withRouteCustomizer(corsFilter) + .withRequestHandler(handler) + .build()); + + routes.produce( + RouteBuildItem.newManagementRoute(openApiConfig.path + ".yaml", "quarkus.smallrye-openapi.management.enabled") + .withRouteCustomizer(corsFilter) + .withRequestHandler(handler) + .build()); + + routes.produce( + RouteBuildItem.newManagementRoute(openApiConfig.path + ".yml", "quarkus.smallrye-openapi.management.enabled") + .withRouteCustomizer(corsFilter) + .withRequestHandler(handler) + .build()); // If management is enabled and swagger-ui is part of management, we need to add CORS so that swagger can hit the endpoint if (isManagement(managementInterfaceBuildTimeConfig, openApiConfig, launch)) { diff --git a/extensions/vertx-http/deployment-spi/pom.xml b/extensions/vertx-http/deployment-spi/pom.xml index b56cb27d89a19..6b7d3426684ff 100644 --- a/extensions/vertx-http/deployment-spi/pom.xml +++ b/extensions/vertx-http/deployment-spi/pom.xml @@ -18,6 +18,11 @@ io.quarkus quarkus-core-deployment + + + io.vertx + vertx-web + diff --git a/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/RouteBuildItem.java b/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/RouteBuildItem.java new file mode 100644 index 0000000000000..d43538ce33c76 --- /dev/null +++ b/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/RouteBuildItem.java @@ -0,0 +1,366 @@ +package io.quarkus.vertx.http.deployment.spi; + +import java.util.OptionalInt; +import java.util.function.Consumer; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.builder.item.MultiBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.Route; +import io.vertx.ext.web.RoutingContext; + +/** + * A build item that represents a route that should be added to the router. + *

+ * Producing this build item does not mean the HTTP server is available. + * It will be consumed if the Quarkus Vert.x HTTP extension is present. + */ +public final class RouteBuildItem extends MultiBuildItem { + + /** + * The type of route handler + */ + public enum HandlerType { + + /** + * A regular route handler invoked on the event loop. + * + * @see io.vertx.ext.web.Route#handler(Handler) + */ + NORMAL, + /** + * A blocking route handler, invoked on a worker thread. + * + * @see io.vertx.ext.web.Route#blockingHandler(Handler) + */ + BLOCKING, + /** + * A failure handler, invoked when an exception is thrown from a route handler. + * This is invoked on the event loop. + * + * @see io.vertx.ext.web.Route#failureHandler(Handler) + */ + FAILURE + + } + + /** + * Type of routes. + */ + public enum RouteType { + + /** + * Framework routes are provided by the Quarkus framework (or extensions). + * They are not related to the application business logic, but provide a non-functional feature (health, metrics...). + *

+ * Framework route can be mounted on the application router (under the non application route path) or on the management + * router when enabled. + */ + FRAMEWORK_ROUTE, + /** + * Application routes are part of the application business logic. + * They are mounted on the application router (so the application prefix is applied). + */ + APPLICATION_ROUTE, + /** + * Absolute routes are part of the application business logic, and are mounted on the root router (exposed on /). + */ + ABSOLUTE_ROUTE + } + + private RouteType typeOfRoute = RouteType.APPLICATION_ROUTE; + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private OptionalInt order = OptionalInt.empty(); + + private String path; + private Consumer customizer; + + private boolean isManagement; + + private Handler handler; + + private HandlerType typeOfHandler = HandlerType.NORMAL; + + private boolean displayOnNotFoundPage; + private String notFoundPageTitle; + + private String routeConfigKey; + + public RouteType getTypeOfRoute() { + return typeOfRoute; + } + + public boolean hasOrder() { + return order.isPresent(); + } + + public int getOrder() { + if (order.isPresent()) { + return order.getAsInt(); + } else { + throw new IllegalStateException("No order set"); + } + } + + public boolean hasRouteConfigKey() { + return routeConfigKey != null; + } + + public String getRouteConfigKey() { + return routeConfigKey; + } + + public Handler getHandler() { + return handler; + } + + public HandlerType getHandlerType() { + return typeOfHandler; + } + + public String getPath() { + return path; + } + + public Consumer getCustomizer() { + return customizer; + } + + public String getNotFoundPageTitle() { + return notFoundPageTitle; + } + + public boolean isDisplayOnNotFoundPage() { + return displayOnNotFoundPage; + } + + /** + * Declares a new application route. + * Application routes are part of the application business logic and are mounted on the application router. + * The {@code quarkus.http.root-path} property is applied in front of the route path (if set). + * + * @param path the path, must not be {@code null} or empty + * @return the builder to configure the route + */ + public static Builder newApplicationRoute(String path) { + return new Builder(RouteType.APPLICATION_ROUTE, path, false); + } + + /** + * Declares a new absolute route. + * Application routes are part of the application business logic and are mounted at the root of the server. + * The {@code quarkus.http.root-path} property is not applied. + * + * @param path the path, must not be {@code null} or empty, and must start with a slash + * @return the builder to configure the route + */ + public static Builder newAbsoluteRoute(String path) { + return new Builder(RouteType.ABSOLUTE_ROUTE, path, false); + } + + /** + * Declares a new framework route. + * A framework route is provided by the Quarkus framework (or extensions). + *

+ * The {@code quarkus.http.non-application-root-path} property is applied in front of the route path (defaults to + * {@code /q}). + *

+ * The declared route is not considered as a management route, meaning it will be mounted on the application router + * and exposed on the main HTTP server. See {@link #newManagementRoute(String)} to declare a management route. + * + * @param path the path, must not be {@code null} or empty. + * @return the builder to configure the route + */ + public static Builder newFrameworkRoute(String path) { + return new Builder(RouteType.FRAMEWORK_ROUTE, path, false); + } + + /** + * Declares a new management route. + *

+ * A management route is provided by the Quarkus framework (or extensions), and unlike routes declared with + * {@link #newFrameworkRoute(String)}, + * are mounted on the management router (exposed on the management HTTP server) when the management interface is + * enabled (see the management interface + * documentation for further details). + *

+ * If the management interface is not enabled, the {@code quarkus.http.non-application-root-path} property is applied in + * front of the route path (defaults to {@code /q}). + * If the management interface is enabled, the {@code quarkus.management.root-path} property is applied in front of the + * route path (also defaults to {@code /q} but exposed on another port, 9000 by default). + * + * @param path the path, must not be {@code null} or empty. + * @return the builder to configure the route + */ + public static Builder newManagementRoute(String path) { + return new Builder(RouteType.FRAMEWORK_ROUTE, path, true); + } + + /** + * Declares a new framework route, conditionally considered as a management route depending on the value of the + * {@code managementConfigKey} property. + * + *

+ * The route is provided by the Quarkus framework (or extensions). Depending on the value associated to the + * {@code managementConfigKey} property, + * the route is either mounted to the application router (exposed on the main HTTP server) or on the management router + * (exposed on the management HTTP server). + * The property must be a boolean (set to {@code true} to expose the route on the management server or {@code false} to + * expose it on the main HTTP server). + *

+ * If the management interface is not enabled, regardless the value of the property, the route is exposed on the main HTTP + * server. + * The {@code quarkus.http.non-application-root-path} property is applied in front of the route path (defaults to + * {@code /q}). + *

+ * If the management interface is enabled and if the property is set to {@code true}, the route is exposed on the management + * server and the {@code quarkus.management.root-path} property is applied in front of the route path (also defaults to + * {@code /q} but exposed on another port, 9000 by default). + *

+ * If the management interface is enabled and if the property is set to {@code false}, the route is exposed on the main HTTP + * server. + * The {@code quarkus.http.non-application-root-path} property is applied in front of the route path (defaults to + * {@code /q}). + * + * @param path the path, must not be {@code null} or empty. + * @return the builder to configure the route + */ + public static Builder newManagementRoute(String path, String managementConfigKey) { + return new Builder(RouteType.FRAMEWORK_ROUTE, path, + (managementConfigKey == null || isManagement(managementConfigKey))); + } + + private static boolean isManagement(String managementConfigKey) { + Config config = ConfigProvider.getConfig(); + return config.getValue(managementConfigKey, boolean.class); + } + + public boolean isManagement() { + return isManagement; + } + + /** + * A builder to configure the route. + */ + public static class Builder { + + private final RouteBuildItem item; + + private Builder(RouteType type, String path, boolean isManagement) { + item = new RouteBuildItem(); + item.typeOfRoute = type; + item.path = path; + item.isManagement = isManagement; + } + + /** + * Sets a function to customize the route. + * + * @param customizer the customizer, must not be {@code null} + * @return the current builder + */ + public Builder withRouteCustomizer(Consumer customizer) { + item.customizer = customizer; + return this; + } + + /** + * Defines the route order. + * + * @param order the order + * @return the current builder + */ + public Builder withOrder(int order) { + item.order = OptionalInt.of(order); + return this; + } + + /** + * Sets the request handler (mandatory) + * + * @param handler the handler, must not be {@code null} + * @return the current builder + */ + public Builder withRequestHandler(Handler handler) { + item.handler = handler; + return this; + } + + /** + * Sets the route as a blocking route. + * A blocking route handler is invoked on a worker thread, and thus is allowed to block. + * + * @return the current builder + */ + public Builder asBlockingRoute() { + if (item.typeOfHandler == HandlerType.FAILURE) { + throw new IllegalArgumentException("A failure route cannot be a blocking route"); + } + item.typeOfHandler = HandlerType.BLOCKING; + return this; + } + + /** + * Sets the route as a failure route. + * A failure route handler is invoked when an exception is thrown from a route handler. + * + * @return the current builder + */ + public Builder asFailureRoute() { + if (item.typeOfHandler == HandlerType.BLOCKING) { + throw new IllegalArgumentException("A blocking route cannot be a failure route"); + } + item.typeOfHandler = HandlerType.FAILURE; + return this; + } + + /** + * Adds the route to the page returned when a 404 error is returned. + * + * @return the current builder + */ + public Builder displayOnNotFoundPage() { + item.displayOnNotFoundPage = true; + return this; + } + + /** + * Adds the route to the page returned when a 404 error is returned, and sets the title of the page. + * + * @param notFoundPageTitle the title of the route + * @return the current builder + */ + public Builder displayOnNotFoundPage(String notFoundPageTitle) { + item.displayOnNotFoundPage = true; + item.notFoundPageTitle = notFoundPageTitle; + return this; + } + + /** + * Sets a property configuring the route path. + * + * @param attributeName the name of the property configuring the route path + * @return the current builder + */ + public Builder withRoutePathConfigKey(String attributeName) { + item.routeConfigKey = attributeName; + return this; + } + + /** + * Validates the route and build the {@code RouteBuildItem}. + * + * @return the route build item + */ + public RouteBuildItem build() { + if (item.handler == null) { + throw new IllegalArgumentException("The route handler must be set"); + } + + return item; + } + } + +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java index d978518a6295e..3d6720009d289 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/NonApplicationRootPathBuildItem.java @@ -21,8 +21,6 @@ public final class NonApplicationRootPathBuildItem extends SimpleBuildItem { - // TODO Should be handle the management root path? - /** * Normalized of quarkus.http.root-path. * Must end in a slash diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteConverter.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteConverter.java new file mode 100644 index 0000000000000..606889a3d2f81 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteConverter.java @@ -0,0 +1,44 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.vertx.http.runtime.HandlerType; + +/** + * Convert the route build item from the SPI to the internal representation + */ +public class RouteConverter { + + public static RouteBuildItem convert(io.quarkus.vertx.http.deployment.spi.RouteBuildItem item, + HttpRootPathBuildItem httpRootPathBuildItem, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { + // The builder depends on the type of route + RouteBuildItem.Builder builder; + if (item.getTypeOfRoute() == io.quarkus.vertx.http.deployment.spi.RouteBuildItem.RouteType.FRAMEWORK_ROUTE) { + builder = nonApplicationRootPathBuildItem.routeBuilder(); + } else { + builder = httpRootPathBuildItem.routeBuilder(); + } + + if (item.isManagement()) { + builder = builder.management(); + } + if (item.hasRouteConfigKey()) { + builder = builder.routeConfigKey(item.getRouteConfigKey()); + } + + builder = builder.handler(item.getHandler()).handlerType(HandlerType.valueOf(item.getHandlerType().name())); + if (item.isDisplayOnNotFoundPage()) { + builder = builder + .displayOnNotFoundPage(item.getNotFoundPageTitle()); + } + + if (item.hasOrder()) { + builder = builder.orderedRoute(item.getPath(), item.getOrder(), item.getCustomizer()); + } else { + builder = builder.routeFunction(item.getPath(), item.getCustomizer()); + } + + return builder.build(); + + } + +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 0d6835565655e..dc19568107b9a 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -95,6 +95,19 @@ HttpRootPathBuildItem httpRoot(HttpBuildTimeConfig httpBuildTimeConfig) { return new HttpRootPathBuildItem(httpBuildTimeConfig.rootPath); } + @BuildStep + List convertRoutes( + List items, + HttpRootPathBuildItem httpRootPathBuildItem, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { + List list = new ArrayList<>(); + for (io.quarkus.vertx.http.deployment.spi.RouteBuildItem item : items) { + RouteBuildItem converted = RouteConverter.convert(item, httpRootPathBuildItem, nonApplicationRootPathBuildItem); + list.add(converted); + } + return list; + } + @BuildStep NonApplicationRootPathBuildItem frameworkRoot(HttpBuildTimeConfig httpBuildTimeConfig, ManagementInterfaceBuildTimeConfig managementBuildTimeConfig) { @@ -276,7 +289,7 @@ VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder, } } - /** + /* * To create mainrouter when `${quarkus.http.root-path}` is not {@literal /} * Refer https://github.com/quarkusio/quarkus/issues/34261 */ @@ -475,7 +488,7 @@ void registerExchangeAttributeBuilders(final BuildProducer "quarkus.http.insecure-requests" is not explicitly disabled * <2> any of the http SSL runtime properties are set at build time - * + *

* If any of the above rules applied, the port "https" will be generated as part of the Kubernetes resources. */ private static boolean isSslConfigured() { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HandlerType.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HandlerType.java index a8ae971d3a479..881dbb672bbef 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HandlerType.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HandlerType.java @@ -2,22 +2,26 @@ import io.vertx.core.Handler; +/** + * The type of route handler + */ public enum HandlerType { /** - * A request handler. + * A regular route handler invoked on the event loop. * * @see io.vertx.ext.web.Route#handler(Handler) */ NORMAL, /** - * A blocking request handler. + * A blocking route handler, invoked on a worker thread. * * @see io.vertx.ext.web.Route#blockingHandler(Handler) */ BLOCKING, /** - * A failure handler. + * A failure handler, invoked when an exception is thrown from a route handler. + * This is invoked on the event loop. * * @see io.vertx.ext.web.Route#failureHandler(Handler) */