Skip to content

Commit

Permalink
Merge pull request #15604 from gsmet/1.12.2-backports-2
Browse files Browse the repository at this point in the history
1.12.2 backports 2
  • Loading branch information
gsmet authored Mar 10, 2021
2 parents 3a6238c + 4fcab67 commit 83cb710
Show file tree
Hide file tree
Showing 46 changed files with 649 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ private BuildResult runAugment(boolean firstRun, Set<String> changedResources,
}

builder.setLaunchMode(launchMode);
builder.setDevModeType(devModeType);
builder.setRebuild(quarkusBootstrap.isRebuild());
if (firstRun) {
builder.setLiveReloadState(
Expand Down
1 change: 1 addition & 0 deletions core/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<parentFirstArtifact>org.jacoco:org.jacoco.agent:runtime</parentFirstArtifact>
</parentFirstArtifacts>
<runnerParentFirstArtifacts>
<runnerParentFirstArtifact>org.graalvm.sdk:graal-sdk</runnerParentFirstArtifact>
<runnerParentFirstArtifact>io.quarkus:quarkus-bootstrap-runner</runnerParentFirstArtifact>
<runnerParentFirstArtifact>io.quarkus:quarkus-development-mode-spi</runnerParentFirstArtifact>
<!-- logging dependencies always need to be loaded by the JDK ClassLoader -->
Expand Down
69 changes: 59 additions & 10 deletions docs/src/main/asciidoc/dev-ui.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -46,16 +46,56 @@ two links with some styling and icons:

[source,html]
----
<a href="/q/openapi" class="badge badge-light">
<i class="fa ..."></i>
<a href="{config:http-path('quarkus.smallrye-openapi.path')}" class="badge badge-light">
<i class="fa fa-map-signs fa-fw"></i>
OpenAPI</a>
<a href="/q/swagger-ui/" class="badge badge-light">
<i class="fa ..."></i>
<br>
<a href="{config:http-path('quarkus.swagger-ui.path')}/" class="badge badge-light">
<i class="fa fa-map-signs fa-fw"></i>
Swagger UI</a>
----

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]
----
<img src="{devRootAppend}/resources/images/quarkus_icon_rgb_reverse.svg" width="40" height="30" class="d-inline-block align-middle" alt="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
Expand All @@ -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:
Expand Down Expand Up @@ -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]
----
<a href="{urlbase}/caches" class="badge badge-light">// <1>
<i class="fa ..."></i>
Caches <span class="badge badge-light">{info:cacheInfos.size()}</span></a>
----
<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

Expand Down Expand Up @@ -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;
Expand All @@ -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 <<action-example,how to invoke this action from your full page>>.

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;
Expand Down Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void createPrometheusRoute(BuildProducer<RouteBuildItem> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -116,10 +116,10 @@ List<ReflectiveClassBuildItem> addExtensionPointsToNative(CodecProviderBuildItem
}

@BuildStep
public void mongoClientNames(ApplicationArchivesBuildItem applicationArchivesBuildItem,
public void mongoClientNames(BeanArchiveIndexBuildItem indexBuildItem,
BuildProducer<MongoClientNameBuildItem> mongoClientName) {
Set<String> 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) {
Expand Down Expand Up @@ -163,6 +163,16 @@ MongoConnectionPoolListenerBuildItem setupMetrics(
return null;
}

@BuildStep
void additionalBeans(BuildProducer<AdditionalBeanBuildItem> 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(
Expand All @@ -175,22 +185,13 @@ void build(
CommandListenerBuildItem commandListener,
List<MongoConnectionPoolListenerBuildItem> connectionPoolListenerProvider,
BuildProducer<MongoConnectionNameBuildItem> mongoConnections,
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer,
BuildProducer<AdditionalBeanBuildItem> 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<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer) {

List<Supplier<ConnectionPoolListener>> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<SyntheticBeanBuildItem> syntheticBeans,
VertxBuildItem vertxBuildItem) {
Set<String> clientNames = new HashSet<>();
clientNames.add(RedisClientUtil.DEFAULT_CLIENT);

IndexView indexView = applicationArchives.getRootArchive().getIndex();
IndexView indexView = indexBuildItem.getIndex();
Collection<AnnotationInstance> clientAnnotations = indexView.getAnnotations(REDIS_CLIENT_ANNOTATION);
for (AnnotationInstance annotation : clientAnnotations) {
clientNames.add(annotation.value().asString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,10 @@ public List<HandlerChainCustomizer> scan(MethodInfo method, Map<String, Object>
});
}

private String determineApplicationPath(IndexView index, String defaultPath) {
private String determineApplicationPath(IndexView index, Optional<String> defaultPath) {
Collection<AnnotationInstance> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package io.quarkus.resteasy.reactive.server.deployment;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot(name = "rest")
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.
* <p>
* This value is always resolved relative to {@code quarkus.http.root-path}.
*/
@ConfigItem(defaultValue = "/")
String path;
@ConfigItem
Optional<String> path;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/hello")
/**
* Per spec:
* <quote>
* 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 '/'.
* </quote>
*/
@Path("hello")
public class HelloResource {

@GET
public String hello() {
return "hello";
}

@GET
@Path("/nested")
public String nested() {
return "world hello";
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Loading

0 comments on commit 83cb710

Please sign in to comment.