diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java index bb6b858232c5a..3a629bf968b16 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java @@ -104,4 +104,12 @@ public static URI normalizeWithBase(URI base, String segment, boolean trailingSl URI resolvedUri = base.resolve(segmentUri); return resolvedUri; } + + public static String relativize(String rootPath, String leafPath) { + if (leafPath.startsWith(rootPath)) { + return leafPath.substring(rootPath.length()); + } + + return null; + } } diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java index b73e99541793b..46513f13edab0 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/AugmentActionImpl.java @@ -278,6 +278,7 @@ private BuildResult runAugment(boolean firstRun, Set changedResources, } builder.setLaunchMode(launchMode); + builder.setDevModeType(devModeType); builder.setRebuild(quarkusBootstrap.isRebuild()); if (firstRun) { builder.setLiveReloadState( diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 983f97e025e12..5042dbaf65f15 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -151,6 +151,7 @@ org.jacoco:org.jacoco.agent:runtime + org.graalvm.sdk:graal-sdk io.quarkus:quarkus-bootstrap-runner io.quarkus:quarkus-development-mode-spi diff --git a/docs/src/main/asciidoc/dev-ui.adoc b/docs/src/main/asciidoc/dev-ui.adoc index a788f9823678a..2f5cdc9940e1a 100644 --- a/docs/src/main/asciidoc/dev-ui.adoc +++ b/docs/src/main/asciidoc/dev-ui.adoc @@ -10,7 +10,7 @@ include::./attributes.adoc[] This guide covers the Quarkus Dev UI for link:building-my-first-extension[extension authors]. Quarkus now ships with a new experimental Dev UI, which is available in dev mode (when you start -quarkus with `mvn quarkus:dev`) at http://localhost:8080/q/dev[/q/dev] and will show you something like +quarkus with `mvn quarkus:dev`) at http://localhost:8080/q/dev[/q/dev] by default. It will show you something like this: image::dev-ui-overview.png[alt=Dev UI overview,role="center"] @@ -46,16 +46,56 @@ two links with some styling and icons: [source,html] ---- - - + + OpenAPI - - +
+
+ Swagger UI ---- TIP: We use the Font Awesome Free icon set. +Note how the paths are specified: `{config:http-path('quarkus.smallrye-openapi.path')}`. This is a special +directive that the quarkus dev console understands: it will replace that value with the resolved route +named 'quarkus.smallrye-openapi.path'. + +The corresponding non-application endpoint is declared using `.routeConfigKey` to associate the route with a name: + +[source,java] +---- + nonApplicationRootPathBuildItem.routeBuilder() + .route(openApiConfig.path) // <1> + .routeConfigKey("quarkus.smallrye-openapi.path") // <2> + ... + .build(); +---- +<1> The configured path is resolved into a valid route. +<2> The resolved route path is then associated with the key `quarkus.smallrye-openapi.path`. + +== Path considerations + +Paths are tricky business. Keep the following in mind: + +* Assume your UI will be nested under the dev endpoint. Do not provide a way to customize this without a strong reason. +* Never construct your own absolute paths. Adding a suffix to a known, normalized and resolved path is fine. + +Configured paths, like the `dev` endpoint used by the console or the SmallRye OpenAPI path shown in the example above, +need to be properly resolved against both `quarkus.http.root-path` and `quarkus.http.non-application-root-path`. +Use `NonApplicationRootPathBuildItem` or `HttpRootPathBuildItem` to construct endpoint routes and identify resolved +path values that can then be used in templates. + +The `{devRootAppend}` variable can also be used in templates to construct URLs for static dev console resources, for example: + +[source,html] +---- +Quarkus +---- + +Refer to the link:all-config#quarkus-vertx-http_quarkus.http.non-application-root-path[Quarkus Vertx HTTP configuration reference] +for details on how the non-application root path is configured. + == Template and styling support Both the `embedded.html` files and any full page you add in `/dev-templates` will be interpreted by @@ -75,6 +115,9 @@ A `config:property(name)` expression can be used to output the config value for The property name can be either a string literal or obtained dynamically by another expression. For example `{config:property('quarkus.lambda.handler')}` and `{config:property(foo.propertyName)}`. +Reminder: do not use this to retrieve raw configured path values. As shown above, use `{config:http-path(...)}` with +a known route configuration key when working with resource paths. + == Adding full pages To add full pages for your Dev UI extension such as this one: @@ -137,17 +180,19 @@ link:building-my-first-extension#description-of-a-quarkus-extension[`deployment` == Linking to your full-page templates -Every full-page template lives under the `/q/dev/{groupId}.{artifactId}/` URI (for example -`/q/dev/io.quarkus.quarkus-cache/`), so if you want to link -to them from your `embedded.html` file you can use the `urlbase` template parameter to point to them: +Full-page templates for extensions live under a pre-defined `{devRootAppend}/{groupId}.{artifactId}/` directory +that is referenced using the `urlbase` template parameter. Using configuration defaults, that would resolve to +`/q/dev/io.quarkus.quarkus-cache/`, as an example. -[source,java] +Use the `{urlbase}` template parameter to reference this folder in `embedded.html`: + +[source,html] ---- // <1> Caches {info:cacheInfos.size()} ---- -<1> Use the `urlbase` template parameter to point to where your full-page templates are located +<1> Use the `urlbase` template parameter to reference full-page templates for your extension == Passing information to your templates @@ -266,6 +311,7 @@ This can be done by adding another link:building-my-first-extension#deploying-th declare the action in your extension's link:building-my-first-extension#description-of-a-quarkus-extension[`deployment`] module: + [source,java] ---- package io.quarkus.cache.deployment.devconsole; @@ -290,11 +336,13 @@ public class DevConsoleProcessor { <1> Mark the recorder as optional, so it will only be invoked when in dev mode <2> Declare a `POST {urlbase}/caches` route handled by the given handler + Note: you can see <>. Now all you have to do is implement the recorder in your extension's link:building-my-first-extension#description-of-a-quarkus-extension[`runtime module`]: + [source,java] ---- package io.quarkus.cache.runtime.devconsole; @@ -338,6 +386,7 @@ public class CacheDevConsoleRecorder { <3> Don't forget to add a message for the user to let them know everything went fine <4> You can also add error messages + NOTE: Flash messages are handled by the `main` DEV template and will result in nice notifications for your users: diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java index 7c512dd094d44..19d0bd12d5e04 100644 --- a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/export/PrometheusRegistryProcessor.java @@ -65,7 +65,7 @@ void createPrometheusRoute(BuildProducer routes, .routeFunction(pConfig.path, recorder.route()) .handler(recorder.getHandler()) .requiresLegacyRedirect() - .displayOnNotFoundPage("Metrics", pConfig.path) + .displayOnNotFoundPage("Metrics") .build()); // Match paths that begin with the deployment path diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java index e3e7c99f4de3e..4d9bd542e92c6 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java @@ -29,6 +29,7 @@ import com.mongodb.event.ConnectionPoolListener; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; @@ -41,7 +42,6 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.annotations.Weak; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; @@ -116,10 +116,10 @@ List addExtensionPointsToNative(CodecProviderBuildItem } @BuildStep - public void mongoClientNames(ApplicationArchivesBuildItem applicationArchivesBuildItem, + public void mongoClientNames(BeanArchiveIndexBuildItem indexBuildItem, BuildProducer mongoClientName) { Set values = new HashSet<>(); - IndexView indexView = applicationArchivesBuildItem.getRootArchive().getIndex(); + IndexView indexView = indexBuildItem.getIndex(); addMongoClientNameValues(LEGACY_MONGO_CLIENT_ANNOTATION, indexView, values); addMongoClientNameValues(MONGO_CLIENT_ANNOTATION, indexView, values); for (String value : values) { @@ -163,6 +163,16 @@ MongoConnectionPoolListenerBuildItem setupMetrics( return null; } + @BuildStep + void additionalBeans(BuildProducer additionalBeans) { + // add the @MongoClientName class otherwise it won't registered as a qualifier + additionalBeans.produce( + AdditionalBeanBuildItem.builder().addBeanClass(io.quarkus.mongodb.runtime.MongoClientName.class).build()); + additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(MongoClientName.class).build()); + // make MongoClients an unremoveable bean + additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClasses(MongoClients.class).setUnremovable().build()); + } + @Record(STATIC_INIT) @BuildStep void build( @@ -175,22 +185,13 @@ void build( CommandListenerBuildItem commandListener, List connectionPoolListenerProvider, BuildProducer mongoConnections, - BuildProducer syntheticBeanBuildItemBuildProducer, - BuildProducer additionalBeans) { - - // add the @MongoClientName class otherwise it won't registered as a qualifier - additionalBeans.produce( - AdditionalBeanBuildItem.builder().addBeanClass(io.quarkus.mongodb.runtime.MongoClientName.class).build()); - additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(MongoClientName.class).build()); + BuildProducer syntheticBeanBuildItemBuildProducer) { List> poolListenerList = new ArrayList<>(connectionPoolListenerProvider.size()); for (MongoConnectionPoolListenerBuildItem item : connectionPoolListenerProvider) { poolListenerList.add(item.getConnectionPoolListener()); } - // make MongoClients an unremoveable bean - additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClasses(MongoClients.class).setUnremovable().build()); - // create MongoClientSupport as a synthetic bean as it's used in AbstractMongoClientProducer syntheticBeanBuildItemBuildProducer.produce(SyntheticBeanBuildItem.configure(MongoClientSupport.class) .scope(Singleton.class) diff --git a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java index 8be65bfaef81c..319d51f966a1c 100644 --- a/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java +++ b/extensions/redis-client/deployment/src/main/java/io/quarkus/redis/client/deployment/RedisClientProcessor.java @@ -16,13 +16,13 @@ import org.jboss.jandex.IndexView; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; @@ -80,13 +80,13 @@ RuntimeInitializedClassBuildItem initializeBulkTypeDuringRuntime() { @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - public void produceRedisClient(RedisClientRecorder recorder, ApplicationArchivesBuildItem applicationArchives, + public void produceRedisClient(RedisClientRecorder recorder, BeanArchiveIndexBuildItem indexBuildItem, BuildProducer syntheticBeans, VertxBuildItem vertxBuildItem) { Set clientNames = new HashSet<>(); clientNames.add(RedisClientUtil.DEFAULT_CLIENT); - IndexView indexView = applicationArchives.getRootArchive().getIndex(); + IndexView indexView = indexBuildItem.getIndex(); Collection clientAnnotations = indexView.getAnnotations(REDIS_CLIENT_ANNOTATION); for (AnnotationInstance annotation : clientAnnotations) { clientNames.add(annotation.value().asString()); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 7ef67365b415d..11704af473d1c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -582,10 +582,10 @@ public List scan(MethodInfo method, Map }); } - private String determineApplicationPath(IndexView index, String defaultPath) { + private String determineApplicationPath(IndexView index, Optional defaultPath) { Collection applicationPaths = index.getAnnotations(ResteasyReactiveDotNames.APPLICATION_PATH); if (applicationPaths.isEmpty()) { - return defaultPath; + return defaultPath.orElse("/"); } // currently we only examine the first class that is annotated with @ApplicationPath so best // fail if the user code has multiple such annotations instead of surprising the user diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveServerConfig.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveServerConfig.java index 5baba89e338c2..4c9721d275321 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveServerConfig.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveServerConfig.java @@ -1,5 +1,7 @@ package io.quarkus.resteasy.reactive.server.deployment; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -7,9 +9,12 @@ public class ResteasyReactiveServerConfig { /** - * Set this to override the default path for JAX-RS resources if there are no - * annotated application classes. + * Set this to define the application path that serves as the base URI for all + * JAX-RS resource URIs provided by {@code @Path} annotations when there are no + * {@code @ApplicationPath} annotations defined on {@code Application} classes. + *

+ * This value is always resolved relative to {@code quarkus.http.root-path}. */ - @ConfigItem(defaultValue = "/") - String path; + @ConfigItem + Optional path; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/HelloResource.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/HelloResource.java index 5ff8b528d8cb6..c172e743f13df 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/HelloResource.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/HelloResource.java @@ -3,11 +3,26 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; -@Path("/hello") +/** + * Per spec: + * + * Paths are relative. For an annotated class the base URI is the application path, see ApplicationPath. + * For an annotated method the base URI is the effective URI of the containing class. For the purposes of + * absolutizing a path against the base URI , a leading '/' in a path is ignored and base URIs are treated + * as if they ended in '/'. + * + */ +@Path("hello") public class HelloResource { @GET public String hello() { return "hello"; } + + @GET + @Path("/nested") + public String nested() { + return "world hello"; + } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RelativeRestPathTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RelativeRestPathTestCase.java new file mode 100644 index 0000000000000..8d7c22e65d5eb --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RelativeRestPathTestCase.java @@ -0,0 +1,29 @@ +package io.quarkus.resteasy.reactive.server.test.path; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class RelativeRestPathTestCase { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withConfigurationResource("empty.properties") + .overrideConfigKey("quarkus.rest.path", "foo") + .overrideConfigKey("quarkus.http.root-path", "/app") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(HelloResource.class)); + + @Test + public void testRestPath() { + RestAssured.basePath = "/"; + // This is expected behavior (relative path appended to HTTP root path) + RestAssured.when().get("/app/foo/hello").then().body(Matchers.is("hello")); + RestAssured.when().get("/app/foo/hello/nested").then().body(Matchers.is("world hello")); + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestApplicationPathTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestApplicationPathTestCase.java new file mode 100644 index 0000000000000..2bdc84d39bd7b --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestApplicationPathTestCase.java @@ -0,0 +1,45 @@ +package io.quarkus.resteasy.reactive.server.test.path; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class RestApplicationPathTestCase { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withConfigurationResource("empty.properties") + .overrideConfigKey("quarkus.rest.path", "/foo") + .overrideConfigKey("quarkus.http.root-path", "/app") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class, BarApp.class)); + + /** + * Using @ApplicationPath will overlay/replace `quarkus.rest.path`. + * Per spec: + * + * Identifies the application path that serves as the base URI for all resource + * URIs provided by Path. May only be applied to a subclass of Application. + * + * + * This path will also be relative to the configured HTTP root + */ + @ApplicationPath("/bar") + public static class BarApp extends Application { + } + + @Test + public void testRestPath() { + RestAssured.basePath = "/"; + RestAssured.when().get("/app/bar/hello").then().body(Matchers.is("hello")); + RestAssured.when().get("/app/bar/hello/nested").then().body(Matchers.is("world hello")); + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestPathTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestPathTestCase.java index dec30f56dd0e6..379f4c84644b5 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestPathTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/path/RestPathTestCase.java @@ -2,7 +2,6 @@ import org.hamcrest.Matchers; import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -14,15 +13,16 @@ public class RestPathTestCase { @RegisterExtension static QuarkusUnitTest test = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(HelloResource.class) - .addAsResource(new StringAsset("quarkus.rest.path=/foo"), "application.properties")); + .withConfigurationResource("empty.properties") + .overrideConfigKey("quarkus.rest.path", "/foo") + .overrideConfigKey("quarkus.http.root-path", "/app") + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(HelloResource.class)); @Test public void testRestPath() { - RestAssured.get("/hello") - .then().statusCode(404); - RestAssured.get("/foo/hello") - .then().statusCode(200).body(Matchers.equalTo("hello")); - + RestAssured.basePath = "/"; + RestAssured.when().get("/app/foo/hello").then().body(Matchers.is("hello")); + RestAssured.when().get("/app/foo/hello/nested").then().body(Matchers.is("world hello")); } } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/resources/empty.properties b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/resources/empty.properties new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java index 324367cca4bd6..249330862a9a7 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java @@ -132,22 +132,16 @@ public void handle(HttpServerFileUpload upload) { uploadCount.incrementAndGet(); String uploadedFileName = new File(MultipartFormVertxHandler.this.uploadsDirectory, UUID.randomUUID().toString()).getPath(); - upload.streamToFileSystem(uploadedFileName); + upload.exceptionHandler(new UploadExceptionHandler(rrContext)); + upload.streamToFileSystem(uploadedFileName).exceptionHandler(new UploadExceptionHandler(rrContext)) + .endHandler(new Handler() { + @Override + public void handle(Void event) { + uploadEnded(); + } + }); FileUploadImpl fileUpload = new FileUploadImpl(uploadedFileName, upload); fileUploads.add(fileUpload); - upload.exceptionHandler(new Handler() { - @Override - public void handle(Throwable t) { - MultipartFormVertxHandler.this.deleteFileUploads(); - rrContext.resume(new WebApplicationException(t, Response.Status.INTERNAL_SERVER_ERROR)); - } - }); - upload.endHandler(new Handler() { - @Override - public void handle(Void event) { - uploadEnded(); - } - }); } }); } @@ -221,5 +215,19 @@ private void deleteFileUploads() { } } } + + private class UploadExceptionHandler implements Handler { + private final ResteasyReactiveRequestContext rrContext; + + public UploadExceptionHandler(ResteasyReactiveRequestContext rrContext) { + this.rrContext = rrContext; + } + + @Override + public void handle(Throwable t) { + MultipartFormVertxHandler.this.deleteFileUploads(); + rrContext.resume(new WebApplicationException(t, Response.Status.INTERNAL_SERVER_ERROR)); + } + } } } diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java index ec2612bc1e34a..481963c940d1c 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLConfig.java @@ -11,9 +11,10 @@ public class SmallRyeGraphQLConfig { /** - * The rootPath under which queries will be served. Default to /graphql + * The rootPath under which queries will be served. Default to graphql + * By default, this value will be resolved as a path relative to `${quarkus.http.root-path}`. */ - @ConfigItem(defaultValue = "/graphql") + @ConfigItem(defaultValue = "graphql") String rootPath; /** 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 50a616dc55d9b..4eb7c535760b4 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 @@ -81,7 +81,7 @@ */ public class SmallRyeGraphQLProcessor { private static final Logger LOG = Logger.getLogger(SmallRyeGraphQLProcessor.class); - private static final String SCHEMA_PATH = "/schema.graphql"; + private static final String SCHEMA_PATH = "schema.graphql"; // For Service integration private static final String SERVICE_NOT_AVAILABLE_WARNING = "The %s property is true, but the %s extension is not present. SmallRye GraphQL %s will be disabled."; @@ -181,14 +181,15 @@ void buildExecutionService( @BuildStep void buildSchemaEndpoint( BuildProducer routeProducer, + HttpRootPathBuildItem httpRootPathBuildItem, SmallRyeGraphQLInitializedBuildItem graphQLInitializedBuildItem, SmallRyeGraphQLRecorder recorder, SmallRyeGraphQLConfig graphQLConfig) { Handler schemaHandler = recorder.schemaHandler(graphQLInitializedBuildItem.getInitialized()); - routeProducer.produce(new RouteBuildItem.Builder() - .route(graphQLConfig.rootPath + SCHEMA_PATH) + routeProducer.produce(httpRootPathBuildItem.routeBuilder() + .nestedRoute(graphQLConfig.rootPath, SCHEMA_PATH) .handler(schemaHandler) .displayOnNotFoundPage("MicroProfile GraphQL Schema") .blockingRoute() @@ -201,6 +202,7 @@ void buildSchemaEndpoint( @Consume(BeanContainerBuildItem.class) void buildExecutionEndpoint( BuildProducer routeProducer, + HttpRootPathBuildItem httpRootPathBuildItem, SmallRyeGraphQLInitializedBuildItem graphQLInitializedBuildItem, SmallRyeGraphQLRecorder recorder, ShutdownContextBuildItem shutdownContext, @@ -225,9 +227,10 @@ void buildExecutionEndpoint( Handler executionHandler = recorder.executionHandler(graphQLInitializedBuildItem.getInitialized(), allowGet); - routeProducer.produce(new RouteBuildItem.Builder() + routeProducer.produce(httpRootPathBuildItem.routeBuilder() .routeFunction(graphQLConfig.rootPath, recorder.routeFunction(bodyHandlerBuildItem.getHandler())) .handler(executionHandler) + .routeConfigKey("quarkus.smallrye-graphql.root-path") .displayOnNotFoundPage("MicroProfile GraphQL Endpoint") .blockingRoute() .build()); @@ -268,6 +271,7 @@ private Class[] getGraphQLJavaClasses() { classes.add(graphql.schema.GraphQLSchema.class); classes.add(graphql.schema.GraphQLTypeReference.class); classes.add(List.class); + classes.add(Collection.class); return classes.toArray(new Class[] {}); } @@ -527,6 +531,7 @@ void registerGraphQLUiHandler( smallRyeGraphQLBuildItem.getGraphqlUiPath(), runtimeConfig); routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(graphQLConfig.ui.rootPath) + .routeConfigKey("quarkus.smallrye-graphql.ui.root-path") .displayOnNotFoundPage("MicroProfile GraphQL UI") .handler(handler) .requiresLegacyRedirect() 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 19cee4b8415a6..f90f7aef7f132 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 @@ -9,6 +9,7 @@ public class SmallRyeGraphQLUIConfig { /** * The path where GraphQL UI is available. * The value `/` is not allowed as it blocks the application from serving anything else. + * By default, this URL will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "graphql-ui") String rootPath; diff --git a/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html b/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html index 60015ff1e292d..772d905b9902b 100644 --- a/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/smallrye-graphql/deployment/src/main/resources/dev-templates/embedded.html @@ -1,7 +1,7 @@ - + GraphQL Schema
- + GraphQL UI diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java index de652acb15373..96051e54fbc61 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/HealthOpenAPIFilter.java @@ -125,14 +125,15 @@ private Operation createReadinessOperation() { private APIResponses createAPIResponses() { APIResponses responses = new APIResponsesImpl(); - responses.addAPIResponse("200", createAPIResponse()); - responses.addAPIResponse("503", createAPIResponse()); - responses.addAPIResponse("500", createAPIResponse()); + responses.addAPIResponse("200", createAPIResponse("OK")); + responses.addAPIResponse("503", createAPIResponse("Service Unavailable")); + responses.addAPIResponse("500", createAPIResponse("Internal Server Error")); return responses; } - private APIResponse createAPIResponse() { + private APIResponse createAPIResponse(String description) { APIResponse response = new APIResponseImpl(); + response.setDescription(description); response.setContent(createContent()); return response; } diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java index 9a80dcc1bed0a..7cb60dfabf3fd 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthConfig.java @@ -9,30 +9,35 @@ public class SmallRyeHealthConfig { /** * Root path for health-checking endpoints. + * By default, this value will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "health") String rootPath; /** * The relative path of the liveness health-checking endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "live") String livenessPath; /** * The relative path of the readiness health-checking endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "ready") String readinessPath; /** * The relative path of the health group endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "group") String groupPath; /** * The relative path of the wellness health-checking endpoint. + * By default, this value will be resolved as a path relative to `${quarkus.smallrye-health.rootPath}`. */ @ConfigItem(defaultValue = "well") String wellnessPath; 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 571a7a321c72b..4bfcd89cd8f42 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 @@ -69,11 +69,8 @@ import io.quarkus.smallrye.health.runtime.SmallRyeReadinessHandler; import io.quarkus.smallrye.health.runtime.SmallRyeWellnessHandler; import io.quarkus.smallrye.openapi.deployment.spi.AddToOpenAPIDefinitionBuildItem; -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.devmode.NotFoundPageDisplayableEndpointBuildItem; -import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.smallrye.health.SmallRyeHealthReporter; import io.smallrye.health.api.HealthGroup; import io.smallrye.health.api.HealthGroups; @@ -97,7 +94,8 @@ class SmallRyeHealthProcessor { private static final String HEALTH_UI_WEBJAR_ARTIFACT_ID = "smallrye-health-ui"; private static final String HEALTH_UI_WEBJAR_PREFIX = "META-INF/resources/health-ui/"; private static final String HEALTH_UI_FINAL_DESTINATION = "META-INF/health-ui-files"; - private static final String FILE_TO_UPDATE = "healthui.js"; + private static final String JS_FILE_TO_UPDATE = "healthui.js"; + private static final String INDEX_FILE_TO_UPDATE = "index.html"; // Branding files to monitor for changes private static final String BRANDING_DIR = "META-INF/branding/"; @@ -150,11 +148,7 @@ void healthCheck(BuildProducer buildItemBuildProducer, void build(SmallRyeHealthRecorder recorder, BuildProducer feature, BuildProducer additionalBean, - BuildProducer beanDefiningAnnotation, - BuildProducer displayableEndpoints, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - LaunchModeBuildItem launchMode, - SmallRyeHealthConfig healthConfig) + BuildProducer beanDefiningAnnotation) throws IOException, ClassNotFoundException { feature.produce(new FeatureBuildItem(Feature.SMALLRYE_HEALTH)); @@ -207,6 +201,7 @@ public void defineHealthRoutes(BuildProducer routes, // Register the health handler routes.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(healthConfig.rootPath) + .routeConfigKey("quarkus.smallrye-health.root-path") .handler(new SmallRyeHealthHandler()) .requiresLegacyRedirect() .displayOnNotFoundPage() @@ -278,16 +273,17 @@ public void defineHealthRoutes(BuildProducer routes, @BuildStep(onlyIf = OpenAPIIncluded.class) public void includeInOpenAPIEndpoint(BuildProducer openAPIProducer, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - HttpRootPathBuildItem httpRootPath, Capabilities capabilities, SmallRyeHealthConfig healthConfig) { // Add to OpenAPI if OpenAPI is available if (capabilities.isPresent(Capability.SMALLRYE_OPENAPI)) { String healthRootPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath); + HealthOpenAPIFilter filter = new HealthOpenAPIFilter(healthRootPath, nonApplicationRootPathBuildItem.resolveNestedPath(healthRootPath, healthConfig.livenessPath), nonApplicationRootPathBuildItem.resolveNestedPath(healthRootPath, healthConfig.readinessPath)); + openAPIProducer.produce(new AddToOpenAPIDefinitionBuildItem(filter)); } } @@ -316,17 +312,17 @@ private void warnIfJaxRsPathUsed(IndexView index, DotName healthAnnotation) { } @BuildStep - public void kubernetes(HttpBuildTimeConfig httpConfig, NonApplicationRootPathBuildItem frameworkRootPath, + public void kubernetes(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, SmallRyeHealthConfig healthConfig, BuildProducer livenessPathItemProducer, BuildProducer readinessPathItemProducer) { livenessPathItemProducer.produce( new KubernetesHealthLivenessPathBuildItem( - frameworkRootPath.resolveNestedPath(healthConfig.rootPath, healthConfig.livenessPath))); + nonApplicationRootPathBuildItem.resolveNestedPath(healthConfig.rootPath, healthConfig.livenessPath))); readinessPathItemProducer.produce( new KubernetesHealthReadinessPathBuildItem( - frameworkRootPath.resolveNestedPath(healthConfig.rootPath, healthConfig.readinessPath))); + nonApplicationRootPathBuildItem.resolveNestedPath(healthConfig.rootPath, healthConfig.readinessPath))); } @BuildStep @@ -386,7 +382,6 @@ void registerUiExtension( BuildProducer generatedResourceProducer, BuildProducer nativeImageResourceProducer, BuildProducer smallRyeHealthBuildProducer, - HttpRootPathBuildItem httpRootPath, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, SmallRyeHealthConfig healthConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, @@ -400,9 +395,8 @@ void registerUiExtension( "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.resolvePath(nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath)); - String healthUiPath = httpRootPath - .resolvePath(nonApplicationRootPathBuildItem.resolvePath(healthConfig.ui.rootPath)); + String healthPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.rootPath); + String healthUiPath = nonApplicationRootPathBuildItem.resolvePath(healthConfig.ui.rootPath); AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, HEALTH_UI_WEBJAR_GROUP_ID, HEALTH_UI_WEBJAR_ARTIFACT_ID); @@ -410,7 +404,8 @@ void registerUiExtension( if (launchMode.getLaunchMode().isDevOrTest()) { Path tempPath = WebJarUtil.copyResourcesForDevOrTest(curateOutcomeBuildItem, launchMode, artifact, HEALTH_UI_WEBJAR_PREFIX); - updateApiUrl(tempPath.resolve(FILE_TO_UPDATE), healthPath); + updateApiUrl(tempPath.resolve(JS_FILE_TO_UPDATE), healthPath); + updateApiUrl(tempPath.resolve(INDEX_FILE_TO_UPDATE), healthPath); smallRyeHealthBuildProducer .produce(new SmallRyeHealthBuildItem(tempPath.toAbsolutePath().toString(), healthUiPath)); @@ -428,7 +423,7 @@ void registerUiExtension( String fileName = file.getKey(); byte[] content = file.getValue(); - if (fileName.endsWith(FILE_TO_UPDATE)) { + if (fileName.endsWith(JS_FILE_TO_UPDATE) || fileName.endsWith(INDEX_FILE_TO_UPDATE)) { content = updateApiUrl(new String(content, StandardCharsets.UTF_8), healthPath) .getBytes(StandardCharsets.UTF_8); } @@ -457,8 +452,10 @@ void registerHealthUiHandler( if (shouldInclude(launchMode, healthConfig)) { Handler handler = recorder.uiHandler(smallRyeHealthBuildItem.getHealthUiFinalDestination(), smallRyeHealthBuildItem.getHealthUiPath(), runtimeConfig); + routeProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(healthConfig.ui.rootPath) + .routeConfigKey("quarkus.smallrye-health.ui.root-path") .displayOnNotFoundPage("Health UI") .requiresLegacyRedirect() .handler(handler) @@ -471,16 +468,18 @@ void registerHealthUiHandler( } } - private void updateApiUrl(Path healthUiJs, String healthPath) throws IOException { - String content = new String(Files.readAllBytes(healthUiJs), StandardCharsets.UTF_8); + private void updateApiUrl(Path fileToUpdate, String healthPath) throws IOException { + String content = new String(Files.readAllBytes(fileToUpdate), StandardCharsets.UTF_8); String result = updateApiUrl(content, healthPath); if (result != null) { - Files.write(healthUiJs, result.getBytes(StandardCharsets.UTF_8)); + Files.write(fileToUpdate, result.getBytes(StandardCharsets.UTF_8)); } } + // Replace health URL in static files public String updateApiUrl(String original, String healthPath) { - return original.replace("url = \"/health\";", "url = \"" + healthPath + "\";"); + return original.replace("url = \"/health\";", "url = \"" + healthPath + "\";") + .replace("placeholder=\"/health\"", "placeholder=\"" + healthPath + "\""); } private static boolean shouldInclude(LaunchModeBuildItem launchMode, SmallRyeHealthConfig healthConfig) { 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 f85e119bbce3c..be3861e301175 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 @@ -8,6 +8,7 @@ public class SmallRyeHealthUIConfig { /** * The path where Health UI is available. * The value `/` is not allowed as it blocks the application from serving anything else. + * By default, this value will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "health-ui") String rootPath; diff --git a/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html b/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html index 6dff17579d7d8..36f058723ff00 100644 --- a/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/smallrye-health/deployment/src/main/resources/dev-templates/embedded.html @@ -1,7 +1,7 @@ - + Health
- + Health UI 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 index 70a5a53d0e072..266d2cb33dcd5 100644 --- 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 @@ -44,7 +44,6 @@ public void handle(RoutingContext event) { .setDefaultContentEncoding("UTF-8"); if (event.normalisedPath().length() == healthUiPath.length()) { - event.response().setStatusCode(302); event.response().headers().set(HttpHeaders.LOCATION, healthUiPath + "/"); event.response().end(); diff --git a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java index 5bfbadd59f33d..310ae17e4d580 100644 --- a/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java +++ b/extensions/smallrye-metrics/deployment/src/main/java/io/quarkus/smallrye/metrics/deployment/SmallRyeMetricsProcessor.java @@ -163,12 +163,12 @@ void createRoute(BuildProducer routes, .route(metrics.path + (metrics.path.endsWith("/") ? "*" : "/*")) .handler(recorder.handler(frameworkRoot.resolvePath(metrics.path))) .requiresLegacyRedirect() - .displayOnNotFoundPage("Metrics", metrics.path) .blockingRoute() .build()); routes.produce(frameworkRoot.routeBuilder() .route(metrics.path) .handler(recorder.handler(frameworkRoot.resolvePath(metrics.path))) + .displayOnNotFoundPage("Metrics") .requiresLegacyRedirect() .blockingRoute() .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 0749a676da798..4ad0ef4b84b2c 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 @@ -172,6 +172,7 @@ RouteBuildItem handler(LaunchModeBuildItem launch, Handler handler = recorder.handler(openApiRuntimeConfig); return nonApplicationRootPathBuildItem.routeBuilder() .route(openApiConfig.path) + .routeConfigKey("quarkus.smallrye-openapi.path") .handler(handler) .displayOnNotFoundPage("Open API Schema document") .requiresLegacyRedirect() diff --git a/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html b/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html index 8f548cc42dd17..301c0af54e22a 100644 --- a/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html +++ b/extensions/smallrye-openapi/deployment/src/main/resources/dev-templates/embedded.html @@ -1,7 +1,7 @@ - + OpenAPI
- + Swagger UI 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 index ccb86c99987ab..7f50ce0c10e54 100644 --- 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 @@ -18,6 +18,7 @@ public class SwaggerUiConfig { * The path where Swagger UI is available. *

* The value `/` is not allowed as it blocks the application from serving anything else. + * By default, this value will be resolved as a path relative to `${quarkus.http.non-application-root-path}`. */ @ConfigItem(defaultValue = "swagger-ui") String path; 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 40c828dd7f9e3..c45e43a5675d6 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 @@ -27,10 +27,8 @@ 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.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; import io.smallrye.openapi.ui.IndexHtmlCreator; import io.smallrye.openapi.ui.Option; import io.smallrye.openapi.ui.ThemeHref; @@ -79,12 +77,10 @@ public void getSwaggerUiFinalDestination( BuildProducer nativeImageResourceBuildItemBuildProducer, BuildProducer swaggerUiBuildProducer, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, - BuildProducer displayableEndpoints, CurateOutcomeBuildItem curateOutcomeBuildItem, LaunchModeBuildItem launchMode, SwaggerUiConfig swaggerUiConfig, SmallRyeOpenApiConfig openapi, - HttpRootPathBuildItem httpRootPathBuildItem, LiveReloadBuildItem liveReloadBuildItem) throws Exception { if (shouldInclude(launchMode, swaggerUiConfig)) { @@ -93,9 +89,8 @@ public void getSwaggerUiFinalDestination( "quarkus.swagger-ui.path was set to \"/\", this is not allowed as it blocks the application from serving anything else."); } - String openApiPath = httpRootPathBuildItem.resolvePath(nonApplicationRootPathBuildItem.resolvePath(openapi.path)); - String swaggerUiPath = httpRootPathBuildItem - .resolvePath(nonApplicationRootPathBuildItem.resolvePath(swaggerUiConfig.path)); + String openApiPath = nonApplicationRootPathBuildItem.resolvePath(openapi.path); + String swaggerUiPath = nonApplicationRootPathBuildItem.resolvePath(swaggerUiConfig.path); AppArtifact artifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, SWAGGER_UI_WEBJAR_GROUP_ID, SWAGGER_UI_WEBJAR_ARTIFACT_ID); @@ -156,6 +151,7 @@ public void registerSwaggerUiHandler(SwaggerUiRecorder recorder, routes.produce(nonApplicationRootPathBuildItem.routeBuilder() .route(swaggerUiConfig.path) .displayOnNotFoundPage("Open API UI") + .routeConfigKey("quarkus.swagger-ui.path") .handler(handler) .requiresLegacyRedirect() .build()); diff --git a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java index 08cce8ddce3ec..8c00ac69b8bb4 100644 --- a/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java +++ b/extensions/vertx-graphql/deployment/src/main/java/io/quarkus/vertx/graphql/deployment/VertxGraphqlProcessor.java @@ -68,12 +68,17 @@ void registerVertxGraphqlUI(VertxGraphqlRecorder recorder, } Handler handler = recorder.handler(); - routes.produce(nonApplicationRootPathBuildItem.routeBuilder().route(path).handler(handler) + routes.produce(nonApplicationRootPathBuildItem.routeBuilder() + .route(path) + .handler(handler) .requiresLegacyRedirect() - .displayOnNotFoundPage("GraphQL UI", path + "/") + .displayOnNotFoundPage("GraphQL UI") .build()); routes.produce( - nonApplicationRootPathBuildItem.routeBuilder().route(path + "/*").handler(handler).requiresLegacyRedirect() + nonApplicationRootPathBuildItem.routeBuilder() + .route(path + "/*") + .handler(handler) + .requiresLegacyRedirect() .build()); nativeResourcesProducer.produce(new NativeImageResourceDirectoryBuildItem("io/vertx/ext/web/handler/graphiql")); } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java index a1bf1775c2867..28b897640ce6d 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpRootPathBuildItem.java @@ -1,9 +1,18 @@ package io.quarkus.vertx.http.deployment; import java.net.URI; +import java.util.function.Consumer; +import java.util.function.Function; import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.util.UriNormalizationUtil; +import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; +import io.quarkus.vertx.http.deployment.devmode.console.ConfiguredPathInfo; +import io.quarkus.vertx.http.runtime.HandlerType; +import io.vertx.core.Handler; +import io.vertx.ext.web.Route; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; public final class HttpRootPathBuildItem extends SimpleBuildItem { @@ -65,4 +74,121 @@ public String adjustPath(String path) { public String resolvePath(String path) { return UriNormalizationUtil.normalizeWithBase(rootPath, path, false).getPath(); } + + public HttpRootPathBuildItem.Builder routeBuilder() { + return new HttpRootPathBuildItem.Builder(this); + } + + public static class Builder extends RouteBuildItem.Builder { + private final HttpRootPathBuildItem buildItem; + private RouteBuildItem.RouteType routeType = RouteBuildItem.RouteType.APPLICATION_ROUTE; + private String path; + + Builder(HttpRootPathBuildItem buildItem) { + this.buildItem = buildItem; + } + + @Override + public Builder routeFunction(Function routeFunction) { + throw new RuntimeException( + "This method is not supported using this builder. Use #routeFunction(String, Consumer)"); + } + + public Builder routeFunction(String route, Consumer routeFunction) { + route = super.absolutePath = buildItem.resolvePath(route); + + if (route.startsWith(buildItem.getRootPath())) { + // relative to http root (leading slash for vert.x route) + this.path = "/" + UriNormalizationUtil.relativize(buildItem.getRootPath(), route); + this.routeType = RouteBuildItem.RouteType.APPLICATION_ROUTE; + } else if (route.startsWith("/")) { + // absolute path + this.path = route; + this.routeType = RouteBuildItem.RouteType.ABSOLUTE_ROUTE; + } + + super.routeFunction(this.path, routeFunction); + return this; + } + + @Override + public Builder route(String route) { + routeFunction(route, null); + return this; + } + + public Builder nestedRoute(String baseRoute, String subRoute) { + if (subRoute.startsWith("/")) { + routeFunction(subRoute, null); + return this; + } + + baseRoute = baseRoute.endsWith("/") ? baseRoute : baseRoute + "/"; + routeFunction(baseRoute + subRoute, null); + return this; + } + + @Override + public Builder handler(Handler handler) { + super.handler(handler); + return this; + } + + @Override + public Builder handlerType(HandlerType handlerType) { + super.handlerType(handlerType); + return this; + } + + @Override + public Builder blockingRoute() { + super.blockingRoute(); + return this; + } + + @Override + public Builder failureRoute() { + super.failureRoute(); + return this; + } + + @Override + public Builder displayOnNotFoundPage() { + super.displayOnNotFoundPage(); + return this; + } + + @Override + public Builder displayOnNotFoundPage(String notFoundPageTitle) { + super.displayOnNotFoundPage(notFoundPageTitle); + return this; + } + + @Override + public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) { + super.displayOnNotFoundPage(notFoundPageTitle, notFoundPagePath); + return this; + } + + @Override + public Builder routeConfigKey(String attributeName) { + super.routeConfigKey(attributeName); + return this; + } + + @Override + public RouteBuildItem build() { + return new RouteBuildItem(this, routeType, false); + } + + @Override + protected ConfiguredPathInfo getRouteConfigInfo() { + return super.getRouteConfigInfo(); + } + + @Override + protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { + return super.getNotFoundEndpoint(); + } + } } 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 c6426ae14ebb5..e16242c878c30 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 @@ -7,6 +7,7 @@ import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.util.UriNormalizationUtil; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; +import io.quarkus.vertx.http.deployment.devmode.console.ConfiguredPathInfo; import io.quarkus.vertx.http.runtime.HandlerType; import io.vertx.core.Handler; import io.vertx.ext.web.Route; @@ -69,20 +70,12 @@ public boolean isAttachedToMainRouter() { */ String getVertxRouterPath() { if (attachedToMainRouter) { - return "/" + relativize(httpRootPath.getPath(), nonApplicationRootPath.getPath()); + return "/" + UriNormalizationUtil.relativize(httpRootPath.getPath(), nonApplicationRootPath.getPath()); } else { return getNonApplicationRootPath(); } } - String relativize(String rootPath, String leafPath) { - if (leafPath.startsWith(rootPath)) { - return leafPath.substring(rootPath.length()); - } - - return null; - } - public String getNormalizedHttpRootPath() { return httpRootPath.getPath(); } @@ -189,7 +182,6 @@ public static class Builder extends RouteBuildItem.Builder { private boolean requiresLegacyRedirect = false; private RouteBuildItem.RouteType routeType = RouteBuildItem.RouteType.FRAMEWORK_ROUTE; private String path; - private String absolute; Builder(NonApplicationRootPathBuildItem buildItem) { this.buildItem = buildItem; @@ -197,23 +189,23 @@ public static class Builder extends RouteBuildItem.Builder { @Override public Builder routeFunction(Function routeFunction) { - throw new RuntimeException("This method is not supported for non-application routes"); + throw new RuntimeException( + "This method is not supported using this builder. Use #routeFunction(String, Consumer)"); } public Builder routeFunction(String route, Consumer routeFunction) { - String temp = route; - route = absolute = buildItem.resolvePath(route); + route = super.absolutePath = buildItem.resolvePath(route); boolean isFrameworkRoute = buildItem.dedicatedRouterRequired && route.startsWith(buildItem.getNonApplicationRootPath()); if (isFrameworkRoute) { - // relative non-application root - this.path = "/" + buildItem.relativize(buildItem.getNonApplicationRootPath(), route); + // relative non-application root (leading slash for vert.x) + this.path = "/" + UriNormalizationUtil.relativize(buildItem.getNonApplicationRootPath(), route); this.routeType = RouteBuildItem.RouteType.FRAMEWORK_ROUTE; } else if (route.startsWith(buildItem.httpRootPath.getPath())) { - // relative to http root - this.path = "/" + buildItem.relativize(buildItem.httpRootPath.getPath(), route); + // relative to http root (leading slash for vert.x route) + this.path = "/" + UriNormalizationUtil.relativize(buildItem.httpRootPath.getPath(), route); this.routeType = RouteBuildItem.RouteType.APPLICATION_ROUTE; } else if (route.startsWith("/")) { // absolute path @@ -277,43 +269,45 @@ public Builder failureRoute() { @Override public Builder displayOnNotFoundPage() { - this.displayOnNotFoundPage = true; + super.displayOnNotFoundPage(); return this; } @Override public Builder displayOnNotFoundPage(String notFoundPageTitle) { - this.displayOnNotFoundPage = true; - this.notFoundPageTitle = notFoundPageTitle; + super.displayOnNotFoundPage(notFoundPageTitle); return this; } @Override public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) { - this.displayOnNotFoundPage = true; - this.notFoundPageTitle = notFoundPageTitle; - this.notFoundPagePath = notFoundPagePath; + super.displayOnNotFoundPage(notFoundPageTitle, notFoundPagePath); + return this; + } + + @Override + public Builder routeConfigKey(String attributeName) { + super.routeConfigKey(attributeName); return this; } + @Override public RouteBuildItem build() { // If path is same as absolute, we don't enable legacy redirect - if (requiresLegacyRedirect && path.equals(absolute)) { + if (requiresLegacyRedirect && path.equals(super.absolutePath)) { requiresLegacyRedirect = false; } return new RouteBuildItem(this, routeType, requiresLegacyRedirect); } + @Override + protected ConfiguredPathInfo getRouteConfigInfo() { + return super.getRouteConfigInfo(); + } + @Override protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { - if (!displayOnNotFoundPage) { - return null; - } - if (notFoundPagePath == null) { - throw new RuntimeException("Cannot display " + routeFunction - + " on not found page as no explicit path was specified and a route function is in use"); - } - return new NotFoundPageDisplayableEndpointBuildItem(absolute, notFoundPageTitle, true); + return super.getNotFoundEndpoint(); } } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java index 2f732f16c473e..0c89744514a19 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RouteBuildItem.java @@ -5,6 +5,7 @@ import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; +import io.quarkus.vertx.http.deployment.devmode.console.ConfiguredPathInfo; import io.quarkus.vertx.http.runtime.BasicRoute; import io.quarkus.vertx.http.runtime.HandlerType; import io.vertx.core.Handler; @@ -24,6 +25,7 @@ public static Builder builder() { private final RouteType routeType; private final boolean requiresLegacyRedirect; private final NotFoundPageDisplayableEndpointBuildItem notFoundPageDisplayableEndpoint; + private final ConfiguredPathInfo devConsoleResolvedPathBuildItem; /** * @deprecated Use the Builder instead. @@ -36,6 +38,7 @@ public RouteBuildItem(Function routeFunction, Handler handler) { this.routeType = routeType; this.requiresLegacyRedirect = requiresLegacyRedirect; this.notFoundPageDisplayableEndpoint = builder.getNotFoundEndpoint(); + this.devConsoleResolvedPathBuildItem = builder.getRouteConfigInfo(); } public Handler getHandler() { @@ -119,6 +123,10 @@ public NotFoundPageDisplayableEndpointBuildItem getNotFoundPageDisplayableEndpoi return notFoundPageDisplayableEndpoint; } + public ConfiguredPathInfo getDevConsoleResolvedPath() { + return devConsoleResolvedPathBuildItem; + } + public enum RouteType { FRAMEWORK_ROUTE, APPLICATION_ROUTE, @@ -126,7 +134,7 @@ public enum RouteType { } /** - * NonApplicationRootPathBuildItem.Builder extends this. + * HttpRootPathBuildItem.Builder and NonApplicationRootPathBuildItem.Builder extend this. * Please verify the extended builders behavior when changing this one. */ public static class Builder { @@ -136,12 +144,27 @@ public static class Builder { protected boolean displayOnNotFoundPage; protected String notFoundPageTitle; protected String notFoundPagePath; + protected String routePath; + protected String routeConfigKey; + protected String absolutePath; + + /** + * Use HttpRootPathBuildItem and NonApplicationRootPathBuildItem to + * ensure paths are constructed/normalized correctly + * + * @deprecated + * @see HttpRootPathBuildItem#routeBuilder() + * @see NonApplicationRootPathBuildItem#routeBuilder() + */ + @Deprecated + Builder() { + } /** * {@link #routeFunction(String, Consumer)} should be used instead * * @param routeFunction - * @return + * @see #routeFunction(String, Consumer) */ @Deprecated public Builder routeFunction(Function routeFunction) { @@ -149,15 +172,25 @@ public Builder routeFunction(Function routeFunction) { return this; } + /** + * @param path A normalized path (e.g. use HttpRootPathBuildItem to construct/resolve the path value) defining + * the route. This path this is also used on the "Not Found" page in dev mode. + * @param routeFunction a Consumer of Route + */ public Builder routeFunction(String path, Consumer routeFunction) { this.routeFunction = new BasicRoute(path, null, routeFunction); - this.notFoundPagePath = path; + this.notFoundPagePath = this.routePath = path; return this; } + /** + * @param route A normalized path used to define a basic route + * (e.g. use HttpRootPathBuildItem to construct/resolve the path value). This path this is also + * used on the "Not Found" page in dev mode. + */ public Builder route(String route) { this.routeFunction = new BasicRoute(route); - this.notFoundPagePath = route; + this.notFoundPagePath = this.routePath = route; return this; } @@ -192,6 +225,12 @@ public Builder displayOnNotFoundPage(String notFoundPageTitle) { return this; } + /** + * @deprecated Specify the path as part of defining the route + * @see #route(String) + * @see #routeFunction(String, Consumer) + */ + @Deprecated public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPagePath) { this.displayOnNotFoundPage = true; this.notFoundPageTitle = notFoundPageTitle; @@ -199,10 +238,29 @@ public Builder displayOnNotFoundPage(String notFoundPageTitle, String notFoundPa return this; } + public Builder routeConfigKey(String attributeName) { + this.routeConfigKey = attributeName; + return this; + } + public RouteBuildItem build() { return new RouteBuildItem(this, RouteType.APPLICATION_ROUTE, false); } + protected ConfiguredPathInfo getRouteConfigInfo() { + if (routeConfigKey == null) { + return null; + } + if (routePath == null) { + throw new RuntimeException("Cannot discover value of " + routeConfigKey + + " as no explicit path was specified and a route function is in use"); + } + if (absolutePath != null) { + return new ConfiguredPathInfo(routeConfigKey, absolutePath, true); + } + return new ConfiguredPathInfo(routeConfigKey, routePath, false); + } + protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { if (!displayOnNotFoundPage) { return null; @@ -211,7 +269,10 @@ protected NotFoundPageDisplayableEndpointBuildItem getNotFoundEndpoint() { throw new RuntimeException("Cannot display " + routeFunction + " on not found page as no explicit path was specified and a route function is in use"); } - return new NotFoundPageDisplayableEndpointBuildItem(notFoundPagePath, notFoundPageTitle); + if (absolutePath != null) { + return new NotFoundPageDisplayableEndpointBuildItem(absolutePath, notFoundPageTitle, true); + } + return new NotFoundPageDisplayableEndpointBuildItem(notFoundPagePath, notFoundPageTitle, false); } } } 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 dd39d2a01b3a2..347fa4fc38dca 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 @@ -120,14 +120,13 @@ public KubernetesPortBuildItem kubernetes() { } @BuildStep - void notRoundRoutes( + void notFoundRoutes( List routes, BuildProducer notFound) { for (RouteBuildItem i : routes) { if (i.getNotFoundPageDisplayableEndpoint() != null) { notFound.produce(i.getNotFoundPageDisplayableEndpoint()); } - } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java index a0d88468946d6..638db01bcf52a 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/NotFoundPageDisplayableEndpointBuildItem.java @@ -13,10 +13,10 @@ public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String descript this(endpoint, description, false); } - public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String description, boolean absolutePath) { + public NotFoundPageDisplayableEndpointBuildItem(String endpoint, String description, boolean isAbsolutePath) { this.endpoint = endpoint; this.description = description; - this.absolutePath = absolutePath; + this.absolutePath = isAbsolutePath; } public NotFoundPageDisplayableEndpointBuildItem(String endpoint) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java new file mode 100644 index 0000000000000..a522178d15ab9 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfiguredPathInfo.java @@ -0,0 +1,37 @@ +package io.quarkus.vertx.http.deployment.devmode.console; + +import io.quarkus.runtime.TemplateHtmlBuilder; +import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; +import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; + +public class ConfiguredPathInfo { + private final String name; + private final String endpointPath; + private final boolean absolutePath; + + public ConfiguredPathInfo(String name, String endpointPath, boolean isAbsolutePath) { + this.name = name; + this.endpointPath = endpointPath; + this.absolutePath = isAbsolutePath; + } + + public String getName() { + return name; + } + + public String getEndpointPath(HttpRootPathBuildItem httpRoot) { + if (absolutePath) { + return endpointPath; + } else { + return TemplateHtmlBuilder.adjustRoot(httpRoot.getRootPath(), endpointPath); + } + } + + public String getEndpointPath(NonApplicationRootPathBuildItem nonAppRoot) { + if (absolutePath) { + return endpointPath; + } else { + return TemplateHtmlBuilder.adjustRoot(nonAppRoot.getNormalizedHttpRootPath(), endpointPath); + } + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java index a6b931815a4cc..559c9604dcfba 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsole.java @@ -44,8 +44,13 @@ public class DevConsole implements Handler { DevConsole(Engine engine, String httpRootPath, String frameworkRootPath) { this.engine = engine; + // Both of these paths will end in slash this.globalData.put("httpRootPath", httpRootPath); - this.globalData.put("frameworkRootPath", cleanFrameworkRootPath(frameworkRootPath)); + this.globalData.put("frameworkRootPath", frameworkRootPath); + + // This includes the dev segment, but does not include a trailing slash (for append) + this.globalData.put("devRootAppend", frameworkRootPath + "dev"); + this.globalData.put("quarkusVersion", Version.getVersion()); this.globalData.put("applicationName", config.getOptionalValue("quarkus.application.name", String.class).orElse("")); this.globalData.put("applicationVersion", @@ -94,19 +99,6 @@ public void handle(RoutingContext event) { } } - /** - * This removes the last / from the path - * - * @param p the path - * @return the path without the last / - */ - private String cleanFrameworkRootPath(String p) { - if (p != null && !p.isEmpty() && p.endsWith("/")) { - return p.substring(0, p.length() - 1); - } - return p; - } - private String getExtensionName(String namespace) { Map map = extensions.get(namespace); if (map == null) diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index a71a945b9ad91..6d5c6d2da5fd5 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -55,6 +55,7 @@ import io.quarkus.deployment.util.ArtifactInfoUtil; import io.quarkus.deployment.util.WebJarUtil; import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.dev.spi.DevModeType; import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; @@ -255,26 +256,56 @@ public void runtimeTemplates(List items, @BuildStep @Record(ExecutionTime.STATIC_INIT) - public HistoryHandlerBuildItem hander(BuildProducer logHandlerBuildItemBuildProducer, + public HistoryHandlerBuildItem handler(BuildProducer logHandlerBuildItemBuildProducer, LogStreamRecorder recorder) { RuntimeValue> handler = recorder.handler(); logHandlerBuildItemBuildProducer.produce(new LogHandlerBuildItem((RuntimeValue) handler)); return new HistoryHandlerBuildItem(handler); } + @Consume(LoggingSetupBuildItem.class) + @BuildStep(onlyIf = IsDevelopment.class) + public ServiceStartBuildItem setupDeploymentSideHandling(List devTemplatePaths, + CurateOutcomeBuildItem curateOutcomeBuildItem, + List allRoutes, + List routes, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return null; + } + + initializeVirtual(); + Engine quteEngine = buildEngine(devTemplatePaths, + allRoutes, + nonApplicationRootPathBuildItem); + newRouter(quteEngine, nonApplicationRootPathBuildItem); + + for (DevConsoleRouteBuildItem i : routes) { + Entry groupAndArtifact = i.groupIdAndArtifactId(curateOutcomeBuildItem); + // deployment side handling + if (!(i.getHandler() instanceof BytecodeRecorderImpl.ReturnedProxy)) { + router.route(HttpMethod.valueOf(i.getMethod()), + "/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath()) + .handler(i.getHandler()); + } + } + + return null; + } + @Record(ExecutionTime.RUNTIME_INIT) @Consume(LoggingSetupBuildItem.class) @BuildStep(onlyIf = IsDevelopment.class) - public void setupActions(List routes, + public void setupDevConsoleRoutes(List routes, BuildProducer routeBuildItemBuildProducer, - List devTemplatePaths, LogStreamRecorder recorder, CurateOutcomeBuildItem curateOutcomeBuildItem, HistoryHandlerBuildItem historyHandlerBuildItem, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { - initializeVirtual(); - - newRouter(buildEngine(devTemplatePaths), nonApplicationRootPathBuildItem); + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + LaunchModeBuildItem launchModeBuildItem) { + if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) { + return; + } // Add the log stream routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() @@ -285,6 +316,7 @@ public void setupActions(List routes, for (DevConsoleRouteBuildItem i : routes) { Entry groupAndArtifact = i.groupIdAndArtifactId(curateOutcomeBuildItem); // if the handler is a proxy, then that means it's been produced by a recorder and therefore belongs in the regular runtime Vert.x instance + // otherwise this is handled in the setupDeploymentSideHandling method if (i.getHandler() instanceof BytecodeRecorderImpl.ReturnedProxy) { routeBuildItemBuildProducer.produce(nonApplicationRootPathBuildItem.routeBuilder() .routeFunction( @@ -292,10 +324,6 @@ public void setupActions(List routes, new RuntimeDevConsoleRoute(i.getMethod())) .handler(i.getHandler()) .build()); - } else { - router.route(HttpMethod.valueOf(i.getMethod()), - "/" + groupAndArtifact.getKey() + "." + groupAndArtifact.getValue() + "/" + i.getPath()) - .handler(i.getHandler()); } } @@ -317,7 +345,12 @@ public void setupActions(List routes, public void deployStaticResources(DevConsoleRecorder recorder, CurateOutcomeBuildItem curateOutcomeBuildItem, LaunchModeBuildItem launchMode, ShutdownContextBuildItem shutdownContext, BuildProducer routeBuildItemBuildProducer, - NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) throws IOException { + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + LaunchModeBuildItem launchModeBuildItem) throws IOException { + + if (launchModeBuildItem.getDevModeType().orElse(DevModeType.LOCAL) != DevModeType.LOCAL) { + return; + } AppArtifact devConsoleResourcesArtifact = WebJarUtil.getAppArtifact(curateOutcomeBuildItem, "io.quarkus", "quarkus-vertx-http-deployment"); @@ -330,7 +363,9 @@ public void deployStaticResources(DevConsoleRecorder recorder, CurateOutcomeBuil .build()); } - private Engine buildEngine(List devTemplatePaths) { + private Engine buildEngine(List devTemplatePaths, + List allRoutes, + NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) { EngineBuilder builder = Engine.builder().addDefaults(); // Escape some characters for HTML templates @@ -352,16 +387,35 @@ private Engine buildEngine(List devTemplatePaths) { return result == null ? Results.Result.NOT_FOUND : result; }).build()); + // Create map of resolved paths + Map resolvedPaths = new HashMap<>(); + for (RouteBuildItem item : allRoutes) { + ConfiguredPathInfo resolvedPathBuildItem = item.getDevConsoleResolvedPath(); + if (resolvedPathBuildItem != null) { + resolvedPaths.put(resolvedPathBuildItem.getName(), + resolvedPathBuildItem.getEndpointPath(nonApplicationRootPathBuildItem)); + } + } + // {config:property('quarkus.lambda.handler')} + // {config:http-path('quarkus.smallrye-graphql.ui.root-path')} + // Note that the output value is always string! builder.addNamespaceResolver(NamespaceResolver.builder("config").resolveAsync(ctx -> { List params = ctx.getParams(); - if (params.size() != 1 || !ctx.getName().equals("property")) { + if (params.size() != 1 || (!ctx.getName().equals("property") && !ctx.getName().equals("http-path"))) { return Results.NOT_FOUND; } - return ctx.evaluate(params.get(0)).thenCompose(propertyName -> { - Optional val = ConfigProvider.getConfig().getOptionalValue(propertyName.toString(), String.class); - return CompletableFuture.completedFuture(val.isPresent() ? val.get() : Result.NOT_FOUND); - }); + if (ctx.getName().equals("http-path")) { + return ctx.evaluate(params.get(0)).thenCompose(propertyName -> { + String value = resolvedPaths.get(propertyName.toString()); + return CompletableFuture.completedFuture(value != null ? value : Result.NOT_FOUND); + }); + } else { + return ctx.evaluate(params.get(0)).thenCompose(propertyName -> { + Optional val = ConfigProvider.getConfig().getOptionalValue(propertyName.toString(), String.class); + return CompletableFuture.completedFuture(val.isPresent() ? val.get() : Result.NOT_FOUND); + }); + } }).build()); // JavaDoc formatting diff --git a/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html b/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html index de8057fae3848..7aabed2c9dfee 100644 --- a/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html +++ b/extensions/vertx-http/deployment/src/main/resources/dev-templates/logmanagerNav.html @@ -1,6 +1,6 @@